mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(topics): synchronize progress and highlights
This commit is contained in:
1 parent
73601ebe26
commit
7c81d19d39
7 files changed
+114
-26
No files matched your search
@@ -586,14 +586,15 @@ test('a second runtime cannot recover or control a live topic execution', async
|
||||
now: () => Date.parse('2026-08-09T12:00:00.000Z'),
|
||||
generateId: () => 'second-runtime-run',
|
||||
})
|
||||
secondService = runtimeService
|
||||
|
||||
assert.equal(secondService.getRun('group', run.id)?.status, 'running')
|
||||
assert.equal(runtimeService.getRun('group', run.id)?.status, 'running')
|
||||
assert.throws(
|
||||
() => secondService.pause('group', run.id),
|
||||
() => runtimeService.pause('group', run.id),
|
||||
(error: unknown) => (error as { statusCode?: number }).statusCode === 409
|
||||
)
|
||||
assert.throws(
|
||||
() => secondService.cancel('group', run.id),
|
||||
() => runtimeService.cancel('group', run.id),
|
||||
(error: unknown) => (error as { statusCode?: number }).statusCode === 409
|
||||
)
|
||||
assert.equal(firstService.getRun('group', run.id)?.status, 'running')
|
||||
|
||||
@@ -471,6 +471,7 @@ export function createChatTopicService(deps: ChatTopicServiceDeps): ChatTopicSer
|
||||
}
|
||||
|
||||
signal.throwIfAborted()
|
||||
run = updateRun(run, { currentBlockIndex: null })
|
||||
const finalPrompts = buildTopicFinalizationPrompt({
|
||||
chatType: source.chatType,
|
||||
dayKey: source.dayKey,
|
||||
|
||||
@@ -3,7 +3,13 @@ import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { ChatTopic } from '@openchatlab/shared-types'
|
||||
import { CHAT_TOPIC_COLOR_STYLES, chatTopicColorStyle, type ChatTopicHighlight } from './topic-highlight'
|
||||
import {
|
||||
CHAT_TOPIC_COLOR_STYLES,
|
||||
chatTopicColorStyle,
|
||||
shouldClearChatTopicHighlight,
|
||||
type ChatTopicHighlight,
|
||||
} from './topic-highlight'
|
||||
import { resolveChatTopicProgressDetail } from './topic-progress'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import {
|
||||
useChatTopicsService,
|
||||
@@ -51,25 +57,7 @@ const progressPercent = computed(() => {
|
||||
if (!run.value || run.value.totalBlocks === 0) return 0
|
||||
return Math.min(100, Math.round((run.value.completedBlocks / run.value.totalBlocks) * 100))
|
||||
})
|
||||
const activeProgressDetail = computed(() => {
|
||||
const currentRun = run.value
|
||||
if (
|
||||
!currentRun ||
|
||||
currentRun.status !== 'running' ||
|
||||
currentRun.currentBlockIndex === null ||
|
||||
currentRun.totalBlocks === 0
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const seconds = Math.max(0, Math.floor((Date.now() - currentRun.updatedAt) / 1000))
|
||||
if (currentRun.currentBlockIndex >= currentRun.totalBlocks) {
|
||||
return { key: 'records.topics.progressFinalizing', params: { seconds } }
|
||||
}
|
||||
return {
|
||||
key: 'records.topics.progressBlock',
|
||||
params: { current: currentRun.currentBlockIndex + 1, total: currentRun.totalBlocks, seconds },
|
||||
}
|
||||
})
|
||||
const activeProgressDetail = computed(() => resolveChatTopicProgressDetail(run.value))
|
||||
const todayDayKey = dayjs().format('YYYY-MM-DD')
|
||||
|
||||
const rangeOptions = computed(() => [
|
||||
@@ -133,7 +121,9 @@ async function refreshRun() {
|
||||
const statusChanged = next.status !== run.value.status || next.completedDays !== run.value.completedDays
|
||||
run.value = next
|
||||
if (statusChanged && props.dayKey) {
|
||||
day.value = await useChatTopicsService().getDay(props.sessionId, props.dayKey, timezone)
|
||||
const nextDay = await useChatTopicsService().getDay(props.sessionId, props.dayKey, timezone)
|
||||
if (shouldClearChatTopicHighlight(day.value, nextDay)) clearTopicSelection()
|
||||
day.value = nextDay
|
||||
}
|
||||
schedulePoll()
|
||||
} catch {
|
||||
@@ -211,6 +201,7 @@ async function regenerateDay() {
|
||||
actionLoading.value = true
|
||||
try {
|
||||
run.value = await useChatTopicsService().generateDay(props.sessionId, props.dayKey, timezone, locale.value)
|
||||
clearTopicSelection()
|
||||
schedulePoll()
|
||||
} catch (error) {
|
||||
toast.fail(t('records.topics.startFailed'), { description: errorMessage(error) })
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { isMessageInChatTopicHighlight, type ChatTopicHighlight } from './topic-highlight'
|
||||
import {
|
||||
isMessageInChatTopicHighlight,
|
||||
shouldClearChatTopicHighlight,
|
||||
type ChatTopicHighlight,
|
||||
} from './topic-highlight'
|
||||
|
||||
test('exact topic highlight excludes an interleaved message inside the same time range', () => {
|
||||
const highlight: ChatTopicHighlight = {
|
||||
@@ -25,3 +29,9 @@ test('legacy topic highlight keeps the minute-expanded range fallback', () => {
|
||||
assert.equal(isMessageInChatTopicHighlight(highlight, { id: 1, timestamp: 120 }), true)
|
||||
assert.equal(isMessageInChatTopicHighlight(highlight, { id: 3, timestamp: 180 }), false)
|
||||
})
|
||||
|
||||
test('clears a topic highlight only when the displayed snapshot is replaced', () => {
|
||||
assert.equal(shouldClearChatTopicHighlight({ generatedAt: 100 }, { generatedAt: 100 }), false)
|
||||
assert.equal(shouldClearChatTopicHighlight({ generatedAt: 100 }, { generatedAt: 200 }), true)
|
||||
assert.equal(shouldClearChatTopicHighlight({ generatedAt: 100 }, null), true)
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChatTopicAssignmentMode, ChatTopicTimeRange } from '@openchatlab/shared-types'
|
||||
import type { ChatTopicAssignmentMode, ChatTopicDay, ChatTopicTimeRange } from '@openchatlab/shared-types'
|
||||
|
||||
export interface ChatTopicHighlight {
|
||||
messageIds: number[]
|
||||
@@ -63,6 +63,13 @@ export function isMessageInChatTopicHighlight(
|
||||
})
|
||||
}
|
||||
|
||||
export function shouldClearChatTopicHighlight(
|
||||
currentDay: Pick<ChatTopicDay, 'generatedAt'> | null,
|
||||
nextDay: Pick<ChatTopicDay, 'generatedAt'> | null
|
||||
): boolean {
|
||||
return currentDay !== null && currentDay.generatedAt !== nextDay?.generatedAt
|
||||
}
|
||||
|
||||
function normalizeColorIndex(colorIndex: number): number {
|
||||
const length = CHAT_TOPIC_COLOR_STYLES.length
|
||||
return ((Math.trunc(colorIndex) % length) + length) % length
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { resolveChatTopicProgressDetail } from './topic-progress'
|
||||
|
||||
test('reports run-wide block progress after advancing to a later day', () => {
|
||||
assert.deepEqual(
|
||||
resolveChatTopicProgressDetail(
|
||||
{
|
||||
status: 'running',
|
||||
currentDay: '2026-08-10',
|
||||
currentBlockIndex: 0,
|
||||
completedBlocks: 2,
|
||||
totalBlocks: 4,
|
||||
updatedAt: 10_000,
|
||||
},
|
||||
13_000
|
||||
),
|
||||
{
|
||||
key: 'records.topics.progressBlock',
|
||||
params: { current: 3, total: 4, seconds: 3 },
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('reports daily finalization after all blocks for the current day finish', () => {
|
||||
assert.deepEqual(
|
||||
resolveChatTopicProgressDetail(
|
||||
{
|
||||
status: 'running',
|
||||
currentDay: '2026-08-10',
|
||||
currentBlockIndex: null,
|
||||
completedBlocks: 2,
|
||||
totalBlocks: 4,
|
||||
updatedAt: 10_000,
|
||||
},
|
||||
13_000
|
||||
),
|
||||
{
|
||||
key: 'records.topics.progressFinalizing',
|
||||
params: { seconds: 3 },
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { ChatTopicRun } from '@openchatlab/shared-types'
|
||||
|
||||
export type ChatTopicProgressDetail =
|
||||
| {
|
||||
key: 'records.topics.progressBlock'
|
||||
params: { current: number; total: number; seconds: number }
|
||||
}
|
||||
| {
|
||||
key: 'records.topics.progressFinalizing'
|
||||
params: { seconds: number }
|
||||
}
|
||||
|
||||
export function resolveChatTopicProgressDetail(
|
||||
run: Pick<
|
||||
ChatTopicRun,
|
||||
'status' | 'currentDay' | 'currentBlockIndex' | 'completedBlocks' | 'totalBlocks' | 'updatedAt'
|
||||
> | null,
|
||||
now = Date.now()
|
||||
): ChatTopicProgressDetail | null {
|
||||
if (!run || run.status !== 'running' || run.currentDay === null || run.totalBlocks === 0) return null
|
||||
|
||||
const seconds = Math.max(0, Math.floor((now - run.updatedAt) / 1000))
|
||||
if (run.currentBlockIndex === null) {
|
||||
return { key: 'records.topics.progressFinalizing', params: { seconds } }
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'records.topics.progressBlock',
|
||||
params: {
|
||||
current: Math.min(run.completedBlocks + 1, run.totalBlocks),
|
||||
total: run.totalBlocks,
|
||||
seconds,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user