tk版大哥池隐藏回复
This commit is contained in:
@@ -404,7 +404,7 @@ const languageList = [
|
||||
{ name: '中文 (繁體)', code: 'zh-Hant-TW' },
|
||||
{ name: 'Deutsch', code: 'de-DE' },
|
||||
{ name: 'Italiano', code: 'it-IT' },
|
||||
{ name: 'Français', code: 'fr-FR' },
|
||||
{ name: 'Français', code: 'fr' },
|
||||
{ name: 'Română', code: 'ro-RO' },
|
||||
{ name: 'Polski', code: 'pl-PL' },
|
||||
{ name: 'Nederlands', code: 'nl-NL' },
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span :class="statusChipClass">{{ statusText }}</span>
|
||||
<button @click="handlePrepareViews" :disabled="isPreparing || !isElectronEnv"
|
||||
<button @click="handlePrepareViews" :disabled="isPrepareButtonDisabled"
|
||||
class="flex items-center gap-2 rounded-lg bg-gradient-to-r from-emerald-500 to-teal-500 px-4 py-2 text-sm text-white shadow-sm transition-all hover:from-emerald-600 hover:to-teal-600 disabled:cursor-not-allowed disabled:opacity-60">
|
||||
<span>{{ isPreparing ? '预热中...' : '预热视图' }}</span>
|
||||
</button>
|
||||
@@ -26,6 +26,11 @@
|
||||
先填写配置,再预热视图,登录完成后再启动脚本。
|
||||
</div>
|
||||
|
||||
<div v-if="isConfigLocked"
|
||||
class="mb-6 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm leading-6 text-amber-800">
|
||||
当前配置已锁定。停止任务成功后,才可以修改配置、重新预热并再次启动。
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<div class="col-span-2 space-y-6">
|
||||
<section class="rounded-xl border border-gray-200 bg-gradient-to-b from-white to-gray-50 p-6 shadow-sm">
|
||||
@@ -56,7 +61,7 @@
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium text-gray-700">打招呼内容</label>
|
||||
</div>
|
||||
<button @click="showGreetingDialog = true" :disabled="isStarting || !isElectronEnv"
|
||||
<button @click="showGreetingDialog = true" :disabled="isConfigControlDisabled"
|
||||
class="rounded-lg border border-blue-200 bg-blue-50 px-4 py-2 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-100 disabled:cursor-not-allowed disabled:opacity-60">
|
||||
打招呼内容配置
|
||||
</button>
|
||||
@@ -67,28 +72,28 @@
|
||||
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">翻译 {{
|
||||
configForm.needTranslate ? '已开启' : '未开启' }}</span>
|
||||
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">语种 {{ languageCount
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,1fr),minmax(0,1fr),220px] items-end">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700">分组轮换间隔(分钟)</label>
|
||||
<input v-model.number="configForm.groupSwitchMinutes" :disabled="isStarting || !isElectronEnv"
|
||||
<input v-model.number="configForm.groupSwitchMinutes" :disabled="isConfigControlDisabled"
|
||||
type="number" min="1" step="1"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-white px-4 text-sm text-gray-800 outline-none focus:border-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
@blur="handleSwitchMinutesBlur" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700">每用户休眠秒数</label>
|
||||
<input v-model.number="configForm.postUserSleepSeconds" :disabled="isStarting || !isElectronEnv"
|
||||
<input v-model.number="configForm.postUserSleepSeconds" :disabled="isConfigControlDisabled"
|
||||
type="number" min="1" step="1"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-white px-4 text-sm text-gray-800 outline-none focus:border-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
@blur="handlePostUserSleepBlur" />
|
||||
</div>
|
||||
<label
|
||||
<label v-if="!isBrotherInfoMode"
|
||||
class="flex h-11 items-center gap-3 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-700">
|
||||
<input v-model="configForm.replyUnreadMessages" :disabled="isStarting || !isElectronEnv"
|
||||
<input v-model="configForm.replyUnreadMessages" :disabled="isConfigControlDisabled"
|
||||
type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
|
||||
<span>开启 AI 回复未读消息</span>
|
||||
</label>
|
||||
@@ -106,27 +111,27 @@
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="flex items-center gap-3 rounded-lg border px-4 py-3 text-sm transition-colors"
|
||||
:class="configForm.dataPoolSource === 'anchor_hosts' ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-gray-200 bg-gray-50 text-gray-700'">
|
||||
<input v-model="configForm.dataPoolSource" :disabled="isStarting || !isElectronEnv" type="radio"
|
||||
<input v-model="configForm.dataPoolSource" :disabled="isConfigControlDisabled" type="radio"
|
||||
value="anchor_hosts" class="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500" />
|
||||
<span>主播池</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-3 rounded-lg border px-4 py-3 text-sm transition-colors"
|
||||
:class="configForm.dataPoolSource === 'brother_info' ? 'border-purple-500 bg-purple-50 text-purple-700' : 'border-gray-200 bg-gray-50 text-gray-700'">
|
||||
<input v-model="configForm.dataPoolSource" :disabled="isStarting || !isElectronEnv" type="radio"
|
||||
<input v-model="configForm.dataPoolSource" :disabled="isConfigControlDisabled" type="radio"
|
||||
value="brother_info" class="h-4 w-4 border-gray-300 text-purple-600 focus:ring-purple-500" />
|
||||
<span>大哥池</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="isBrotherInfoMode" class="mt-4">
|
||||
<!-- <div v-if="isBrotherInfoMode" class="mt-4">
|
||||
<div class="mb-2 flex items-center justify-between gap-3">
|
||||
<label class="block text-sm font-medium text-gray-700">大哥随机回复话术</label>
|
||||
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1 text-xs text-gray-600">{{
|
||||
replyMessageCount }} 条</span>
|
||||
</div>
|
||||
<textarea v-model="configForm.replyMessagesText" :disabled="isStarting || !isElectronEnv" rows="5"
|
||||
<textarea v-model="configForm.replyMessagesText" :disabled="isConfigControlDisabled" rows="5"
|
||||
placeholder="一行一条回复话术,启动时传给后端 replyMessages"
|
||||
class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-sm text-gray-800 outline-none focus:border-purple-500 disabled:cursor-not-allowed disabled:opacity-60" />
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -148,9 +153,9 @@
|
||||
<div class="mt-1 text-xs text-gray-500">{{ groupRangeLabel(index) }}</div>
|
||||
</div>
|
||||
<span class="rounded-full bg-gray-100 px-2 py-1 text-[10px] text-gray-600">{{ groupViewText(count)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<input v-model.number="configForm.groupViewCounts[index]" :disabled="isStarting || !isElectronEnv"
|
||||
<input v-model.number="configForm.groupViewCounts[index]" :disabled="isConfigControlDisabled"
|
||||
type="number" min="0" max="3" step="1"
|
||||
class="mt-4 h-11 w-full rounded-lg border border-gray-300 bg-gray-50 px-4 text-sm text-gray-800 outline-none focus:border-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
@blur="handleGroupCountBlur(index)" />
|
||||
@@ -169,7 +174,7 @@
|
||||
:class="['rounded-full px-3 py-1 text-xs font-medium', aiConfigured ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700']">{{
|
||||
aiConfigured ? '已配置' : '未配置' }}</span>
|
||||
</div>
|
||||
<button @click="showAIDialog = true"
|
||||
<button @click="showAIDialog = true" :disabled="isConfigControlDisabled"
|
||||
class="mt-4 w-full rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-100">配置
|
||||
/ 修改 AI 人设</button>
|
||||
</section>
|
||||
@@ -270,7 +275,7 @@
|
||||
{{ viewId }}</button>
|
||||
<div class="flex-1" />
|
||||
<span class="rounded border border-gray-200 bg-gray-100 px-2 py-1 text-xs text-gray-500">{{ statusText
|
||||
}}</span>
|
||||
}}</span>
|
||||
<span v-if="isSwitchingView"
|
||||
class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-600">切换中...</span>
|
||||
</div>
|
||||
@@ -313,6 +318,7 @@ const showBrotherInfoDialog = ref(false)
|
||||
const aiConfigured = ref(false)
|
||||
const loginConfirmed = ref(false)
|
||||
const preparedConfigKey = ref('')
|
||||
const workbenchPhase = ref('idle')
|
||||
const isPreparing = ref(false)
|
||||
const isStarting = ref(false)
|
||||
const isStopping = ref(false)
|
||||
@@ -329,6 +335,7 @@ const greetingCount = computed(() => greetingMessages.value.length)
|
||||
const replyMessageCount = computed(() => replyMessages.value.length)
|
||||
const languageCount = computed(() => Object.keys(configForm.prologueList || {}).length)
|
||||
const isBrotherInfoMode = computed(() => configForm.dataPoolSource === 'brother_info')
|
||||
const effectiveReplyUnreadMessages = computed(() => !isBrotherInfoMode.value && Boolean(configForm.replyUnreadMessages))
|
||||
const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? '大哥池' : '主播池'))
|
||||
const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes))
|
||||
const normalizedGroupViewCounts = computed(() => normalizeGroupViewCounts(configForm.groupViewCounts))
|
||||
@@ -339,8 +346,11 @@ const browserViewGroups = computed(() => normalizedGroupViewCounts.value.map((en
|
||||
const viewIds = computed(() => browserViewGroups.value.flatMap(group => group.views.filter(view => view.enabled).map(view => view.viewId)))
|
||||
const totalEnabledViews = computed(() => normalizedGroupViewCounts.value.reduce((sum, count) => sum + count, 0))
|
||||
const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2))
|
||||
const currentPrepareKey = computed(() => JSON.stringify({ groupViewCounts: normalizedGroupViewCounts.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, replyUnreadMessages: Boolean(configForm.replyUnreadMessages), dataPoolSource: configForm.dataPoolSource, replyMessages: replyMessages.value, prologueList: configForm.prologueList || {}, needTranslate: Boolean(configForm.needTranslate) }))
|
||||
const currentPrepareKey = computed(() => JSON.stringify({ groupViewCounts: normalizedGroupViewCounts.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, replyUnreadMessages: effectiveReplyUnreadMessages.value, dataPoolSource: configForm.dataPoolSource, replyMessages: replyMessages.value, prologueList: configForm.prologueList || {}, needTranslate: Boolean(configForm.needTranslate) }))
|
||||
const hasPreparedViews = computed(() => preparedConfigKey.value === currentPrepareKey.value)
|
||||
const isConfigLocked = computed(() => workbenchPhase.value !== 'idle')
|
||||
const isConfigControlDisabled = computed(() => !isElectronEnv || isPreparing.value || isStarting.value || isStopping.value || isConfigLocked.value)
|
||||
const isPrepareButtonDisabled = computed(() => !isElectronEnv || isPreparing.value || isStarting.value || isStopping.value || isConfigLocked.value)
|
||||
const statusChipClass = computed(() => statusText.value.includes('运行') ? 'rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700' : statusText.value.includes('失败') ? 'rounded-full border border-rose-200 bg-rose-50 px-3 py-1 text-xs font-medium text-rose-700' : 'rounded-full border border-amber-200 bg-amber-50 px-3 py-1 text-xs font-medium text-amber-700')
|
||||
function clampMinutes(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? 10 : Math.max(1, Math.round(numericValue)) }
|
||||
function clampPositiveSeconds(value, fallback = 6) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? fallback : Math.max(1, Math.round(numericValue)) }
|
||||
@@ -348,13 +358,32 @@ function clampGroupCount(value) { const numericValue = Number(value); return !Nu
|
||||
function normalizeGroupViewCounts(values) { const safeValues = Array.isArray(values) ? values.slice(0, 3) : []; while (safeValues.length < 3) safeValues.push(0); return safeValues.map(clampGroupCount) }
|
||||
const normalizedPostUserSleepSeconds = computed(() => clampPositiveSeconds(configForm.postUserSleepSeconds, 6))
|
||||
function buildStartPayload() {
|
||||
const payload = { dataPoolSource: configForm.dataPoolSource, replyUnreadMessages: Boolean(configForm.replyUnreadMessages), groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, groupViewCounts: normalizedGroupViewCounts.value, prologueList: JSON.parse(JSON.stringify(configForm.prologueList || {})), needTranslate: Boolean(configForm.needTranslate) }
|
||||
const payload = { dataPoolSource: configForm.dataPoolSource, replyUnreadMessages: effectiveReplyUnreadMessages.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, groupViewCounts: normalizedGroupViewCounts.value, prologueList: JSON.parse(JSON.stringify(configForm.prologueList || {})), needTranslate: Boolean(configForm.needTranslate) }
|
||||
if (greetingMessages.value.length > 0) payload.greetingMessages = greetingMessages.value
|
||||
if (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value
|
||||
return payload
|
||||
}
|
||||
function ensureElectronCapability(methodName) { if (!isElectronEnv || !window.electronAPI?.[methodName]) { ElMessage.error('当前环境不支持该功能'); return false } return true }
|
||||
function invalidatePreparedState(nextStatus = '配置已变更,请重新预热视图') { preparedConfigKey.value = ''; loginConfirmed.value = false; if (!statusText.value.includes('运行')) statusText.value = nextStatus }
|
||||
async function syncWorkbenchState() {
|
||||
if (!isElectronEnv || !window.electronAPI?.getStandaloneTikTokWorkbenchState) return
|
||||
try {
|
||||
const state = await window.electronAPI.getStandaloneTikTokWorkbenchState()
|
||||
workbenchPhase.value = state?.phase || 'idle'
|
||||
if (state?.locked) {
|
||||
preparedConfigKey.value = currentPrepareKey.value
|
||||
if (state.phase === 'running') {
|
||||
statusText.value = '运行中'
|
||||
} else if (!statusText.value.includes('运行')) {
|
||||
statusText.value = '视图已预热,当前配置已锁定'
|
||||
}
|
||||
} else {
|
||||
preparedConfigKey.value = ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('sync standalone tiktok workbench state failed:', error)
|
||||
}
|
||||
}
|
||||
async function showBrowserWorkspace(viewId, fallbackStatusText) {
|
||||
pageMode.value = 'browser'
|
||||
await window.electronAPI.switchToView(viewId)
|
||||
@@ -416,6 +445,10 @@ async function saveSharedConfig() {
|
||||
}
|
||||
async function handlePrepareViews() {
|
||||
if (!ensureElectronCapability('prepareStandaloneTikTokViews')) return
|
||||
if (isConfigLocked.value) {
|
||||
ElMessage.warning('当前配置已锁定,请先停止任务后再修改配置或重新预热')
|
||||
return
|
||||
}
|
||||
if (viewIds.value.length === 0) {
|
||||
ElMessage.warning('请至少启用一个视图后再预热')
|
||||
return
|
||||
@@ -426,6 +459,7 @@ async function handlePrepareViews() {
|
||||
const result = await window.electronAPI.prepareStandaloneTikTokViews({ groupViewCounts: normalizedGroupViewCounts.value, targetViewId: activeViewId.value })
|
||||
if (!result?.success) throw new Error(result?.error || 'prepare standalone tiktok views failed')
|
||||
if (result.currentViewId) activeViewId.value = result.currentViewId
|
||||
workbenchPhase.value = 'prepared'
|
||||
preparedConfigKey.value = currentPrepareKey.value
|
||||
await saveSharedConfig()
|
||||
if (pageMode.value === 'config') {
|
||||
@@ -487,6 +521,10 @@ async function handleSwitchView(viewId) {
|
||||
}
|
||||
async function handleStart() {
|
||||
if (!ensureElectronCapability('startStandaloneTikTokAutomationAll')) return
|
||||
if (workbenchPhase.value === 'running') {
|
||||
ElMessage.warning('当前任务已在运行中,请先停止后再重新启动')
|
||||
return
|
||||
}
|
||||
if (!hasPreparedViews.value) {
|
||||
ElMessage.warning('请先完成当前配置对应的视图预热,再启动脚本')
|
||||
return
|
||||
@@ -509,6 +547,7 @@ async function handleStart() {
|
||||
ElMessage.error(result?.error || '启动自动私信(TK版)失败')
|
||||
return
|
||||
}
|
||||
workbenchPhase.value = 'running'
|
||||
statusText.value = `运行中(视图 ${activeViewId.value})`
|
||||
ElMessage.success('自动私信(TK版)已启动')
|
||||
} catch (error) {
|
||||
@@ -529,9 +568,10 @@ async function handleStop() {
|
||||
ElMessage.error(result?.error || '停止自动私信(TK版)失败')
|
||||
return
|
||||
}
|
||||
workbenchPhase.value = 'idle'
|
||||
preparedConfigKey.value = ''
|
||||
loginConfirmed.value = false
|
||||
statusText.value = '已停止'
|
||||
statusText.value = result?.message?.includes('取消预热状态') ? '已解锁' : '已停止'
|
||||
ElMessage.success('自动私信(TK版)已停止')
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
@@ -546,6 +586,7 @@ watch(currentPrepareKey, (nextKey, prevKey) => {
|
||||
if (preparedConfigKey.value && preparedConfigKey.value !== nextKey) invalidatePreparedState()
|
||||
})
|
||||
watch(() => configForm.dataPoolSource, () => {
|
||||
if (isBrotherInfoMode.value && configForm.replyUnreadMessages) configForm.replyUnreadMessages = false
|
||||
invalidatePreparedState()
|
||||
void saveSharedConfig()
|
||||
})
|
||||
@@ -567,6 +608,7 @@ watch(pageMode, async (newVal) => {
|
||||
onMounted(async () => {
|
||||
await checkAIConfig()
|
||||
await loadSharedConfig()
|
||||
await syncWorkbenchState()
|
||||
if (!isElectronEnv || !window.electronAPI?.hideViews) return
|
||||
await window.electronAPI.hideViews().catch(() => { })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user