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

View File

@ -1,6 +1,7 @@
// ui/tabs/tabs.kt // ui/tabs/tabs.kt
package ui.tabs package ui.tabs
import androidx.compose.foundation.HorizontalScrollbar
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable 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.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed 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.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.* import androidx.compose.material.*
@ -93,13 +96,24 @@ fun ScrapBasedPostTab(
Text(Strings.TITLE_SELECT_IMAGES, style = MaterialTheme.typography.subtitle1, modifier = Modifier.weight(1f)) 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) } 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)) { // ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가
items(combinedImagesFromSelectedFiles) { imageUrl -> val imageListState = rememberLazyListState()
val isSelected = imageUrl in currentSelectedImages Box(modifier = Modifier.fillMaxWidth().height(120.dp).border(1.dp, Color.LightGray)) {
Box(modifier = Modifier.padding(4.dp)) { LazyRow(
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) 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)) Spacer(Modifier.height(16.dp))
@ -163,17 +177,29 @@ fun DirectPostTab(
Text(Strings.TITLE_IMAGE_UPLOAD, style = MaterialTheme.typography.h6) Text(Strings.TITLE_IMAGE_UPLOAD, style = MaterialTheme.typography.h6)
Button(onClick = onUploadImage, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_IMAGE) } Button(onClick = onUploadImage, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_IMAGE) }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp)) {
items(uploadedImageFiles) { file -> // ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가
Box(modifier = Modifier.padding(4.dp)) { val imageListState = rememberLazyListState()
Image( Box(modifier = Modifier.fillMaxWidth().height(120.dp)) {
painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader), LazyRow(
contentDescription = "Uploaded Image", modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp),
modifier = Modifier.size(100.dp).clickable { onRemoveUploadedImage(file) }, state = imageListState
contentScale = ContentScale.Crop ) {
) 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)) 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)) 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) } Button(onClick = onUploadReceipt, enabled = !isLoading, modifier = Modifier.fillMaxWidth()) { Text(Strings.BUTTON_UPLOAD_RECEIPT) }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
LazyRow(modifier = Modifier.fillMaxWidth().height(120.dp)) {
items(receiptFiles) { file -> // ⭐️ [수정] 스크롤바를 표시하기 위해 Box로 감싸고 HorizontalScrollbar 추가
Box(modifier = Modifier.padding(4.dp)) { val imageListState = rememberLazyListState()
Image( Box(modifier = Modifier.fillMaxWidth().height(120.dp)) {
painter = rememberAsyncImagePainter(model = file, imageLoader = imageLoader), LazyRow(
contentDescription = "Receipt Image", modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp),
modifier = Modifier.size(100.dp).clickable { onRemoveReceipt(file) }, state = imageListState
contentScale = ContentScale.Crop ) {
) 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)) Spacer(Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
@ -322,7 +360,6 @@ fun ResultTab(
} }
} }
// ⭐️ [수정] SettingsTab 함수 시그니처 변경
@Composable @Composable
fun SettingsTab( fun SettingsTab(
generatePromptPrefix: String, generatePromptPrefix: String,

View File

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