523 lines
26 KiB
Kotlin
Raw Normal View History

2025-10-13 16:18:55 +09:00
// ui/tabs/tabs.kt
2025-10-13 13:26:39 +09:00
package ui.tabs
2025-10-13 16:54:39 +09:00
import androidx.compose.foundation.HorizontalScrollbar
2025-10-13 13:26:39 +09:00
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
2025-10-13 16:18:55 +09:00
import androidx.compose.foundation.lazy.itemsIndexed
2025-10-13 16:54:39 +09:00
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
2025-10-13 13:26:39 +09:00
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
2025-10-13 16:18:55 +09:00
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
2025-10-13 13:26:39 +09:00
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil3.ImageLoader
import coil3.compose.rememberAsyncImagePainter
import models.SearchResult
2025-10-13 16:18:55 +09:00
import utils.Strings
2025-10-13 13:26:39 +09:00
import java.io.File
@Composable
fun ScrapBasedPostTab(
isLoading: Boolean, keywords: List<String>, searchResults: List<SearchResult>, scrapedFiles: List<File>, selectedFiles: Set<File>,
2025-10-13 16:18:55 +09:00
viewedFileContent: String,
combinedImagesFromSelectedFiles: List<String>,
currentSelectedImages: Set<String>,
2025-10-13 13:26:39 +09:00
userPrompt: String, imageLoader: ImageLoader, manualKeyword: String, userScrapComment: String, userMainTopic: String,
onManualKeywordChange: (String) -> Unit, onUserPromptChange: (String) -> Unit, onUserScrapCommentChange: (String) -> Unit, onUserMainTopicChange: (String) -> Unit,
onFetchTrends: () -> Unit, onKeywordSelect: (String) -> Unit, onSearchResultSelect: (SearchResult) -> Unit,
onRefreshFiles: () -> Unit, onFileSelectToggle: (File, Boolean) -> Unit, onFileView: (File) -> Unit,
onImageSelect: (String) -> Unit, onSaveChanges: () -> Unit, onGeneratePost: () -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
Row(modifier = Modifier.fillMaxSize()) {
// 1. 키워드 및 검색
Column(modifier = Modifier.weight(1.5f).border(1.dp, Color.LightGray).padding(4.dp)) {
Row(modifier = Modifier.fillMaxWidth()) {
2025-10-13 16:18:55 +09:00
OutlinedTextField(value = manualKeyword, onValueChange = onManualKeywordChange, label = { Text(Strings.LABEL_KEYWORD_INPUT) }, modifier = Modifier.weight(1f), singleLine = true, enabled = !isLoading)
2025-10-13 13:26:39 +09:00
Spacer(Modifier.width(4.dp))
2025-10-13 16:18:55 +09:00
Button(onClick = { onKeywordSelect(manualKeyword) }, enabled = !isLoading && manualKeyword.isNotBlank()) { Text(Strings.BUTTON_SEARCH) }
2025-10-13 13:26:39 +09:00
}
Spacer(Modifier.height(8.dp))
2025-10-13 16:18:55 +09:00
Button(onClick = onFetchTrends, modifier = Modifier.fillMaxWidth(), enabled = !isLoading) { Text(Strings.BUTTON_FETCH_TRENDS) }
2025-10-13 13:26:39 +09:00
Divider(modifier = Modifier.padding(vertical = 8.dp))
LazyColumn(modifier = Modifier.weight(1f)) {
items(keywords) { keyword -> Text(keyword, modifier = Modifier.fillMaxWidth().clickable { if (!isLoading) onKeywordSelect(keyword) }.padding(8.dp)) }
}
}
// 2. 검색 결과 및 스크랩
Column(modifier = Modifier.weight(2f).border(1.dp, Color.LightGray).padding(4.dp)) {
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_SEARCH_RESULTS, style = MaterialTheme.typography.h6, modifier = Modifier.padding(4.dp))
2025-10-13 13:26:39 +09:00
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(searchResults) { result ->
Column(modifier = Modifier.fillMaxWidth().clickable { if (!isLoading) onSearchResultSelect(result) }.padding(8.dp)) {
Text(result.title, style = MaterialTheme.typography.subtitle1, color = MaterialTheme.colors.primary)
Text(result.url, style = MaterialTheme.typography.caption, maxLines = 1)
}
}
}
}
// 3. 파일 관리 및 생성
Column(modifier = Modifier.weight(3f).padding(horizontal = 4.dp).verticalScroll(rememberScrollState())) {
// 저장된 파일
Column(modifier = Modifier.fillMaxWidth().border(1.dp, Color.LightGray).padding(4.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_SAVED_FILES, style = MaterialTheme.typography.h6, modifier = Modifier.weight(1f).padding(4.dp))
Button(onClick = onRefreshFiles, enabled = !isLoading) { Text(Strings.BUTTON_REFRESH) }
2025-10-13 13:26:39 +09:00
}
Box(modifier = Modifier.heightIn(max = 200.dp)) {
LazyColumn {
items(scrapedFiles) { file ->
Row(modifier = Modifier.fillMaxWidth().clickable { if (!isLoading) onFileView(file) }.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = file in selectedFiles, onCheckedChange = { isChecked -> onFileSelectToggle(file, isChecked) }, enabled = !isLoading)
Text(file.name, modifier = Modifier.padding(start = 4.dp), maxLines = 1)
}
}
}
}
Spacer(Modifier.height(8.dp))
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_FILE_CONTENT, style = MaterialTheme.typography.subtitle1)
2025-10-13 13:26:39 +09:00
Text(text = viewedFileContent, modifier = Modifier.height(100.dp).fillMaxWidth().border(1.dp, Color.LightGray).padding(4.dp).verticalScroll(rememberScrollState()), style = MaterialTheme.typography.body2)
Spacer(Modifier.height(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_SELECT_IMAGES, style = MaterialTheme.typography.subtitle1, modifier = Modifier.weight(1f))
Button(onClick = onSaveChanges, enabled = !isLoading && combinedImagesFromSelectedFiles.isNotEmpty()) { Text(Strings.BUTTON_SAVE_IMAGE_SELECTION) }
2025-10-13 13:26:39 +09:00
}
2025-10-13 16:54:39 +09:00
// ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가
val imageListState = rememberLazyListState()
Box(modifier = Modifier.fillMaxWidth().height(120.dp).border(1.dp, Color.LightGray)) {
LazyRow(
modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), // 스크롤바 공간 확보
state = imageListState
) {
items(combinedImagesFromSelectedFiles) { imageUrl ->
val isSelected = imageUrl in currentSelectedImages
Box(modifier = Modifier.padding(4.dp)) {
Image(painter = rememberAsyncImagePainter(model = imageUrl, imageLoader = imageLoader), contentDescription = "Scraped Image", modifier = Modifier.size(100.dp).clickable { onImageSelect(imageUrl) }.border(if (isSelected) 4.dp else 0.dp, MaterialTheme.colors.primary), contentScale = ContentScale.Crop)
}
2025-10-13 13:26:39 +09:00
}
}
2025-10-13 16:54:39 +09:00
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
adapter = rememberScrollbarAdapter(imageListState)
)
2025-10-13 13:26:39 +09:00
}
}
Spacer(Modifier.height(16.dp))
// 글 생성 제어
Column(modifier = Modifier.fillMaxWidth().border(1.dp, Color.LightGray).padding(8.dp)) {
2025-10-13 16:18:55 +09:00
Text(Strings.LABEL_LLM_REQUEST, style = MaterialTheme.typography.h6)
OutlinedTextField(value = userPrompt, onValueChange = onUserPromptChange, modifier = Modifier.fillMaxWidth().height(100.dp), label = { Text(Strings.LABEL_USER_PROMPT) })
2025-10-13 13:26:39 +09:00
Spacer(Modifier.height(8.dp))
OutlinedTextField(
value = userMainTopic,
onValueChange = onUserMainTopicChange,
modifier = Modifier.fillMaxWidth(),
2025-10-13 16:18:55 +09:00
label = { Text(Strings.LABEL_MAIN_TOPIC) },
2025-10-13 13:26:39 +09:00
singleLine = true
)
Spacer(Modifier.height(8.dp))
2025-10-13 16:18:55 +09:00
OutlinedTextField(value = userScrapComment, onValueChange = onUserScrapCommentChange, modifier = Modifier.fillMaxWidth().height(100.dp), label = { Text(Strings.LABEL_USER_COMMENT) })
2025-10-13 13:26:39 +09:00
Spacer(Modifier.height(8.dp))
Button(onClick = onGeneratePost, enabled = !isLoading && selectedFiles.isNotEmpty(), modifier = Modifier.fillMaxWidth()) {
2025-10-13 16:18:55 +09:00
Text(Strings.buttonGeneratePost(selectedFiles.size))
2025-10-13 13:26:39 +09:00
}
}
}
}
if (isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) }
}
}
@Composable
fun DirectPostTab(
isLoading: Boolean,
userOwnContent: String,
onUserOwnContentChange: (String) -> Unit,
uploadedImageFiles: List<File>,
imageLoader: ImageLoader,
userPrompt: String,
onUserPromptChange: (String) -> Unit,
onUploadImage: () -> Unit,
onRemoveUploadedImage: (File) -> Unit,
onGeneratePost: () -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
Row(modifier = Modifier.fillMaxSize().padding(8.dp)) {
// 1. 내용 작성
Column(modifier = Modifier.weight(2f).padding(end = 8.dp)) {
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_DIRECT_POST, style = MaterialTheme.typography.h6, modifier = Modifier.padding(4.dp))
2025-10-13 13:26:39 +09:00
OutlinedTextField(
value = userOwnContent,
onValueChange = onUserOwnContentChange,
modifier = Modifier.fillMaxSize(),
2025-10-13 16:18:55 +09:00
label = { Text(Strings.LABEL_DIRECT_POST_CONTENT) }
2025-10-13 13:26:39 +09:00
)
}
// 2. 이미지 및 생성 제어
Column(modifier = Modifier.weight(1f).border(1.dp, Color.LightGray).padding(8.dp)) {
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_IMAGE_UPLOAD, style = MaterialTheme.typography.h6)
Button(onClick = onUploadImage, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_IMAGE) }
2025-10-13 13:26:39 +09:00
Spacer(Modifier.height(8.dp))
2025-10-13 16:54:39 +09:00
// ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가
val imageListState = rememberLazyListState()
Box(modifier = Modifier.fillMaxWidth().height(120.dp)) {
LazyRow(
modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp),
state = imageListState
) {
items(uploadedImageFiles) { file ->
Box(modifier = Modifier.padding(4.dp)) {
Image(
painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader),
contentDescription = "Uploaded Image",
modifier = Modifier.size(100.dp).clickable { onRemoveUploadedImage(file) },
contentScale = ContentScale.Crop
)
}
2025-10-13 13:26:39 +09:00
}
}
2025-10-13 16:54:39 +09:00
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
adapter = rememberScrollbarAdapter(imageListState)
)
2025-10-13 13:26:39 +09:00
}
Spacer(Modifier.height(16.dp))
2025-10-13 16:18:55 +09:00
Text(Strings.LABEL_LLM_REQUEST, style = MaterialTheme.typography.h6)
2025-10-13 13:26:39 +09:00
OutlinedTextField(
value = userPrompt,
onValueChange = onUserPromptChange,
modifier = Modifier.fillMaxWidth().height(150.dp),
2025-10-13 16:18:55 +09:00
label = { Text(Strings.LABEL_USER_PROMPT) }
2025-10-13 13:26:39 +09:00
)
Spacer(Modifier.height(8.dp))
Button(
onClick = onGeneratePost,
enabled = !isLoading && userOwnContent.isNotBlank() && uploadedImageFiles.isNotEmpty(),
modifier = Modifier.fillMaxWidth()
2025-10-13 16:18:55 +09:00
) { Text(Strings.BUTTON_GENERATE_POST_DIRECT) }
2025-10-13 13:26:39 +09:00
}
}
if (isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) }
}
}
@Composable
fun ReceiptAnalyzerTab(
isLoading: Boolean,
receiptFiles: List<File>,
receiptAnalysisResult: String,
isAnalyzing: Boolean,
receiptContextPrompt: String,
imageLoader: ImageLoader,
onUploadReceipt: () -> Unit,
onRemoveReceipt: (File) -> Unit,
onReceiptContextPromptChange: (String) -> Unit,
onAnalyzeReceipts: () -> Unit,
onCancelAnalysis: () -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
2025-10-13 16:18:55 +09:00
Text(Strings.TITLE_RECEIPT_ANALYZER, style = MaterialTheme.typography.h5, modifier = Modifier.padding(bottom = 8.dp))
Button(onClick = onUploadReceipt, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_RECEIPT) }
2025-10-13 13:26:39 +09:00
Spacer(Modifier.height(8.dp))
2025-10-13 16:54:39 +09:00
// ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가
val imageListState = rememberLazyListState()
Box(modifier = Modifier.fillMaxWidth().height(120.dp)) {
LazyRow(
modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp),
state = imageListState
) {
items(receiptFiles) { file ->
Box(modifier = Modifier.padding(4.dp)) {
Image(
painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader),
contentDescription = "Receipt Image",
modifier = Modifier.size(100.dp).clickable { onRemoveReceipt(file) },
contentScale = ContentScale.Crop
)
}
2025-10-13 13:26:39 +09:00
}
}
2025-10-13 16:54:39 +09:00
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
adapter = rememberScrollbarAdapter(imageListState)
)
2025-10-13 13:26:39 +09:00
}
Spacer(Modifier.height(16.dp))
OutlinedTextField(
value = receiptContextPrompt,
onValueChange = onReceiptContextPromptChange,
modifier = Modifier.fillMaxWidth(),
2025-10-13 16:18:55 +09:00
placeholder = { Text(Strings.PLACEHOLDER_RECEIPT_CONTEXT) },
label = { Text(Strings.LABEL_RECEIPT_CONTEXT) }
2025-10-13 13:26:39 +09:00
)
Spacer(Modifier.height(16.dp))
if (isAnalyzing) {
Button(onClick = onCancelAnalysis, modifier = Modifier.fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.error)) {
Row(verticalAlignment = Alignment.CenterVertically) {
CircularProgressIndicator(modifier = Modifier.size(20.dp), color = Color.White, strokeWidth = 2.dp)
Spacer(Modifier.width(8.dp))
2025-10-13 16:18:55 +09:00
Text(Strings.BUTTON_CANCEL_ANALYSIS)
2025-10-13 13:26:39 +09:00
}
}
} else {
Button(onClick = onAnalyzeReceipts, enabled = !isLoading && receiptFiles.isNotEmpty(), modifier = Modifier.fillMaxWidth()) {
2025-10-13 16:18:55 +09:00
Text(Strings.buttonStartAnalysis(receiptFiles.size))
2025-10-13 13:26:39 +09:00
}
}
Spacer(Modifier.height(16.dp))
OutlinedTextField(
value = receiptAnalysisResult,
onValueChange = {},
readOnly = true,
modifier = Modifier.fillMaxSize(),
2025-10-13 16:18:55 +09:00
label = { Text(Strings.LABEL_ANALYSIS_RESULT) }
2025-10-13 13:26:39 +09:00
)
}
if (isLoading && !isAnalyzing) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) }
}
}
@Composable
fun LogTab(logs: List<String>) {
TextField(value = logs.joinToString("\n"), onValueChange = {}, readOnly = true, modifier = Modifier.fillMaxSize().padding(8.dp))
}
@Composable
fun ResultTab(
result: String,
onRequestResultChange: (String) -> Unit,
revisionRequest: String,
onRevisionRequestChange: (String) -> Unit,
isLoading: Boolean,
onRevise: () -> Unit,
onCopyToClipboard: () -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
OutlinedTextField(
value = result,
onValueChange = onRequestResultChange,
modifier = Modifier.weight(1f).fillMaxWidth(),
2025-10-13 16:18:55 +09:00
label = { Text(Strings.LABEL_BLOG_RESULT) }
2025-10-13 13:26:39 +09:00
)
Spacer(Modifier.height(16.dp))
OutlinedTextField(
value = revisionRequest,
onValueChange = onRevisionRequestChange,
modifier = Modifier.fillMaxWidth().height(100.dp),
2025-10-13 16:18:55 +09:00
label = { Text(Strings.LABEL_REVISION_REQUEST) },
placeholder = { Text(Strings.PLACEHOLDER_REVISION_REQUEST) },
2025-10-13 13:26:39 +09:00
enabled = !isLoading
)
Spacer(Modifier.height(8.dp))
Row(modifier = Modifier.fillMaxWidth()) {
Button(
onClick = onRevise,
enabled = !isLoading && revisionRequest.isNotBlank(),
modifier = Modifier.weight(1f)
) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.size(20.dp), color = MaterialTheme.colors.onPrimary, strokeWidth = 2.dp)
} else {
2025-10-13 16:18:55 +09:00
Text(Strings.BUTTON_REVISE_POST)
2025-10-13 13:26:39 +09:00
}
}
Spacer(Modifier.width(8.dp))
Button(
onClick = onCopyToClipboard,
enabled = !isLoading && result.isNotBlank(),
modifier = Modifier.weight(1f)
) {
2025-10-13 16:18:55 +09:00
Text(Strings.BUTTON_COPY_TO_CLIPBOARD)
2025-10-13 13:26:39 +09:00
}
}
}
}
}
2025-10-13 16:18:55 +09:00
@Composable
fun SettingsTab(
generatePromptPrefix: String,
onGeneratePromptPrefixChange: (String) -> Unit,
generatePromptInstructions: List<String>,
onGeneratePromptInstructionsChange: (List<String>) -> Unit,
revisePrompt: String,
onRevisePromptChange: (String) -> Unit,
receiptPrompt: String,
onReceiptPromptChange: (String) -> Unit,
articleSelectors: List<String>,
onArticleSelectorsChange: (List<String>) -> Unit,
onSave: () -> Unit,
onReset: () -> Unit,
isLoading: Boolean
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text("프롬프트 및 설정", style = MaterialTheme.typography.h5, modifier = Modifier.padding(bottom = 16.dp))
// --- 블로그 글 생성 프롬프트 섹션 ---
Text("블로그 글 생성 프롬프트", style = MaterialTheme.typography.h6)
Spacer(Modifier.height(8.dp))
OutlinedTextField(
value = generatePromptPrefix,
onValueChange = onGeneratePromptPrefixChange,
modifier = Modifier.fillMaxWidth().height(150.dp),
label = { Text("기본 역할 (Prefix)") },
enabled = !isLoading
)
Spacer(Modifier.height(8.dp))
Text("요청사항 목록", style = MaterialTheme.typography.subtitle1)
// LazyColumn은 Column 내에서 높이가 지정되어야 하므로 Box로 감싸서 제한
Box(modifier = Modifier.heightIn(max = 250.dp)) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
itemsIndexed(generatePromptInstructions) { index, instruction ->
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedTextField(
value = instruction,
onValueChange = { newText ->
val newList = generatePromptInstructions.toMutableList()
newList[index] = newText
onGeneratePromptInstructionsChange(newList)
},
modifier = Modifier.weight(1f),
singleLine = true,
enabled = !isLoading
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = {
val newList = generatePromptInstructions.toMutableList()
newList.removeAt(index)
onGeneratePromptInstructionsChange(newList)
}, enabled = !isLoading) {
Icon(Icons.Default.Delete, contentDescription = "삭제")
}
}
}
}
}
Button(
onClick = { onGeneratePromptInstructionsChange(generatePromptInstructions + "") },
enabled = !isLoading
) {
Text("요청사항 항목 추가")
}
Divider(modifier = Modifier.padding(vertical = 16.dp))
// ⭐️ [추가] 스크래핑 CSS 셀렉터 설정 UI
Text("아티클 스크래핑 CSS 셀렉터", style = MaterialTheme.typography.h6)
Text(
"스크랩 시 본문을 찾기 위해 사용되는 CSS 셀렉터 목록입니다. 우선순위가 높은 것을 위로 배치하세요.",
style = MaterialTheme.typography.caption,
modifier = Modifier.padding(bottom = 8.dp)
)
Box(modifier = Modifier.heightIn(max = 250.dp)) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
itemsIndexed(articleSelectors) { index, selector ->
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedTextField(
value = selector,
onValueChange = { newText ->
val newList = articleSelectors.toMutableList()
newList[index] = newText
onArticleSelectorsChange(newList)
},
modifier = Modifier.weight(1f),
singleLine = true,
enabled = !isLoading
)
Spacer(Modifier.width(8.dp))
IconButton(onClick = {
val newList = articleSelectors.toMutableList()
newList.removeAt(index)
onArticleSelectorsChange(newList)
}, enabled = !isLoading) {
Icon(Icons.Default.Delete, contentDescription = "셀렉터 삭제")
}
}
}
}
}
Button(
onClick = { onArticleSelectorsChange(articleSelectors + "") },
enabled = !isLoading
) {
Text("셀렉터 항목 추가")
}
Divider(modifier = Modifier.padding(vertical = 16.dp))
// --- 글 수정 및 영수증 분석 프롬프트 ---
OutlinedTextField(
value = revisePrompt,
onValueChange = onRevisePromptChange,
modifier = Modifier.fillMaxWidth().height(150.dp),
label = { Text("블로그 글 수정 프롬프트") },
enabled = !isLoading
)
Spacer(Modifier.height(16.dp))
OutlinedTextField(
value = receiptPrompt,
onValueChange = onReceiptPromptChange,
modifier = Modifier.fillMaxWidth().height(150.dp),
label = { Text("영수증 분석 프롬프트") },
enabled = !isLoading
)
Spacer(Modifier.height(24.dp))
// --- 저장 및 초기화 버튼 ---
Row(modifier = Modifier.fillMaxWidth()) {
Button(onClick = onSave, enabled = !isLoading, modifier = Modifier.weight(1f)) {
Text("설정 저장하기")
}
Spacer(Modifier.width(16.dp))
Button(
onClick = onReset,
enabled = !isLoading,
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.secondary)
) {
Text("기본값으로 초기화")
}
}
}
if (isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) }
}
}