Skip to content

AI 기반 이미지 설명 기능을 추가한다#73

Merged
komodgn merged 2 commits intodevelopfrom
feature/#72
Jan 12, 2026
Merged

AI 기반 이미지 설명 기능을 추가한다#73
komodgn merged 2 commits intodevelopfrom
feature/#72

Conversation

@komodgn
Copy link
Owner

@komodgn komodgn commented Jan 12, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added AI-powered image analysis feature that provides detailed descriptions of photos.
    • Introduced modal interface for displaying AI analysis results with loading indicators and user notifications.
  • Bug Fixes

    • Fixed API endpoint URL path construction.
  • Refactor

    • Removed legacy commented code.

✏️ Tip: You can customize this high-level summary in your review settings.

- 이미지 메타데이터 활용하여 이미지 설명 생성
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

This PR introduces image description functionality by adding a new OpenAI-powered API endpoint, implementing backend repository logic, extending UI state management with toast notifications, and creating modal bottom sheet components to display AI-generated image descriptions alongside user feedback mechanisms.

Changes

Cohort / File(s) Summary
Constants & Prompts
core/common/src/main/java/com/example/metasearch/core/common/constants/PromptConstants.kt
Added CREATE_IMAGE_BASIC_PROMPT constant containing Korean instructions for image analysis.
Repository Interface
core/data/api/src/main/java/com/example/metasearch/core/data/api/repository/ImageAnalysisRepository.kt
Added suspend fun getImageDescription(uriString: String): Result<String?> method to interface.
Repository Implementation
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt
Implemented getImageDescription() with file name retrieval, database lookups, prompt building, OpenAI chat completion requests, and error handling via coroutine scope and async operations.
Network Layer
core/network/src/main/java/com/example/metasearch/core/network/service/WebService.kt
Changed fetchTripleData endpoint path from absolute (/path) to relative (path).
UI State
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUiState.kt
Extended PhotoDetailUiState with toastMessage: String? and imageDescription: String? fields; added ShowToast(message: String) and HideToast events to PhotoDetailUiEvent.
Presenter Logic
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailPresenter.kt
Added ImageAnalysisRepository dependency; implemented OnCreateImageDescriptionButtonClick handler with async image description fetching, loading state toggles, exception handling, and toast state management.
Composable Components
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailToastEffect.kt,
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt,
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/component/ImageDescriptionBottomSheetContent.kt
Added PhotoDetailToastEffect composable to auto-hide toasts after 1.5s; integrated ModalBottomSheet with automatic opening on image description arrival; added MetaSearchLoadingIndicator and MetaSearchToast displays; created ImageDescriptionBottomSheetContent component for rendering AI results.
String Resources
feature/detail/src/main/res/values/strings.xml
Added photo_detail_screen_openai_bottom_sheet_title string resource ("🐻‍❄️ AI 이미지 분석 결과").
Code Cleanup
feature/graph/src/main/java/com/example/metasearch/feature/graph/GraphPresenter.kt
Removed commented-out hardcoded URL assignment.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as PhotoDetail UI
    participant Presenter as PhotoDetailPresenter
    participant Repo as ImageAnalysisRepository
    participant OpenAI as OpenAI Service
    participant DB as Local Database

    User->>UI: Click Create Image Description
    UI->>Presenter: OnCreateImageDescriptionButtonClick(uri)
    Presenter->>Presenter: Toggle Loading = true
    
    par Parallel Operations
        Presenter->>Repo: Retrieve file name
        Presenter->>DB: Fetch database name
    end
    
    Repo->>Repo: Build prompt with triple data
    Repo->>OpenAI: Send chat completion request
    OpenAI-->>Repo: Return AI-generated description
    Repo-->>Presenter: Result<String>
    
    alt Success
        Presenter->>Presenter: Update imageDescription state
        Presenter->>UI: Render ModalBottomSheet with description
    else Error
        Presenter->>Presenter: Handle exception
        Presenter->>UI: Show toast message
    end
    
    Presenter->>Presenter: Toggle Loading = false
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Poem

🐰 With whiskers twitching, I hop with glee,
AI now sees images, clear as can be!
Toast pop-ups flutter, descriptions unfold,
In bottom sheets pink, image stories are told! 🎨✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'AI 기반 이미지 설명 기능을 추가한다' (Add AI-based image description feature) directly and accurately describes the main objective of the pull request, which is to introduce image analysis capabilities for generating AI-powered image descriptions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt:
- Around line 45-49: LaunchedEffect currently only opens the sheet when
state.imageDescription is non-null but never closes it; update the
LaunchedEffect watching state.imageDescription to set isSheetOpen =
(state.imageDescription != null) so the sheet closes when imageDescription
becomes null (i.e., replace the conditional block with a single assignment
inside LaunchedEffect that sets isSheetOpen based on state.imageDescription).
🧹 Nitpick comments (5)
core/data/api/src/main/java/com/example/metasearch/core/data/api/repository/ImageAnalysisRepository.kt (1)

10-10: Consider return type semantics.

Result<String?> allows three states: success with value, success with null, and failure. If null represents "no description available" as a valid outcome, this is appropriate. Otherwise, consider Result<String> with a descriptive error for the no-description case to simplify consumer logic.

feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/component/ImageDescriptionBottomSheetContent.kt (1)

23-25: Consider adding a text style to the title for consistency.

The description Text uses MetaSearchTheme.typography.bodyLarge, but the title Text has no style specified, defaulting to Material3's base style. For visual consistency, consider applying an appropriate typography style from MetaSearchTheme.

Suggested fix
         Text(
             text = stringResource(R.string.photo_detail_screen_openai_bottom_sheet_title),
+            style = MetaSearchTheme.typography.titleMedium,
         )
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt (1)

86-95: Guard against empty description when sheet is open.

While the current flow should prevent this scenario, if isSheetOpen becomes true but imageDescription is null (due to race conditions or future changes), the component will render with an empty string. Consider adding a defensive check.

Suggested fix
-        if (isSheetOpen) {
+        if (isSheetOpen && state.imageDescription != null) {
             ModalBottomSheet(
                 onDismissRequest = { isSheetOpen = false },
                 sheetState = sheetState,
                 containerColor = LightPink,
                 dragHandle = { BottomSheetDefaults.DragHandle() },
             ) {
-                ImageDescriptionBottomSheetContent(state.imageDescription ?: "")
+                ImageDescriptionBottomSheetContent(state.imageDescription)
             }
         }
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt (1)

115-120: Consider extracting the model name to a constant.

The model "gpt-3.5-turbo" is hardcoded. Consider moving it to PromptConstants or a dedicated configuration for easier maintenance and potential future model upgrades.

Suggested fix
+            // In PromptConstants.kt, add:
+            // const val OPENAI_MODEL = "gpt-3.5-turbo"

             val imageDescriptionResponse = openAIService.createChatCompletion(
                 OpenAIRequest(
-                    model = "gpt-3.5-turbo",
+                    model = PromptConstants.OPENAI_MODEL,
                     messages = listOf(OpenAIMessage(role = "user", content = fullPrompt)),
                 ),
             )
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailPresenter.kt (1)

53-57: Null check is redundant.

The ImageAnalysisRepositoryImpl.getImageDescription returns Result<String> (non-nullable). On success, description is guaranteed to be non-null, making the if (description != null) check unnecessary.

Suggested fix
                             .onSuccess { description ->
-                                if (description != null) {
-                                    imageDescription = description
-                                }
+                                imageDescription = description
                             }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae08f26 and 886638d.

📒 Files selected for processing (11)
  • core/common/src/main/java/com/example/metasearch/core/common/constants/PromptConstants.kt
  • core/data/api/src/main/java/com/example/metasearch/core/data/api/repository/ImageAnalysisRepository.kt
  • core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt
  • core/network/src/main/java/com/example/metasearch/core/network/service/WebService.kt
  • feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailPresenter.kt
  • feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailToastEffect.kt
  • feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt
  • feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUiState.kt
  • feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/component/ImageDescriptionBottomSheetContent.kt
  • feature/detail/src/main/res/values/strings.xml
  • feature/graph/src/main/java/com/example/metasearch/feature/graph/GraphPresenter.kt
💤 Files with no reviewable changes (1)
  • feature/graph/src/main/java/com/example/metasearch/feature/graph/GraphPresenter.kt
🧰 Additional context used
🧬 Code graph analysis (3)
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt (4)
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailToastEffect.kt (1)
  • PhotoDetailToastEffect (7-19)
core/ui/src/main/java/com/example/metasearch/core/ui/component/MetaSearchLoadingIndicator.kt (1)
  • MetaSearchLoadingIndicator (16-31)
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/component/ImageDescriptionBottomSheetContent.kt (1)
  • ImageDescriptionBottomSheetContent (15-32)
core/designsystem/src/main/java/com/example/metasearch/core/designsystem/component/MetaSearchToast.kt (1)
  • MetaSearchToast (22-57)
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt (1)
core/common/src/main/java/com/example/metasearch/core/common/utils/RunCatching.kt (1)
  • runSuspendCatching (14-28)
feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailPresenter.kt (1)
core/common/src/main/java/com/example/metasearch/core/common/utils/Exception.kt (1)
  • handleException (10-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (10)
feature/detail/src/main/res/values/strings.xml (1)

29-29: LGTM!

The new string resource follows the existing naming convention and appropriately supports the AI image analysis feature.

feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailToastEffect.kt (1)

7-19: LGTM!

The toast effect implementation correctly uses LaunchedEffect keyed on toastMessage, ensuring each new message gets a fresh 1.5-second timer and prior effects are cancelled on message change.

core/common/src/main/java/com/example/metasearch/core/common/constants/PromptConstants.kt (1)

38-48: LGTM!

The new prompt constant is well-structured with clear instructions for the AI to generate natural, concise image descriptions in a polite Korean tone.

feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUiState.kt (2)

6-12: LGTM!

The new state properties toastMessage and imageDescription are well-designed with appropriate nullable types and default values. The state structure follows Circuit UI patterns correctly.


46-50: LGTM!

The ShowToast and HideToast events are cleanly defined and integrate well with the existing event pattern.

feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt (1)

97-100: LGTM!

The toast integration with MetaSearchToast and visibility driven by state.toastMessage is correctly implemented.

core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt (2)

103-125: LGTM on the overall structure.

The implementation correctly uses runSuspendCatching for error handling and coroutineScope with async for parallel retrieval of filename and database name. The error handling properly propagates failures.


113-123: Verify timeout configuration for external API calls.

The webService.fetchTripleData and openAIService.createChatCompletion calls have no explicit timeout. If the OkHttp/Retrofit client doesn't have appropriate timeouts configured, these calls could hang indefinitely, blocking the coroutine.

feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailPresenter.kt (2)

48-69: LGTM on the image description fetch flow.

The coroutine-based async pattern is well-implemented with proper loading state management. The error handling via handleException correctly routes error messages to the toast.


89-89: ShowToast event appears unused.

The ShowToast event handler is implemented, but the current flow sets toastMessage directly via the handleException callback. Either remove this event if not needed, or use it consistently for showing toast messages.

#!/bin/bash
# Check if ShowToast event is used anywhere in the codebase
rg -n "ShowToast" --type=kotlin

Comment on lines +45 to +49
LaunchedEffect(state.imageDescription) {
if (state.imageDescription != null) {
isSheetOpen = true
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sheet doesn't close when imageDescription becomes null.

The LaunchedEffect only opens the sheet when imageDescription is non-null, but never closes it. If imageDescription is reset to null (e.g., state reset, error recovery), the sheet remains open with stale or empty content.

Suggested fix
     LaunchedEffect(state.imageDescription) {
-        if (state.imageDescription != null) {
-            isSheetOpen = true
-        }
+        isSheetOpen = state.imageDescription != null
     }
📝 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
LaunchedEffect(state.imageDescription) {
if (state.imageDescription != null) {
isSheetOpen = true
}
}
LaunchedEffect(state.imageDescription) {
isSheetOpen = state.imageDescription != null
}
🤖 Prompt for AI Agents
In
@feature/detail/src/main/java/com/example/metasearch/feature/detail/photo/PhotoDetailUi.kt
around lines 45 - 49, LaunchedEffect currently only opens the sheet when
state.imageDescription is non-null but never closes it; update the
LaunchedEffect watching state.imageDescription to set isSheetOpen =
(state.imageDescription != null) so the sheet closes when imageDescription
becomes null (i.e., replace the conditional block with a single assignment
inside LaunchedEffect that sets isSheetOpen based on state.imageDescription).

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

🤖 Android CI Summary

Step Results:

  • Unit Test: ✅ Success (3m 10s)
  • Debug Build: ✅ Success (1m 8s)
  • Code Style Check: ✅ Success (3m 43s)

Total Time: 8m 1s

🎉 All steps completed successfully!

@komodgn komodgn merged commit 32c7c04 into develop Jan 12, 2026
2 checks passed
@komodgn komodgn deleted the feature/#72 branch January 12, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant