feat: add account sync entry and cleanup legacy runtime
This commit is contained in:
@@ -147,6 +147,32 @@ class StoryForgeRepository(private val context: Context) {
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun createContentSourceSyncJob(
|
||||
platform: String,
|
||||
handle: String,
|
||||
sourceUrl: String,
|
||||
title: String,
|
||||
knowledgeBaseId: String,
|
||||
assistantId: String,
|
||||
analysisModelProfileId: String,
|
||||
maxItems: Int,
|
||||
skipExisting: Boolean,
|
||||
autoTriggerAnalysis: Boolean
|
||||
): JobDto = api().createContentSourceSyncJob(
|
||||
ContentSourceSyncRequest(
|
||||
knowledge_base_id = knowledgeBaseId,
|
||||
assistant_id = assistantId,
|
||||
platform = platform,
|
||||
handle = handle,
|
||||
source_url = sourceUrl,
|
||||
title = title,
|
||||
analysis_model_profile_id = analysisModelProfileId,
|
||||
max_items = maxItems,
|
||||
skip_existing = skipExisting,
|
||||
auto_trigger_analysis = autoTriggerAnalysis
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun uploadVideo(
|
||||
uri: Uri,
|
||||
title: String,
|
||||
|
||||
@@ -293,6 +293,7 @@ private fun ExploreTab(state: StoryForgeUiState, vm: StoryForgeViewModel, onPick
|
||||
SectionCard(title = "素材入口", subtitle = "视频链接、上传视频、输入文字都会转成文本并做风格分析") {
|
||||
ChoiceRow(
|
||||
options = listOf(
|
||||
"账号同步" to (state.exploreInputMode == ExploreInputMode.ContentSource),
|
||||
"视频链接" to (state.exploreInputMode == ExploreInputMode.VideoLink),
|
||||
"上传视频" to (state.exploreInputMode == ExploreInputMode.UploadVideo),
|
||||
"输入文字" to (state.exploreInputMode == ExploreInputMode.Text)
|
||||
@@ -300,6 +301,7 @@ private fun ExploreTab(state: StoryForgeUiState, vm: StoryForgeViewModel, onPick
|
||||
onSelect = { label ->
|
||||
vm.setExploreInputMode(
|
||||
when (label) {
|
||||
"账号同步" -> ExploreInputMode.ContentSource
|
||||
"视频链接" -> ExploreInputMode.VideoLink
|
||||
"上传视频" -> ExploreInputMode.UploadVideo
|
||||
else -> ExploreInputMode.Text
|
||||
@@ -319,6 +321,93 @@ private fun ExploreTab(state: StoryForgeUiState, vm: StoryForgeViewModel, onPick
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
when (state.exploreInputMode) {
|
||||
ExploreInputMode.ContentSource -> {
|
||||
Text(
|
||||
text = "适合导入抖音、B 站、小红书创作者账号主页。抖音 public 页面抓不到时,也可以把分享页链接和账号标识手工填进来。",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
ChoiceRow(
|
||||
options = listOf(
|
||||
"抖音" to (state.accountSyncPlatform == "抖音"),
|
||||
"B站" to (state.accountSyncPlatform == "bilibili"),
|
||||
"小红书" to (state.accountSyncPlatform == "小红书")
|
||||
),
|
||||
onSelect = { label ->
|
||||
vm.updateAccountSyncPlatform(
|
||||
when (label) {
|
||||
"B站" -> "bilibili"
|
||||
else -> label
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
value = state.accountSyncUrl,
|
||||
onValueChange = vm::updateAccountSyncUrl,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text("账号主页或分享页链接") },
|
||||
minLines = 2
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
value = state.accountSyncHandle,
|
||||
onValueChange = vm::updateAccountSyncHandle,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text("账号标识(可选)") },
|
||||
singleLine = true
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
value = state.accountSyncTitle,
|
||||
onValueChange = vm::updateAccountSyncTitle,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text("任务标题(可选)") },
|
||||
singleLine = true
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
OutlinedTextField(
|
||||
value = state.accountSyncMaxItems,
|
||||
onValueChange = vm::updateAccountSyncMaxItems,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text("最多拉取视频数(1-20)") },
|
||||
singleLine = true
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text("跳过已存在视频", style = MaterialTheme.typography.bodySmall)
|
||||
Switch(
|
||||
checked = state.accountSyncSkipExisting,
|
||||
onCheckedChange = vm::setAccountSyncSkipExisting
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text("自动触发分析", style = MaterialTheme.typography.bodySmall)
|
||||
Switch(
|
||||
checked = state.accountSyncAutoTriggerAnalysis,
|
||||
onCheckedChange = vm::setAccountSyncAutoTriggerAnalysis
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(onClick = vm::submitContentSourceSync, enabled = !state.busy) {
|
||||
Text("同步账号内容")
|
||||
}
|
||||
}
|
||||
ExploreInputMode.VideoLink -> {
|
||||
OutlinedTextField(
|
||||
value = state.videoUrl,
|
||||
|
||||
@@ -26,6 +26,7 @@ enum class StoryForgeAuthMode {
|
||||
}
|
||||
|
||||
enum class ExploreInputMode {
|
||||
ContentSource,
|
||||
VideoLink,
|
||||
UploadVideo,
|
||||
Text
|
||||
@@ -72,6 +73,13 @@ data class StoryForgeUiState(
|
||||
val createKnowledgeBaseName: String = "",
|
||||
val createKnowledgeBaseDescription: String = "",
|
||||
val exploreInputMode: ExploreInputMode = ExploreInputMode.VideoLink,
|
||||
val accountSyncPlatform: String = "抖音",
|
||||
val accountSyncHandle: String = "",
|
||||
val accountSyncUrl: String = "",
|
||||
val accountSyncTitle: String = "",
|
||||
val accountSyncMaxItems: String = "5",
|
||||
val accountSyncSkipExisting: Boolean = true,
|
||||
val accountSyncAutoTriggerAnalysis: Boolean = true,
|
||||
val videoUrl: String = "",
|
||||
val videoTitle: String = "",
|
||||
val textTitle: String = "",
|
||||
@@ -155,6 +163,35 @@ class StoryForgeViewModel(application: Application) : AndroidViewModel(applicati
|
||||
_state.value = _state.value.copy(videoUrl = value)
|
||||
}
|
||||
|
||||
fun updateAccountSyncPlatform(value: String) {
|
||||
_state.value = _state.value.copy(accountSyncPlatform = value)
|
||||
}
|
||||
|
||||
fun updateAccountSyncHandle(value: String) {
|
||||
_state.value = _state.value.copy(accountSyncHandle = value)
|
||||
}
|
||||
|
||||
fun updateAccountSyncUrl(value: String) {
|
||||
_state.value = _state.value.copy(accountSyncUrl = value)
|
||||
}
|
||||
|
||||
fun updateAccountSyncTitle(value: String) {
|
||||
_state.value = _state.value.copy(accountSyncTitle = value)
|
||||
}
|
||||
|
||||
fun updateAccountSyncMaxItems(value: String) {
|
||||
val digits = value.filter { it.isDigit() }
|
||||
_state.value = _state.value.copy(accountSyncMaxItems = digits)
|
||||
}
|
||||
|
||||
fun setAccountSyncSkipExisting(value: Boolean) {
|
||||
_state.value = _state.value.copy(accountSyncSkipExisting = value)
|
||||
}
|
||||
|
||||
fun setAccountSyncAutoTriggerAnalysis(value: Boolean) {
|
||||
_state.value = _state.value.copy(accountSyncAutoTriggerAnalysis = value)
|
||||
}
|
||||
|
||||
fun updateVideoTitle(value: String) {
|
||||
_state.value = _state.value.copy(videoTitle = value)
|
||||
}
|
||||
@@ -463,6 +500,43 @@ class StoryForgeViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
}
|
||||
|
||||
fun submitContentSourceSync() {
|
||||
val current = state.value
|
||||
if (current.accountSyncUrl.isBlank()) {
|
||||
setError("请先输入账号主页链接")
|
||||
return
|
||||
}
|
||||
val knowledgeBaseId = selectedKnowledgeBaseIdOrFallback()
|
||||
if (knowledgeBaseId.isBlank()) {
|
||||
setError("请先选择知识库")
|
||||
return
|
||||
}
|
||||
val maxItems = current.accountSyncMaxItems.toIntOrNull()?.coerceIn(1, 20) ?: 5
|
||||
runBusy(message = "正在创建账号同步任务...", task = {
|
||||
repository.createContentSourceSyncJob(
|
||||
platform = current.accountSyncPlatform.trim(),
|
||||
handle = current.accountSyncHandle.trim(),
|
||||
sourceUrl = current.accountSyncUrl.trim(),
|
||||
title = current.accountSyncTitle.trim(),
|
||||
knowledgeBaseId = knowledgeBaseId,
|
||||
assistantId = current.selectedAssistantId,
|
||||
analysisModelProfileId = preferredModelId(),
|
||||
maxItems = maxItems,
|
||||
skipExisting = current.accountSyncSkipExisting,
|
||||
autoTriggerAnalysis = current.accountSyncAutoTriggerAnalysis
|
||||
)
|
||||
}) { job ->
|
||||
appendTimeline("账号同步任务已创建: ${job.title}")
|
||||
_state.value = state.value.copy(
|
||||
accountSyncHandle = "",
|
||||
accountSyncUrl = "",
|
||||
accountSyncTitle = "",
|
||||
accountSyncMaxItems = maxItems.toString()
|
||||
)
|
||||
afterJobCreated(job)
|
||||
}
|
||||
}
|
||||
|
||||
fun submitText() {
|
||||
val current = state.value
|
||||
if (current.textTitle.isBlank() || current.textContent.isBlank()) {
|
||||
|
||||
Reference in New Issue
Block a user