// ui/tabs/ScrapBasedPostTab.kt package ui.tabs 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 import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll // (필요한 import 추가) import androidx.compose.material.* 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 io.ktor.client.request.url import models.SearchResult import java.io.File @Composable fun ScrapBasedPostTab( isLoading: Boolean, keywords: List, searchResults: List, scrapedFiles: List, selectedFiles: Set, viewedFileContent: String, imagesForSelection: List, currentSelectedImages: Set, 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()) { OutlinedTextField(value = manualKeyword, onValueChange = onManualKeywordChange, label = { Text("키워드 직접 입력") }, modifier = Modifier.weight(1f), singleLine = true, enabled = !isLoading) Spacer(Modifier.width(4.dp)) Button(onClick = { onKeywordSelect(manualKeyword) }, enabled = !isLoading && manualKeyword.isNotBlank()) { Text("검색") } } Spacer(Modifier.height(8.dp)) Button(onClick = onFetchTrends, modifier = Modifier.fillMaxWidth(), enabled = !isLoading) { Text("트렌드 가져오기") } 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)) { Text("검색 결과 (클릭하여 스크랩)", style = MaterialTheme.typography.h6, modifier = Modifier.padding(4.dp)) 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) { Text("저장된 파일", style = MaterialTheme.typography.h6, modifier = Modifier.weight(1f).padding(4.dp)) Button(onClick = onRefreshFiles, enabled = !isLoading) { Text("새로고침") } } 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)) Text("파일 내용", style = MaterialTheme.typography.subtitle1) 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) { Text("대표 이미지 선택 (다중 가능)", style = MaterialTheme.typography.subtitle1, modifier = Modifier.weight(1f)) Button(onClick = onSaveChanges, enabled = !isLoading && imagesForSelection.isNotEmpty()) { Text("선택 이미지 저장") } } LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp).border(1.dp, Color.LightGray)) { items(imagesForSelection) { 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) } } } } Spacer(Modifier.height(16.dp)) // 글 생성 제어 Column(modifier = Modifier.fillMaxWidth().border(1.dp, Color.LightGray).padding(8.dp)) { Text("LLM 요청사항", style = MaterialTheme.typography.h6) OutlinedTextField(value = userPrompt, onValueChange = onUserPromptChange, modifier = Modifier.fillMaxWidth().height(100.dp), label = { Text("글 스타일, 톤앤매너 등을 지시하세요.") }) Spacer(Modifier.height(8.dp)) OutlinedTextField( value = userMainTopic, onValueChange = onUserMainTopicChange, modifier = Modifier.fillMaxWidth(), label = { Text("글의 핵심 주제 (예: 2025년 최신 IT 트렌드)") }, singleLine = true ) Spacer(Modifier.height(8.dp)) OutlinedTextField(value = userScrapComment, onValueChange = onUserScrapCommentChange, modifier = Modifier.fillMaxWidth().height(100.dp), label = { Text("작성자 코멘트 (스크랩한 내용에 대한 당신의 생각)") }) Spacer(Modifier.height(8.dp)) Button(onClick = onGeneratePost, enabled = !isLoading && selectedFiles.isNotEmpty(), modifier = Modifier.fillMaxWidth()) { Text("선택한 파일(${selectedFiles.size}개)로 글 생성") } } } } if (isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } } @Composable fun DirectPostTab( isLoading: Boolean, userOwnContent: String, onUserOwnContentChange: (String) -> Unit, uploadedImageFiles: List, 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)) { Text("직접 작성 (여행 기록, 정보 공유 등)", style = MaterialTheme.typography.h6, modifier = Modifier.padding(4.dp)) OutlinedTextField( value = userOwnContent, onValueChange = onUserOwnContentChange, modifier = Modifier.fillMaxSize(), label = { Text("블로그에 올릴 내용을 직접 작성하세요.") } ) } // 2. 이미지 및 생성 제어 Column(modifier = Modifier.weight(1f).border(1.dp, Color.LightGray).padding(8.dp)) { Text("이미지 업로드", style = MaterialTheme.typography.h6) Button(onClick = onUploadImage, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text("내 PC에서 이미지 업로드") } Spacer(Modifier.height(8.dp)) LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp)) { 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 ) } } } Spacer(Modifier.height(16.dp)) Text("LLM 요청사항", style = MaterialTheme.typography.h6) OutlinedTextField( value = userPrompt, onValueChange = onUserPromptChange, modifier = Modifier.fillMaxWidth().height(150.dp), label = { Text("글 스타일, 톤앤매너 등을 지시하세요.") } ) Spacer(Modifier.height(8.dp)) Button( onClick = onGeneratePost, enabled = !isLoading && userOwnContent.isNotBlank() && uploadedImageFiles.isNotEmpty(), modifier = Modifier.fillMaxWidth() ) { Text("작성한 내용으로 글 생성") } } } if (isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } } @Composable fun ReceiptAnalyzerTab( isLoading: Boolean, receiptFiles: List, 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)) { Text("영수증 분석기", style = MaterialTheme.typography.h5, modifier = Modifier.padding(bottom = 8.dp)) Button(onClick = onUploadReceipt, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text("영수증 이미지 업로드") } Spacer(Modifier.height(8.dp)) LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp)) { 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 ) } } } Spacer(Modifier.height(16.dp)) OutlinedTextField( value = receiptContextPrompt, onValueChange = onReceiptContextPromptChange, modifier = Modifier.fillMaxWidth(), placeholder = { Text("추가 정보를 입력하면 더 정확하게 분석할 수 있습니다.") }, label = { Text("추가 정보 입력 (예: 부산 출장 경비)") } ) 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)) Text("분석 중단하기") } } } else { Button(onClick = onAnalyzeReceipts, enabled = !isLoading && receiptFiles.isNotEmpty(), modifier = Modifier.fillMaxWidth()) { Text("선택한 영수증 분석 시작 (${receiptFiles.size}개)") } } Spacer(Modifier.height(16.dp)) OutlinedTextField( value = receiptAnalysisResult, onValueChange = {}, readOnly = true, modifier = Modifier.fillMaxSize(), label = { Text("분석 결과 (내용 복사하여 사용)") } ) } if (isLoading && !isAnalyzing) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } } @Composable fun LogTab(logs: List) { 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(), label = { Text("블로그 글 결과 (LLM 생성 본문)") } ) Spacer(Modifier.height(16.dp)) OutlinedTextField( value = revisionRequest, onValueChange = onRevisionRequestChange, modifier = Modifier.fillMaxWidth().height(100.dp), label = { Text("추가 요청사항") }, placeholder = { Text("예: 문체를 좀 더 전문적으로 바꿔줘. 1번 항목을 더 자세히 설명해줘.") }, 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 { Text("LLM으로 글 보완하기") } } Spacer(Modifier.width(8.dp)) Button( onClick = onCopyToClipboard, enabled = !isLoading && result.isNotBlank(), modifier = Modifier.weight(1f) ) { Text("전체 내용 클립보드에 복사") } } } } }