添加选择组别开始运行 tk版

This commit is contained in:
2026-04-24 16:41:50 +08:00
parent deb9fc98f0
commit 719317e281
2 changed files with 34 additions and 14 deletions

View File

@@ -89,6 +89,7 @@ export interface StandaloneTikTokAutomationOptions {
dataPoolSource?: 'anchor_hosts' | 'brother_info' | '/api/anchor/hosts/getAll' | '/api/gifters/brotherInfo/getAll' dataPoolSource?: 'anchor_hosts' | 'brother_info' | '/api/anchor/hosts/getAll' | '/api/gifters/brotherInfo/getAll'
groupSwitchMinutes?: number groupSwitchMinutes?: number
groupViewCounts?: number[] groupViewCounts?: number[]
initialFullGroupNumber?: number
continuousMode?: boolean continuousMode?: boolean
runMode?: string runMode?: string
homeUrl?: string homeUrl?: string

View File

@@ -72,11 +72,11 @@
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">翻译 {{ <span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">翻译 {{
configForm.needTranslate ? '已开启' : '未开启' }}</span> configForm.needTranslate ? '已开启' : '未开启' }}</span>
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">语种 {{ languageCount <span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">语种 {{ languageCount
}}</span> }}</span>
</div> </div>
</div> </div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,1fr),minmax(0,1fr),220px] items-end"> <div class="grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,1fr),minmax(0,1fr),220px,220px] items-end">
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700">分组轮换间隔分钟</label> <label class="mb-2 block text-sm font-medium text-gray-700">分组轮换间隔分钟</label>
<input v-model.number="configForm.groupSwitchMinutes" :disabled="isConfigControlDisabled" <input v-model.number="configForm.groupSwitchMinutes" :disabled="isConfigControlDisabled"
@@ -91,6 +91,17 @@
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" 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" /> @blur="handlePostUserSleepBlur" />
</div> </div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-700">全流程起始分组</label>
<select v-model.number="configForm.initialFullGroupNumber" :disabled="isConfigControlDisabled"
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"
@change="handleInitialFullGroupChange">
<option v-for="option in startFullGroupOptions" :key="option.value" :value="option.value"
:disabled="!option.enabled">
{{ option.label }}{{ option.hint }}{{ option.enabled ? '' : ',未启用' }}
</option>
</select>
</div>
<label v-if="!isBrotherInfoMode" <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"> 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="isConfigControlDisabled" <input v-model="configForm.replyUnreadMessages" :disabled="isConfigControlDisabled"
@@ -153,7 +164,7 @@
<div class="mt-1 text-xs text-gray-500">{{ groupRangeLabel(index) }}</div> <div class="mt-1 text-xs text-gray-500">{{ groupRangeLabel(index) }}</div>
</div> </div>
<span class="rounded-full bg-gray-100 px-2 py-1 text-[10px] text-gray-600">{{ groupViewText(count) <span class="rounded-full bg-gray-100 px-2 py-1 text-[10px] text-gray-600">{{ groupViewText(count)
}}</span> }}</span>
</div> </div>
<input v-model.number="configForm.groupViewCounts[index]" :disabled="isConfigControlDisabled" <input v-model.number="configForm.groupViewCounts[index]" :disabled="isConfigControlDisabled"
type="number" min="0" max="3" step="1" type="number" min="0" max="3" step="1"
@@ -199,10 +210,10 @@
class="mt-4 w-full rounded-lg border border-fuchsia-200 bg-fuchsia-50 px-4 py-3 text-sm font-medium text-fuchsia-600 transition-colors hover:bg-fuchsia-100">打开大哥池</button> class="mt-4 w-full rounded-lg border border-fuchsia-200 bg-fuchsia-50 px-4 py-3 text-sm font-medium text-fuchsia-600 transition-colors hover:bg-fuchsia-100">打开大哥池</button>
</section> </section>
<section class="rounded-xl bg-slate-950 p-5 shadow-sm"> <!-- <section class="rounded-xl bg-slate-950 p-5 shadow-sm">
<div class="text-sm font-medium text-slate-100">本次提交参数</div> <div class="text-sm font-medium text-slate-100">本次提交参数</div>
<pre class="mt-3 overflow-x-auto text-xs leading-6 text-slate-300">{{ payloadPreview }}</pre> <pre class="mt-3 overflow-x-auto text-xs leading-6 text-slate-300">{{ payloadPreview }}</pre>
</section> </section> -->
</div> </div>
</div> </div>
@@ -275,7 +286,7 @@
{{ viewId }}</button> {{ viewId }}</button>
<div class="flex-1" /> <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 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" <span v-if="isSwitchingView"
class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-600">切换中...</span> class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-600">切换中...</span>
</div> </div>
@@ -307,6 +318,7 @@ import BrotherInfoDialog from '@/components/BrotherInfoDialog.vue'
const props = defineProps({ navSidebarWidth: { type: Number, default: 144 } }) const props = defineProps({ navSidebarWidth: { type: Number, default: 144 } })
const TIKTOK_VIEW_IDS = Array.from({ length: 9 }, (_, index) => index + 10) const TIKTOK_VIEW_IDS = Array.from({ length: 9 }, (_, index) => index + 10)
const DEFAULT_GROUP_COUNTS = [3, 3, 3] const DEFAULT_GROUP_COUNTS = [3, 3, 3]
const DEFAULT_INITIAL_FULL_GROUP_NUMBER = 1
const isElectronEnv = isElectron() const isElectronEnv = isElectron()
const pageMode = ref('config') const pageMode = ref('config')
const activeViewId = ref(TIKTOK_VIEW_IDS[0]) const activeViewId = ref(TIKTOK_VIEW_IDS[0])
@@ -323,7 +335,7 @@ const isPreparing = ref(false)
const isStarting = ref(false) const isStarting = ref(false)
const isStopping = ref(false) const isStopping = ref(false)
const isSwitchingView = ref(false) const isSwitchingView = ref(false)
const configForm = reactive({ prologueList: {}, needTranslate: false, dataPoolSource: 'anchor_hosts', replyMessagesText: '', replyUnreadMessages: true, groupSwitchMinutes: 10, postUserSleepSeconds: 6, groupViewCounts: [...DEFAULT_GROUP_COUNTS] }) const configForm = reactive({ prologueList: {}, needTranslate: false, dataPoolSource: 'anchor_hosts', replyMessagesText: '', replyUnreadMessages: true, groupSwitchMinutes: 10, initialFullGroupNumber: DEFAULT_INITIAL_FULL_GROUP_NUMBER, postUserSleepSeconds: 6, groupViewCounts: [...DEFAULT_GROUP_COUNTS] })
const aiConfig = ref({ agentName: '', guildName: '', contactTool: '', contact: '' }) const aiConfig = ref({ agentName: '', guildName: '', contactTool: '', contact: '' })
const sidebarStyle = computed(() => ({ width: `${props.navSidebarWidth}px`, minWidth: '96px', maxWidth: '400px' })) const sidebarStyle = computed(() => ({ width: `${props.navSidebarWidth}px`, minWidth: '96px', maxWidth: '400px' }))
const greetingMessages = computed(() => { const greetingMessages = computed(() => {
@@ -339,6 +351,8 @@ const effectiveReplyUnreadMessages = computed(() => !isBrotherInfoMode.value &&
const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? '大哥池' : '主播池')) const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? '大哥池' : '主播池'))
const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes)) const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes))
const normalizedGroupViewCounts = computed(() => normalizeGroupViewCounts(configForm.groupViewCounts)) const normalizedGroupViewCounts = computed(() => normalizeGroupViewCounts(configForm.groupViewCounts))
const effectiveInitialFullGroupNumber = computed(() => resolveInitialFullGroupNumber(configForm.initialFullGroupNumber, normalizedGroupViewCounts.value))
const startFullGroupOptions = computed(() => normalizedGroupViewCounts.value.map((enabledCount, groupIndex) => ({ value: groupIndex + 1, label: `${groupIndex + 1}`, hint: groupRangeLabel(groupIndex), enabled: enabledCount > 0 })))
const browserViewGroups = computed(() => normalizedGroupViewCounts.value.map((enabledCount, groupIndex) => { const browserViewGroups = computed(() => normalizedGroupViewCounts.value.map((enabledCount, groupIndex) => {
const baseViewId = TIKTOK_VIEW_IDS[groupIndex * 3] const baseViewId = TIKTOK_VIEW_IDS[groupIndex * 3]
return { groupIndex, label: `视图 ${baseViewId}-${baseViewId + 2}`, views: Array.from({ length: 3 }, (_, offset) => ({ viewId: baseViewId + offset, enabled: offset < enabledCount, active: activeViewId.value === baseViewId + offset })) } return { groupIndex, label: `视图 ${baseViewId}-${baseViewId + 2}`, views: Array.from({ length: 3 }, (_, offset) => ({ viewId: baseViewId + offset, enabled: offset < enabledCount, active: activeViewId.value === baseViewId + offset })) }
@@ -346,7 +360,7 @@ 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 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 totalEnabledViews = computed(() => normalizedGroupViewCounts.value.reduce((sum, count) => sum + count, 0))
const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2)) const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2))
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 currentPrepareKey = computed(() => JSON.stringify({ groupViewCounts: normalizedGroupViewCounts.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, initialFullGroupNumber: effectiveInitialFullGroupNumber.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 hasPreparedViews = computed(() => preparedConfigKey.value === currentPrepareKey.value)
const isConfigLocked = computed(() => workbenchPhase.value !== 'idle') const isConfigLocked = computed(() => workbenchPhase.value !== 'idle')
const isConfigControlDisabled = computed(() => !isElectronEnv || isPreparing.value || isStarting.value || isStopping.value || isConfigLocked.value) const isConfigControlDisabled = computed(() => !isElectronEnv || isPreparing.value || isStarting.value || isStopping.value || isConfigLocked.value)
@@ -355,10 +369,12 @@ const statusChipClass = computed(() => statusText.value.includes('运行') ? 'ro
function clampMinutes(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? 10 : Math.max(1, Math.round(numericValue)) } 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)) } function clampPositiveSeconds(value, fallback = 6) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? fallback : Math.max(1, Math.round(numericValue)) }
function clampGroupCount(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) ? 0 : Math.min(3, Math.max(0, Math.round(numericValue))) } function clampGroupCount(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) ? 0 : Math.min(3, Math.max(0, Math.round(numericValue))) }
function clampInitialFullGroupNumber(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) ? DEFAULT_INITIAL_FULL_GROUP_NUMBER : Math.min(DEFAULT_GROUP_COUNTS.length, Math.max(1, Math.round(numericValue))) }
function normalizeGroupViewCounts(values) { const safeValues = Array.isArray(values) ? values.slice(0, 3) : []; while (safeValues.length < 3) safeValues.push(0); return safeValues.map(clampGroupCount) } function normalizeGroupViewCounts(values) { const safeValues = Array.isArray(values) ? values.slice(0, 3) : []; while (safeValues.length < 3) safeValues.push(0); return safeValues.map(clampGroupCount) }
function resolveInitialFullGroupNumber(value, groupViewCounts) { const requestedGroupNumber = clampInitialFullGroupNumber(value); for (let offset = 0; offset < groupViewCounts.length; offset += 1) { const candidateIndex = (requestedGroupNumber - 1 + offset) % groupViewCounts.length; if ((groupViewCounts[candidateIndex] || 0) > 0) return candidateIndex + 1 } return requestedGroupNumber }
const normalizedPostUserSleepSeconds = computed(() => clampPositiveSeconds(configForm.postUserSleepSeconds, 6)) const normalizedPostUserSleepSeconds = computed(() => clampPositiveSeconds(configForm.postUserSleepSeconds, 6))
function buildStartPayload() { function buildStartPayload() {
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) } const payload = { dataPoolSource: configForm.dataPoolSource, replyUnreadMessages: effectiveReplyUnreadMessages.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, initialFullGroupNumber: effectiveInitialFullGroupNumber.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 (greetingMessages.value.length > 0) payload.greetingMessages = greetingMessages.value
if (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value if (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value
return payload return payload
@@ -392,8 +408,9 @@ async function showBrowserWorkspace(viewId, fallbackStatusText) {
} }
function handleGreetingConfirm(data) { configForm.prologueList = { yolo: data.sentences || [], ...(data.translations || {}) }; configForm.needTranslate = Boolean(data.needTranslate); showGreetingDialog.value = false; invalidatePreparedState(); void saveSharedConfig() } function handleGreetingConfirm(data) { configForm.prologueList = { yolo: data.sentences || [], ...(data.translations || {}) }; configForm.needTranslate = Boolean(data.needTranslate); showGreetingDialog.value = false; invalidatePreparedState(); void saveSharedConfig() }
function handleSwitchMinutesBlur() { configForm.groupSwitchMinutes = clampMinutes(configForm.groupSwitchMinutes); invalidatePreparedState() } function handleSwitchMinutesBlur() { configForm.groupSwitchMinutes = clampMinutes(configForm.groupSwitchMinutes); invalidatePreparedState() }
function handleInitialFullGroupChange() { configForm.initialFullGroupNumber = effectiveInitialFullGroupNumber.value; invalidatePreparedState(); void saveSharedConfig() }
function handlePostUserSleepBlur() { configForm.postUserSleepSeconds = clampPositiveSeconds(configForm.postUserSleepSeconds, 6); invalidatePreparedState() } function handlePostUserSleepBlur() { configForm.postUserSleepSeconds = clampPositiveSeconds(configForm.postUserSleepSeconds, 6); invalidatePreparedState() }
function handleGroupCountBlur(index) { configForm.groupViewCounts[index] = clampGroupCount(configForm.groupViewCounts[index]); invalidatePreparedState('视图配置已变更,请重新预热视图') } function handleGroupCountBlur(index) { configForm.groupViewCounts[index] = clampGroupCount(configForm.groupViewCounts[index]); configForm.initialFullGroupNumber = effectiveInitialFullGroupNumber.value; invalidatePreparedState('视图配置已变更,请重新预热视图') }
function groupViewText(count) { return count <= 0 ? '不启用' : `开启 ${count}` } function groupViewText(count) { return count <= 0 ? '不启用' : `开启 ${count}` }
function groupRangeLabel(index) { const startViewId = TIKTOK_VIEW_IDS[index * 3]; const endViewId = TIKTOK_VIEW_IDS[index * 3 + 2]; return `视图 ${startViewId}-${endViewId}` } function groupRangeLabel(index) { const startViewId = TIKTOK_VIEW_IDS[index * 3]; const endViewId = TIKTOK_VIEW_IDS[index * 3 + 2]; return `视图 ${startViewId}-${endViewId}` }
async function checkAIConfig() { async function checkAIConfig() {
@@ -421,6 +438,7 @@ async function loadSharedConfig() {
if (typeof saved?.needTranslate === 'boolean') configForm.needTranslate = saved.needTranslate if (typeof saved?.needTranslate === 'boolean') configForm.needTranslate = saved.needTranslate
if (saved?.standaloneTikTokDataPoolSource === 'anchor_hosts' || saved?.standaloneTikTokDataPoolSource === 'brother_info') configForm.dataPoolSource = saved.standaloneTikTokDataPoolSource if (saved?.standaloneTikTokDataPoolSource === 'anchor_hosts' || saved?.standaloneTikTokDataPoolSource === 'brother_info') configForm.dataPoolSource = saved.standaloneTikTokDataPoolSource
if (Array.isArray(saved?.standaloneTikTokReplyMessages)) configForm.replyMessagesText = saved.standaloneTikTokReplyMessages.join('\n') if (Array.isArray(saved?.standaloneTikTokReplyMessages)) configForm.replyMessagesText = saved.standaloneTikTokReplyMessages.join('\n')
if (saved?.standaloneTikTokInitialFullGroupNumber !== undefined) configForm.initialFullGroupNumber = clampInitialFullGroupNumber(saved.standaloneTikTokInitialFullGroupNumber)
if (saved?.standaloneTikTokPostUserSleepSeconds !== undefined) configForm.postUserSleepSeconds = clampPositiveSeconds(saved.standaloneTikTokPostUserSleepSeconds, 6) if (saved?.standaloneTikTokPostUserSleepSeconds !== undefined) configForm.postUserSleepSeconds = clampPositiveSeconds(saved.standaloneTikTokPostUserSleepSeconds, 6)
} catch (error) { } catch (error) {
console.error('load shared config failed:', error) console.error('load shared config failed:', error)
@@ -436,6 +454,7 @@ async function saveSharedConfig() {
needTranslate: Boolean(configForm.needTranslate), needTranslate: Boolean(configForm.needTranslate),
standaloneTikTokDataPoolSource: configForm.dataPoolSource, standaloneTikTokDataPoolSource: configForm.dataPoolSource,
standaloneTikTokReplyMessages: replyMessages.value, standaloneTikTokReplyMessages: replyMessages.value,
standaloneTikTokInitialFullGroupNumber: effectiveInitialFullGroupNumber.value,
standaloneTikTokPostUserSleepSeconds: normalizedPostUserSleepSeconds.value standaloneTikTokPostUserSleepSeconds: normalizedPostUserSleepSeconds.value
} }
await window.electronAPI.saveRunConfig(nextConfig) await window.electronAPI.saveRunConfig(nextConfig)
@@ -529,10 +548,6 @@ async function handleStart() {
ElMessage.warning('请先完成当前配置对应的视图预热,再启动脚本') ElMessage.warning('请先完成当前配置对应的视图预热,再启动脚本')
return return
} }
if (isBrotherInfoMode.value && replyMessages.value.length === 0) {
ElMessage.warning('选择大哥池后,请先填写大哥随机回复话术')
return
}
if (!loginConfirmed.value) { if (!loginConfirmed.value) {
ElMessage.warning('请先在当前视图手动登录,并勾选“当前视图已完成手动登录”后再启动脚本') ElMessage.warning('请先在当前视图手动登录,并勾选“当前视图已完成手动登录”后再启动脚本')
return return
@@ -594,6 +609,10 @@ watch(() => configForm.replyMessagesText, () => {
if (!isBrotherInfoMode.value) return if (!isBrotherInfoMode.value) return
invalidatePreparedState() invalidatePreparedState()
}) })
watch(normalizedGroupViewCounts, () => {
const nextGroupNumber = effectiveInitialFullGroupNumber.value
if (configForm.initialFullGroupNumber !== nextGroupNumber) configForm.initialFullGroupNumber = nextGroupNumber
})
watch(viewIds, (nextViewIds) => { watch(viewIds, (nextViewIds) => {
if (nextViewIds.length === 0) { if (nextViewIds.length === 0) {
activeViewId.value = TIKTOK_VIEW_IDS[0] activeViewId.value = TIKTOK_VIEW_IDS[0]