fix: stabilize background operations and large embedding results

This commit is contained in:
2026-09-06 16:26:17 +08:00
parent 874e916106
commit 3b9490e3fb
73 changed files with 3847 additions and 149 deletions
+15 -2
View File
@@ -58,9 +58,22 @@ export const useAgentStore = defineStore('agent', () => {
tools.value = await agentService.listTools()
}
let listVersion = 0
async function loadRuns() {
const resp = await agentService.listAgentRuns()
runs.value = resp.items
const version = ++listVersion
const items: AgentRun[] = []
let offset = 0
do {
const resp = await agentService.listAgentRuns({ limit: 100, offset })
if (version !== listVersion) return
items.push(...resp.items)
offset += resp.items.length
if (!resp.items.length || offset >= resp.total) break
} while (true)
const active = runs.value.find(run => run.run_id === activeRunId.value)
const merged = new Map(items.map(item => [item.run_id, item]))
if (active && !merged.has(active.run_id)) merged.set(active.run_id, active)
runs.value = [...merged.values()]
}
async function loadRun(runId: string) {
@@ -0,0 +1,29 @@
// @vitest-environment happy-dom
import { beforeEach, expect, it, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { useTaskStore } from './task'
import { useAgentStore } from './agent'
const mocks = vi.hoisted(() => ({ tasks: vi.fn(), runs: vi.fn() }))
vi.mock('@/services/taskService', () => ({ listTasks: mocks.tasks }))
vi.mock('@/services/agentService', () => ({ listAgentRuns: mocks.runs }))
beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks() })
it('loads tasks past the first page before applying global status filters', async () => {
const tasks = Array.from({ length: 251 }, (_, i) => ({ task_id: `t${i}`, status: i >= 200 ? 'done' : 'todo' }))
mocks.tasks.mockImplementation(async ({ offset, limit }) => ({ items: tasks.slice(offset, offset + limit), total: tasks.length }))
const store = useTaskStore()
await store.loadTasks()
expect(store.tasks).toHaveLength(251)
store.setFilterStatus('done')
expect(store.filteredTasks).toHaveLength(51)
expect(mocks.tasks).toHaveBeenCalledTimes(3)
})
it('includes older Agent history and leaves failed refresh data intact', async () => {
const runs = Array.from({ length: 201 }, (_, i) => ({ run_id: `r${i}`, status: 'completed' }))
mocks.runs.mockImplementation(async ({ offset, limit }) => ({ items: runs.slice(offset, offset + limit), total: runs.length }))
const store = useAgentStore()
await store.loadRuns()
expect(store.runs).toHaveLength(201)
mocks.runs.mockRejectedValue(new Error('offline'))
await expect(store.loadRuns()).rejects.toThrow('offline')
expect(store.runs).toHaveLength(201)
})
+11 -1
View File
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import type { AiCoreStatus, IndexStatus } from '@/contracts'
import { resolveApiUrl } from '@/services/apiClient'
import packageInfo from '../../package.json'
@@ -31,6 +31,15 @@ export const useSettingsStore = defineStore('settings', () => {
// Index
const emptyIndex = (): IndexStatus => ({ status: 'unknown', pending_jobs: 0, total_notes: null, total_blocks: null })
const indexStatus = ref<IndexStatus>(emptyIndex())
const indexStatusLabel = computed(() => {
const value = indexStatus.value
if (value.status === 'unknown') return t('索引状态未获取', 'Index status unavailable')
if (value.status === 'indexing') return t('后台计算索引', 'Indexing in background')
if (value.status === 'error') return t('索引错误', 'Index error')
if (value.vector_refresh_required) return t('全文可用 · 向量待重建', 'Full text ready · vectors need rebuilding')
if (value.active_searches) return t('向量检索中', 'Vector search running')
return t('索引就绪', 'Index ready')
})
// Permissions
const permissionPolicy = ref<Record<string, 'allow' | 'confirm' | 'deny'>>({})
@@ -85,6 +94,7 @@ export const useSettingsStore = defineStore('settings', () => {
aiCoreStatus,
aiCoreAddress,
indexStatus,
indexStatusLabel,
permissionPolicy,
diagnosticsError,
loadDiagnostics,
+14 -3
View File
@@ -25,16 +25,27 @@ export const useTaskStore = defineStore('task', () => {
const inProgressTasks = computed(() => tasks.value.filter((t) => t.status === 'in_progress'))
const doneTasks = computed(() => tasks.value.filter((t) => t.status === 'done'))
let loadVersion = 0
async function loadTasks() {
const version = ++loadVersion
isLoading.value = true
try {
const resp = await listTasks()
tasks.value = resp.items
const items: TaskItem[] = []
let offset = 0
do {
const resp = await listTasks({ limit: 100, offset })
if (version !== loadVersion) return
items.push(...resp.items)
offset += resp.items.length
if (!resp.items.length || offset >= resp.total) break
} while (true)
tasks.value = [...new Map(items.map(item => [item.task_id, item])).values()]
error.value = null
} catch (reason) {
if (version !== loadVersion) return
error.value = reason instanceof Error ? reason.message : t('任务加载失败', 'Failed to load tasks')
} finally {
isLoading.value = false
if (version === loadVersion) isLoading.value = false
}
}