diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index d2087a7..d6a7489 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -89,6 +89,7 @@ export interface StandaloneTikTokAutomationOptions { dataPoolSource?: 'anchor_hosts' | 'brother_info' | '/api/anchor/hosts/getAll' | '/api/gifters/brotherInfo/getAll' groupSwitchMinutes?: number groupViewCounts?: number[] + initialFullGroupNumber?: number continuousMode?: boolean runMode?: string homeUrl?: string diff --git a/src/views/auto-dm/AutoDmTkWorkbench.vue b/src/views/auto-dm/AutoDmTkWorkbench.vue index 8278683..2fca8f8 100644 --- a/src/views/auto-dm/AutoDmTkWorkbench.vue +++ b/src/views/auto-dm/AutoDmTkWorkbench.vue @@ -72,11 +72,11 @@ 翻译 {{ configForm.needTranslate ? '已开启' : '未开启' }} 语种 {{ languageCount - }} + }} -
+
+
+ + +
{{ groupViewText(count) - }} + }} 打开大哥池 -
+ @@ -275,7 +286,7 @@ {{ viewId }}
{{ statusText - }} + }} 切换中...
@@ -307,6 +318,7 @@ import BrotherInfoDialog from '@/components/BrotherInfoDialog.vue' const props = defineProps({ navSidebarWidth: { type: Number, default: 144 } }) const TIKTOK_VIEW_IDS = Array.from({ length: 9 }, (_, index) => index + 10) const DEFAULT_GROUP_COUNTS = [3, 3, 3] +const DEFAULT_INITIAL_FULL_GROUP_NUMBER = 1 const isElectronEnv = isElectron() const pageMode = ref('config') const activeViewId = ref(TIKTOK_VIEW_IDS[0]) @@ -323,7 +335,7 @@ const isPreparing = ref(false) const isStarting = ref(false) const isStopping = 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 sidebarStyle = computed(() => ({ width: `${props.navSidebarWidth}px`, minWidth: '96px', maxWidth: '400px' })) const greetingMessages = computed(() => { @@ -339,6 +351,8 @@ const effectiveReplyUnreadMessages = computed(() => !isBrotherInfoMode.value && const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? '大哥池' : '主播池')) const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes)) 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 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 })) } @@ -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 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: 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 isConfigLocked = computed(() => workbenchPhase.value !== 'idle') 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 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 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 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)) 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 (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value 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 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 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 groupRangeLabel(index) { const startViewId = TIKTOK_VIEW_IDS[index * 3]; const endViewId = TIKTOK_VIEW_IDS[index * 3 + 2]; return `视图 ${startViewId}-${endViewId}` } async function checkAIConfig() { @@ -421,6 +438,7 @@ async function loadSharedConfig() { if (typeof saved?.needTranslate === 'boolean') configForm.needTranslate = saved.needTranslate 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 (saved?.standaloneTikTokInitialFullGroupNumber !== undefined) configForm.initialFullGroupNumber = clampInitialFullGroupNumber(saved.standaloneTikTokInitialFullGroupNumber) if (saved?.standaloneTikTokPostUserSleepSeconds !== undefined) configForm.postUserSleepSeconds = clampPositiveSeconds(saved.standaloneTikTokPostUserSleepSeconds, 6) } catch (error) { console.error('load shared config failed:', error) @@ -436,6 +454,7 @@ async function saveSharedConfig() { needTranslate: Boolean(configForm.needTranslate), standaloneTikTokDataPoolSource: configForm.dataPoolSource, standaloneTikTokReplyMessages: replyMessages.value, + standaloneTikTokInitialFullGroupNumber: effectiveInitialFullGroupNumber.value, standaloneTikTokPostUserSleepSeconds: normalizedPostUserSleepSeconds.value } await window.electronAPI.saveRunConfig(nextConfig) @@ -529,10 +548,6 @@ async function handleStart() { ElMessage.warning('请先完成当前配置对应的视图预热,再启动脚本') return } - if (isBrotherInfoMode.value && replyMessages.value.length === 0) { - ElMessage.warning('选择大哥池后,请先填写大哥随机回复话术') - return - } if (!loginConfirmed.value) { ElMessage.warning('请先在当前视图手动登录,并勾选“当前视图已完成手动登录”后再启动脚本') return @@ -594,6 +609,10 @@ watch(() => configForm.replyMessagesText, () => { if (!isBrotherInfoMode.value) return invalidatePreparedState() }) +watch(normalizedGroupViewCounts, () => { + const nextGroupNumber = effectiveInitialFullGroupNumber.value + if (configForm.initialFullGroupNumber !== nextGroupNumber) configForm.initialFullGroupNumber = nextGroupNumber +}) watch(viewIds, (nextViewIds) => { if (nextViewIds.length === 0) { activeViewId.value = TIKTOK_VIEW_IDS[0]