Skip to content

πŸ”€ :: (#954) AI 챗봇 κΈ°λŠ₯ κ΅¬ν˜„#955

Merged
ashxom merged 65 commits into
developfrom
feature/954-ai-챗봇-κΈ°λŠ₯-κ΅¬ν˜„
Jul 5, 2026

Hidden character warning

The head ref may contain hidden characters: "feature/954-ai-\ucc57\ubd07-\uae30\ub2a5-\uad6c\ud604"
Merged

πŸ”€ :: (#954) AI 챗봇 κΈ°λŠ₯ κ΅¬ν˜„#955
ashxom merged 65 commits into
developfrom
feature/954-ai-챗봇-κΈ°λŠ₯-κ΅¬ν˜„

Conversation

@ashxom

@ashxom ashxom commented Jul 5, 2026

Copy link
Copy Markdown
Member

κ°œμš”

AI 챗봇을 톡해 κΈ°μˆ™μ‚¬ κ·œμ • μ§ˆμ˜μ‘λ‹΅μ„ ν•  수 μžˆλŠ” κΈ°λŠ₯을 κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

μž‘μ—…μ‚¬ν•­

μΆ”κ°€ 둜 ν•  말

Summary by CodeRabbit

  • New Features

    • Added a new Chatbot tab in the bottom navigation.
    • Introduced a chat screen with message bubbles, suggestion chips, typing indicator, and a message input bar.
    • Chatbot responses now appear in the conversation view, with support for loading states.
  • Bug Fixes

    • Improved navigation behavior so the bottom bar hides appropriately on the chat screen and tab switching keeps the back stack clean.

ashxom added 30 commits July 2, 2026 21:48
ashxom added 19 commits July 3, 2026 10:12
@ashxom ashxom self-assigned this Jul 5, 2026
@ashxom ashxom added the feat μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ ν•  경우 label Jul 5, 2026
@ashxom ashxom linked an issue Jul 5, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ashxom, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd03dc6a-0dc6-4b0d-92b7-86e666e89f43

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between a49a1b0 and 49f04fe.

πŸ“’ Files selected for processing (4)
  • app/src/main/kotlin/team/aliens/dms/android/app/ui/DmsApp.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/ChatBotScreen.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotInputBar.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/viewmodel/ChatBotViewModel.kt
πŸ“ Walkthrough

Walkthrough

This PR adds a full chatbot feature: network API/models/datasource, a data-layer repository, a Compose-based chat UI (screen, input bar, message bubbles, suggestion chips, typing indicator), a ViewModel managing chat state, new chatbot icons, and app navigation/bottom-bar integration including a new ChatBotScreenNav route and bottom-bar visibility logic tied to input focus.

Changes

Chatbot Feature

Layer / File(s) Summary
Network layer: API, models, datasource, DI
network/.../chatbot/apiservice/ChatBotApiService.kt, network/.../chatbot/model/ChatBotAnswerResponse.kt, ChatBotQuestionRequest.kt, network/.../chatbot/datasource/NetworkChatBotDataSource.kt, NetworkChatBotDataSourceImpl.kt, network/.../chatbot/di/ChatBotNetworkModule.kt
Retrofit ChatBotApiService.askQuestion POSTs to /chatbots/questions; new request/response models; NetworkChatBotDataSourceImpl delegates to the API; Hilt module binds datasource and provides the API service.
Data layer: repository and DI binding
data/.../chatbot/repository/ChatBotRepository.kt, ChatBotRepositoryImpl.kt, data/.../chatbot/di/ChatBotDataModule.kt
ChatBotRepository interface with askQuestion; ChatBotRepositoryImpl delegates to the network datasource; Hilt module binds the implementation.
ViewModel and UI state
feature/.../chatbot/viewmodel/ChatBotState.kt, ChatBotViewModel.kt
New ChatBotState/ChatBotMessage data classes; ChatBotViewModel manages input, sends questions via the repository, appends messages, and handles loading/error states.
Chat UI components
feature/.../chatbot/ui/component/ChatBotInputBar.kt, ChatBotMessageBubble.kt, ChatBotSuggestionChip.kt, ChatBotTypingIndicator.kt
Compose components for text input with send action, message/typing bubbles, clickable suggestion chips, and an animated typing indicator.
Chat screen and route
feature/.../chatbot/ui/ChatBotScreen.kt
ChatBotRoute wires the Hilt viewmodel to ChatBotScreen, which renders headers, suggestion chips or a scrollable message list with auto-scroll, typing indicator, and the input bar.
Navigation, bottom bar, and icons
app/.../navigation/DmsNavKeys.kt, BottomMenu.kt, BottomNavigationBar.kt, app/.../ui/DmsApp.kt, core/design-system/.../DmsIcon.kt, drawable/ic_chatbot*.xml
Adds ChatBotScreenNav key and BottomMenu.ChatBot entry with new icons; bottom nav items are always enabled and only navigate when not selected; DmsApp wires the chat route, hides the bottom bar when chat input is focused, and clears chat entries on tab switch.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatBotScreen
  participant ChatBotViewModel
  participant ChatBotRepository
  participant NetworkChatBotDataSource
  participant ChatBotApiService

  ChatBotScreen->>ChatBotViewModel: sendQuestion()
  ChatBotViewModel->>ChatBotViewModel: append user message, isLoading=true
  ChatBotViewModel->>ChatBotRepository: askQuestion(question)
  ChatBotRepository->>NetworkChatBotDataSource: askQuestion(request)
  NetworkChatBotDataSource->>ChatBotApiService: askQuestion(request)
  ChatBotApiService-->>NetworkChatBotDataSource: ChatBotAnswerResponse
  NetworkChatBotDataSource-->>ChatBotRepository: ChatBotAnswerResponse
  ChatBotRepository-->>ChatBotViewModel: answer
  ChatBotViewModel-->>ChatBotScreen: updated state (message, isLoading=false)
Loading
sequenceDiagram
  participant BottomNavigationBar
  participant DmsApp
  participant ChatBotRoute

  BottomNavigationBar->>DmsApp: onNavigate(ChatBotScreenNav)
  DmsApp->>ChatBotRoute: render NavDisplay entry
  ChatBotRoute->>DmsApp: onInputFocusChanged(isFocused)
  DmsApp->>DmsApp: update isChatInputFocused, toggle bottom bar visibility
Loading

Possibly related PRs

  • team-aliens/DMS-Android#852: Both PRs modify bottom-navigation wiring in BottomNavigationBar.kt (selection/enablement/routing) and DmsApp.kt bottom-bar behavior.

Suggested reviewers: parkuiery

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title clearly matches the main change: implementing an AI chatbot feature.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/954-ai-챗봇-κΈ°λŠ₯-κ΅¬ν˜„

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotTypingIndicator.kt (1)

17-35: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Consider animating the typing dots.

The dots currently render statically; an infinite pulsing/bouncing animation (e.g., rememberInfiniteTransition) would better communicate "typing" activity to users.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotTypingIndicator.kt`
around lines 17 - 35, The ChatBotTypingIndicator composable currently renders
static dots, so update it to animate the typing indicator instead of showing
fixed circles. Use ChatBotTypingIndicator and its three-dot loop to add an
infinite pulsing or bouncing effect with a Compose animation API such as
rememberInfiniteTransition, and apply the animated state to each dot so the
indicator clearly conveys ongoing typing activity.
feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/ChatBotScreen.kt (1)

121-131: πŸš€ Performance & Scalability | πŸ”΅ Trivial | ⚑ Quick win

Use items(..., key = ...) instead of forEach + item for the message list.

Building LazyColumn content with state.messages.forEach { item { ... } } gives Compose no stable identity for each row, so any mutation to messages forces full re-evaluation/recomposition of all items and disables efficient diffing. Prefer items(state.messages, key = { it.id }) (assuming ChatBotMessage has/gets a stable id) for typing bubble too.

♻️ Suggested refactor
-                state.messages.forEach { message ->
-                    item {
-                        ChatBotMessageItem(message = message)
-                    }
-                }
+                items(
+                    items = state.messages,
+                    key = { it.id },
+                ) { message ->
+                    ChatBotMessageItem(message = message)
+                }
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/ChatBotScreen.kt`
around lines 121 - 131, Replace the `state.messages.forEach { item { ... } }`
pattern in `ChatBotScreen` with `items(...)` so each chat row has stable
identity and Compose can diff efficiently. Update the `LazyColumn` content to
use `items(state.messages, key = { it.id })` for `ChatBotMessageItem`, and give
the typing bubble (`ChatBotTypingItem`) a stable keyed item as well if it is
part of the list. Use the existing `state.messages` and `ChatBotMessage` model
to locate the change.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/viewmodel/ChatBotViewModel.kt`:
- Around line 45-73: The `askQuestion` call inside `ChatBotViewModel` is wrapped
in `runCatching`, which swallows `CancellationException` and incorrectly treats
coroutine cancellation as a normal failure. Update this flow to preserve
cancellation by either rethrowing `CancellationException` explicitly or
replacing `runCatching` with a `try/catch` around
`chatBotRepository.askQuestion(question)` that rethrows cancellation before
handling real errors in the `onFailure`-equivalent path.

---

Nitpick comments:
In
`@feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/ChatBotScreen.kt`:
- Around line 121-131: Replace the `state.messages.forEach { item { ... } }`
pattern in `ChatBotScreen` with `items(...)` so each chat row has stable
identity and Compose can diff efficiently. Update the `LazyColumn` content to
use `items(state.messages, key = { it.id })` for `ChatBotMessageItem`, and give
the typing bubble (`ChatBotTypingItem`) a stable keyed item as well if it is
part of the list. Use the existing `state.messages` and `ChatBotMessage` model
to locate the change.

In
`@feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotTypingIndicator.kt`:
- Around line 17-35: The ChatBotTypingIndicator composable currently renders
static dots, so update it to animate the typing indicator instead of showing
fixed circles. Use ChatBotTypingIndicator and its three-dot loop to add an
infinite pulsing or bouncing effect with a Compose animation API such as
rememberInfiniteTransition, and apply the animated state to each dot so the
indicator clearly conveys ongoing typing activity.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d340e95b-00c2-47e7-b14d-48db0f61acd6

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 46f1f68 and a49a1b0.

πŸ“’ Files selected for processing (23)
  • app/src/main/kotlin/team/aliens/dms/android/app/navigation/BottomMenu.kt
  • app/src/main/kotlin/team/aliens/dms/android/app/navigation/BottomNavigationBar.kt
  • app/src/main/kotlin/team/aliens/dms/android/app/navigation/DmsNavKeys.kt
  • app/src/main/kotlin/team/aliens/dms/android/app/ui/DmsApp.kt
  • core/design-system/src/main/java/team/aliens/dms/android/core/designsystem/foundation/DmsIcon.kt
  • core/design-system/src/main/res/drawable/ic_chatbot.xml
  • core/design-system/src/main/res/drawable/ic_chatbot_fill.xml
  • data/src/main/kotlin/team/aliens/dms/android/data/chatbot/di/ChatBotDataModule.kt
  • data/src/main/kotlin/team/aliens/dms/android/data/chatbot/repository/ChatBotRepository.kt
  • data/src/main/kotlin/team/aliens/dms/android/data/chatbot/repository/ChatBotRepositoryImpl.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/ChatBotScreen.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotInputBar.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotMessageBubble.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotSuggestionChip.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/ui/component/ChatBotTypingIndicator.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/viewmodel/ChatBotState.kt
  • feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/viewmodel/ChatBotViewModel.kt
  • network/src/main/kotlin/team/aliens/dms/android/network/chatbot/apiservice/ChatBotApiService.kt
  • network/src/main/kotlin/team/aliens/dms/android/network/chatbot/datasource/NetworkChatBotDataSource.kt
  • network/src/main/kotlin/team/aliens/dms/android/network/chatbot/datasource/NetworkChatBotDataSourceImpl.kt
  • network/src/main/kotlin/team/aliens/dms/android/network/chatbot/di/ChatBotNetworkModule.kt
  • network/src/main/kotlin/team/aliens/dms/android/network/chatbot/model/ChatBotAnswerResponse.kt
  • network/src/main/kotlin/team/aliens/dms/android/network/chatbot/model/ChatBotQuestionRequest.kt

Comment on lines +45 to +73
runCatching {
chatBotRepository.askQuestion(question)
}.onSuccess { answer ->
_state.update {
it.copy(
isLoading = false,
messages = it.messages + ChatBotMessage(
text = answer,
isUser = false,
),
)
}
}.onFailure { throwable ->
Log.e(
"ChatBot",
"request failed: ${throwable::class.java.simpleName}, message=${throwable.message}",
throwable,
)

_state.update {
it.copy(
isLoading = false,
messages = it.messages + ChatBotMessage(
text = "닡변을 λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμ–΄μš”. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.",
isUser = false,
),
)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

Rethrow CancellationException from runCatching.

runCatching wraps a suspend call and catches all Throwables, including CancellationException. If viewModelScope is cancelled mid-request (e.g., ViewModel cleared), the cancellation is swallowed and turned into a normal failure path, which continues to update state and log as if it were a real error instead of allowing the coroutine to stop β€” a documented anti-pattern for runCatching + suspend calls.

πŸ›‘οΈ Proposed fix to rethrow cancellation
             runCatching {
                 chatBotRepository.askQuestion(question)
+            }.onFailure { throwable ->
+                if (throwable is kotlinx.coroutines.CancellationException) throw throwable
             }.onSuccess { answer ->

Or more idiomatically, replace runCatching with a try { ... } catch (e: CancellationException) { throw e } catch (e: Throwable) { ... } block.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
runCatching {
chatBotRepository.askQuestion(question)
}.onSuccess { answer ->
_state.update {
it.copy(
isLoading = false,
messages = it.messages + ChatBotMessage(
text = answer,
isUser = false,
),
)
}
}.onFailure { throwable ->
Log.e(
"ChatBot",
"request failed: ${throwable::class.java.simpleName}, message=${throwable.message}",
throwable,
)
_state.update {
it.copy(
isLoading = false,
messages = it.messages + ChatBotMessage(
text = "닡변을 λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμ–΄μš”. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.",
isUser = false,
),
)
}
}
runCatching {
chatBotRepository.askQuestion(question)
}.onFailure { throwable ->
if (throwable is kotlinx.coroutines.CancellationException) throw throwable
}.onSuccess { answer ->
_state.update {
it.copy(
isLoading = false,
messages = it.messages + ChatBotMessage(
text = answer,
isUser = false,
),
)
}
}.onFailure { throwable ->
Log.e(
"ChatBot",
"request failed: ${throwable::class.java.simpleName}, message=${throwable.message}",
throwable,
)
_state.update {
it.copy(
isLoading = false,
messages = it.messages + ChatBotMessage(
text = "닡변을 λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμ–΄μš”. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.",
isUser = false,
),
)
}
}
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/src/main/kotlin/team/aliens/dms/android/feature/chatbot/viewmodel/ChatBotViewModel.kt`
around lines 45 - 73, The `askQuestion` call inside `ChatBotViewModel` is
wrapped in `runCatching`, which swallows `CancellationException` and incorrectly
treats coroutine cancellation as a normal failure. Update this flow to preserve
cancellation by either rethrowing `CancellationException` explicitly or
replacing `runCatching` with a `try/catch` around
`chatBotRepository.askQuestion(question)` that rethrows cancellation before
handling real errors in the `onFailure`-equivalent path.

@ashxom
ashxom merged commit e5652b5 into develop Jul 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ ν•  경우

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI 챗봇 κΈ°λŠ₯ κ΅¬ν˜„

2 participants