This commit is contained in:
lunaticbum 2025-10-13 16:54:39 +09:00
parent 92aa1a998e
commit 161c00c7b9
3 changed files with 70 additions and 29 deletions

View File

@ -49,25 +49,27 @@ object ScrapingService {
return results
}
// ⭐️ [수정] 함수 시그니처에 selectors 파라미터 추가
suspend fun scrapeArticleByUrl(url: String, selectors: List<String>, logs: MutableList<String>, isBrowserVisible: Boolean, keepSession: Boolean): ScrapedData? {
logMessage(logs, Strings.logScrapeUrlStart(url))
val options = ChromeOptions().apply { if (!isBrowserVisible) addArguments("--headless=new"); addArguments("--disable-gpu") }
val currentDriver = BrowserManager.getChromeDriver(options)
return try {
currentDriver.get(url)
Thread.sleep(2000)
Thread.sleep(4000)
val doc = Jsoup.parse(currentDriver.pageSource)
// ⭐️ [수정] 인자로 받은 selectors 리스트를 쉼표로 연결하여 Jsoup 쿼리로 사용하고, 첫 번째 요소를 찾습니다.
val articleElement = doc.select(selectors.joinToString(", "))?.firstOrNull()
// ⭐️ [수정] 1. 셀렉터와 일치하는 '모든' 요소를 찾습니다.
val candidateElements = doc.select(selectors.joinToString(", "))
// ⭐️ [수정] 2. 찾은 요소들 중에서 텍스트 길이가 100자 이상인 첫 번째 요소를 본문으로 선택합니다.
val articleElement = candidateElements.find { it.text().length >= 100 }
if (articleElement == null) {
logMessage(logs, Strings.LOG_WARN_ARTICLE_BODY_NOT_FOUND)
return null
}
// 찾은 요소에서 텍스트와 이미지를 각각 추출합니다.
// 3. 찾은 요소에서 텍스트와 이미지를 각각 추출합니다.
val articleContent = articleElement.text()
val allImages = articleElement.select("img")
.map { it.absUrl("src") }

View File

@ -1,6 +1,7 @@
// ui/tabs/tabs.kt
package ui.tabs
import androidx.compose.foundation.HorizontalScrollbar
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
@ -9,6 +10,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
@ -93,13 +96,24 @@ fun ScrapBasedPostTab(
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) }
}
LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp).border(1.dp, Color.LightGray)) {
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)
// ⭐️ [수정] 스크롤바를 표시하기 위해 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)
}
}
}
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
adapter = rememberScrollbarAdapter(imageListState)
)
}
}
Spacer(Modifier.height(16.dp))
@ -163,17 +177,29 @@ fun DirectPostTab(
Text(Strings.TITLE_IMAGE_UPLOAD, style = MaterialTheme.typography.h6)
Button(onClick = onUploadImage, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_IMAGE) }
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
)
// ⭐️ [수정] 스크롤바를 표시하기 위해 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
)
}
}
}
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
adapter = rememberScrollbarAdapter(imageListState)
)
}
Spacer(Modifier.height(16.dp))
@ -216,17 +242,29 @@ fun ReceiptAnalyzerTab(
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) }
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
)
// ⭐️ [수정] 스크롤바를 표시하기 위해 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
)
}
}
}
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
adapter = rememberScrollbarAdapter(imageListState)
)
}
Spacer(Modifier.height(16.dp))
OutlinedTextField(
@ -322,7 +360,6 @@ fun ResultTab(
}
}
// ⭐️ [수정] SettingsTab 함수 시그니처 변경
@Composable
fun SettingsTab(
generatePromptPrefix: String,

View File

@ -110,6 +110,8 @@ object Strings {
const val DEFAULT_USER_OWN_CONTENT = "예시: 강릉으로 1박 2일 여행을 다녀왔습니다."
// ⭐️ [추가] 스크래핑 셀렉터 기본값
val DEFAULT_ARTICLE_SELECTORS = listOf(
"main",
"#post-area",
"#postListBody",
"#app",
".app",