fix(frontend): 移除运行时演示数据并接入真实后端状态
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { AgentRun, AgentEvent, ToolDefinition, PermissionRequest, ToolCall } from '@/contracts'
|
||||
import { mockAgentRuns, mockAgentEvents, mockTools, mockPermissionRequest } from '@/services/agentService'
|
||||
import * as agentService from '@/services/agentService'
|
||||
import type { SseClient } from '@/services/sseClient'
|
||||
|
||||
export const useAgentStore = defineStore('agent', () => {
|
||||
const runs = ref<AgentRun[]>(mockAgentRuns)
|
||||
const activeRunId = ref<string | null>('run-1')
|
||||
const events = ref<AgentEvent[]>(mockAgentEvents.filter((e) => e.run_id === 'run-1'))
|
||||
const tools = ref<ToolDefinition[]>(mockTools)
|
||||
const runs = ref<AgentRun[]>([])
|
||||
const activeRunId = ref<string | null>(null)
|
||||
const events = ref<AgentEvent[]>([])
|
||||
const tools = ref<ToolDefinition[]>([])
|
||||
const isCreating = ref(false)
|
||||
const isRunning = ref(false)
|
||||
const permissionRequest = ref<PermissionRequest | null>(null)
|
||||
const toolCalls = ref<ToolCall[]>([])
|
||||
const error = ref<string | null>(null)
|
||||
let eventStream: SseClient | null = null
|
||||
let selectionVersion = 0
|
||||
|
||||
const activeRun = computed(() =>
|
||||
runs.value.find((r) => r.run_id === activeRunId.value) || null
|
||||
@@ -40,9 +40,15 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
}
|
||||
|
||||
async function loadRun(runId: string) {
|
||||
const version = ++selectionVersion
|
||||
eventStream?.cancel()
|
||||
activeRunId.value = runId
|
||||
events.value = []
|
||||
toolCalls.value = []
|
||||
permissionRequest.value = null
|
||||
isRunning.value = false
|
||||
const run = await agentService.getAgentRun(runId)
|
||||
if (version !== selectionVersion) return
|
||||
const existingIndex = runs.value.findIndex((item) => item.run_id === runId)
|
||||
if (existingIndex >= 0) runs.value[existingIndex] = run
|
||||
else runs.value.unshift(run)
|
||||
@@ -106,9 +112,9 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
isRunning.value = true
|
||||
error.value = null
|
||||
eventStream = agentService.streamAgentEvents(runId, {
|
||||
onEvent: processEvent,
|
||||
onError(streamError) { error.value = streamError.message; isRunning.value = false },
|
||||
onDone() { isRunning.value = false; eventStream = null },
|
||||
onEvent(event) { if (activeRunId.value === runId) processEvent(event) },
|
||||
onError(streamError) { if (activeRunId.value === runId) { error.value = streamError.message; isRunning.value = false } },
|
||||
onDone() { if (activeRunId.value === runId) { isRunning.value = false; eventStream = null } },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,6 +122,7 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
isCreating.value = true
|
||||
try {
|
||||
const run = await agentService.createAgentRun(request)
|
||||
selectionVersion++
|
||||
runs.value.unshift(run)
|
||||
activeRunId.value = run.run_id
|
||||
events.value = []
|
||||
@@ -143,10 +150,6 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
permissionRequest.value = null
|
||||
}
|
||||
|
||||
function showPermissionDemo() {
|
||||
permissionRequest.value = mockPermissionRequest
|
||||
}
|
||||
|
||||
return {
|
||||
runs,
|
||||
activeRunId,
|
||||
@@ -166,6 +169,5 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
createRun,
|
||||
cancelRun,
|
||||
respondPermission,
|
||||
showPermissionDemo,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { beforeEach, expect, it, vi } from 'vitest'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useChatStore } from './chat'
|
||||
import { streamChat } from '@/services/chatService'
|
||||
import type { SseClient } from '@/services/sseClient'
|
||||
|
||||
vi.mock('@/services/chatService', () => ({ streamChat: vi.fn() }))
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
vi.mocked(streamChat).mockReset().mockReturnValue({ cancel: vi.fn() } as unknown as SseClient)
|
||||
})
|
||||
|
||||
it('sends real user history, applies streaming changes, and restores it when switching conversations', async () => {
|
||||
const store = useChatStore()
|
||||
store.selectedProviderId = 'real'
|
||||
store.selectedModel = 'configured-model'
|
||||
await store.sendMessage('user input')
|
||||
const [request, handlers] = vi.mocked(streamChat).mock.calls[0]!
|
||||
expect(request.messages).toEqual([{ role: 'user', content: 'user input' }])
|
||||
handlers.onEvent?.({ event: 'TextDelta', sequence: 0, timestamp: '', data: { text: 'real response' } })
|
||||
expect(store.messages[1]?.content).toBe('real response')
|
||||
handlers.onDone?.()
|
||||
const id = store.activeConversationId!
|
||||
store.createNewConversation()
|
||||
expect(store.messages).toEqual([])
|
||||
await store.setActiveConversation(id)
|
||||
expect(store.messages.map(m => m.content)).toEqual(['user input', 'real response'])
|
||||
})
|
||||
|
||||
it('does not send without a provider and ignores late callbacks from a cancelled conversation', async () => {
|
||||
const store = useChatStore()
|
||||
await store.sendMessage('no provider')
|
||||
expect(streamChat).not.toHaveBeenCalled()
|
||||
store.selectedProviderId = 'real'
|
||||
store.selectedModel = 'configured-model'
|
||||
await store.sendMessage('first')
|
||||
const old = vi.mocked(streamChat).mock.calls[0]![1]
|
||||
store.createNewConversation()
|
||||
await store.sendMessage('second')
|
||||
old.onDone?.()
|
||||
expect(store.isStreaming).toBe(true)
|
||||
expect(store.messages[0]?.content).toBe('second')
|
||||
})
|
||||
+43
-21
@@ -1,22 +1,24 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import type { ChatMessage, Conversation } from '@/contracts'
|
||||
import { mockConversations, mockMessages, streamChat } from '@/services/chatService'
|
||||
import { streamChat } from '@/services/chatService'
|
||||
import type { SseClient } from '@/services/sseClient'
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const conversations = ref<Conversation[]>(mockConversations)
|
||||
const activeConversationId = ref<string | null>('conv-1')
|
||||
const messages = ref<ChatMessage[]>(mockMessages['conv-1'] || [])
|
||||
const conversations = ref<Conversation[]>([])
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
const messages = ref<ChatMessage[]>([])
|
||||
const isStreaming = ref(false)
|
||||
const inputText = ref('')
|
||||
const useRag = ref(true)
|
||||
const useRag = ref(false)
|
||||
const selectedSkillId = ref<string | null>(null)
|
||||
const selectedProviderId = ref('mock')
|
||||
const selectedModel = ref('mock-1')
|
||||
const selectedProviderId = ref('')
|
||||
const selectedModel = ref('')
|
||||
let sseClient: SseClient | null = null
|
||||
let streamVersion = 0
|
||||
|
||||
// TODO(chat): 会话持久化接口完成后移除 mockConversations/mockMessages 数据源。
|
||||
// User-created conversations live in this browser session; no fabricated history.
|
||||
const history = reactive<Record<string, ChatMessage[]>>({})
|
||||
|
||||
const activeConversation = computed(() =>
|
||||
conversations.value.find((c) => c.conversation_id === activeConversationId.value) || null
|
||||
@@ -27,13 +29,14 @@ export const useChatStore = defineStore('chat', () => {
|
||||
)
|
||||
|
||||
async function setActiveConversation(id: string) {
|
||||
stopGeneration()
|
||||
activeConversationId.value = id
|
||||
messages.value = mockMessages[id] || []
|
||||
messages.value = history[id] ?? []
|
||||
}
|
||||
|
||||
async function sendMessage(text: string) {
|
||||
if (!text.trim() || isStreaming.value) return
|
||||
const conversationId = activeConversationId.value || `conv-${Date.now()}`
|
||||
if (!text.trim() || isStreaming.value || !selectedProviderId.value || !selectedModel.value) return
|
||||
const conversationId = activeConversationId.value || crypto.randomUUID()
|
||||
|
||||
if (!activeConversationId.value) {
|
||||
const newConv: Conversation = {
|
||||
@@ -47,8 +50,10 @@ export const useChatStore = defineStore('chat', () => {
|
||||
activeConversationId.value = conversationId
|
||||
}
|
||||
|
||||
history[conversationId] = messages.value
|
||||
const conversationMessages = messages.value
|
||||
const userMsg: ChatMessage = {
|
||||
message_id: `msg-${Date.now()}`,
|
||||
message_id: crypto.randomUUID(),
|
||||
conversation_id: conversationId,
|
||||
role: 'user',
|
||||
content: text,
|
||||
@@ -57,29 +62,34 @@ export const useChatStore = defineStore('chat', () => {
|
||||
messages.value.push(userMsg)
|
||||
inputText.value = ''
|
||||
isStreaming.value = true
|
||||
const conversation = conversations.value.find(c => c.conversation_id === conversationId)
|
||||
if (conversation) { conversation.updated_at = new Date().toISOString(); conversation.message_count = messages.value.length }
|
||||
|
||||
// 先插入占位消息,随后将 SSE 增量原位合并,避免每个 token 重建消息列表。
|
||||
const aiMsg: ChatMessage = {
|
||||
message_id: `msg-${Date.now() + 1}`,
|
||||
const aiMsg = reactive<ChatMessage>({
|
||||
message_id: crypto.randomUUID(),
|
||||
conversation_id: conversationId,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
created_at: new Date().toISOString(),
|
||||
citations: [],
|
||||
tool_calls: [],
|
||||
}
|
||||
})
|
||||
messages.value.push(aiMsg)
|
||||
|
||||
const version = ++streamVersion
|
||||
const argumentBuffers = new Map<string, string>()
|
||||
sseClient = streamChat({
|
||||
provider_id: selectedProviderId.value,
|
||||
model: selectedModel.value,
|
||||
conversation_id: conversationId,
|
||||
use_rag: useRag.value,
|
||||
messages: messages.value
|
||||
.filter((message) => message !== aiMsg)
|
||||
.filter((message) => message.message_id !== aiMsg.message_id)
|
||||
.map((message) => ({ role: message.role, content: message.content })),
|
||||
}, {
|
||||
onEvent(event) {
|
||||
if (version !== streamVersion) return
|
||||
if (event.event === 'TextDelta') aiMsg.content += String(event.data.text ?? '')
|
||||
if (event.event === 'ThinkingDelta') aiMsg.thinking = `${aiMsg.thinking ?? ''}${String(event.data.text ?? '')}`
|
||||
if (event.event === 'ToolCallStart') {
|
||||
@@ -92,6 +102,11 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
if (event.event === 'ToolCallDelta') {
|
||||
const call = aiMsg.tool_calls?.find((item) => item.tool_call_id === event.data.tool_call_id)
|
||||
if (call && typeof event.data.arguments_delta === 'string') {
|
||||
const buffer = (argumentBuffers.get(call.tool_call_id) ?? '') + event.data.arguments_delta
|
||||
argumentBuffers.set(call.tool_call_id, buffer)
|
||||
try { call.parameters = JSON.parse(buffer) } catch { /* incomplete JSON fragment */ }
|
||||
}
|
||||
if (call && event.data.arguments && typeof event.data.arguments === 'object') {
|
||||
Object.assign(call.parameters, event.data.arguments)
|
||||
}
|
||||
@@ -116,14 +131,16 @@ export const useChatStore = defineStore('chat', () => {
|
||||
if (event.event === 'Error') aiMsg.content += `\n\n生成失败:${String(event.data.message ?? '未知错误')}`
|
||||
},
|
||||
onError(error) {
|
||||
if (version !== streamVersion) return
|
||||
aiMsg.content += `\n\n连接失败:${error.message}`
|
||||
isStreaming.value = false
|
||||
sseClient = null
|
||||
},
|
||||
onDone() {
|
||||
if (version !== streamVersion) return
|
||||
const conversation = conversations.value.find((item) => item.conversation_id === conversationId)
|
||||
if (conversation) {
|
||||
conversation.message_count = messages.value.length
|
||||
conversation.message_count = conversationMessages.length
|
||||
conversation.updated_at = new Date().toISOString()
|
||||
}
|
||||
isStreaming.value = false
|
||||
@@ -133,6 +150,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
function stopGeneration() {
|
||||
streamVersion++
|
||||
if (sseClient) {
|
||||
sseClient.cancel()
|
||||
sseClient = null
|
||||
@@ -141,8 +159,9 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
function createNewConversation() {
|
||||
stopGeneration()
|
||||
const newConv: Conversation = {
|
||||
conversation_id: `conv-${Date.now()}`,
|
||||
conversation_id: crypto.randomUUID(),
|
||||
title: '新对话',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
@@ -150,16 +169,19 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
conversations.value.unshift(newConv)
|
||||
activeConversationId.value = newConv.conversation_id
|
||||
messages.value = []
|
||||
history[newConv.conversation_id] = []
|
||||
messages.value = history[newConv.conversation_id]
|
||||
}
|
||||
|
||||
function deleteConversation(id: string) {
|
||||
if (activeConversationId.value === id) stopGeneration()
|
||||
delete history[id]
|
||||
const idx = conversations.value.findIndex((c) => c.conversation_id === id)
|
||||
if (idx > -1) {
|
||||
conversations.value.splice(idx, 1)
|
||||
if (activeConversationId.value === id) {
|
||||
activeConversationId.value = conversations.value[0]?.conversation_id || null
|
||||
messages.value = conversations.value[0] ? mockMessages[conversations.value[0].conversation_id] || [] : []
|
||||
messages.value = conversations.value[0] ? history[conversations.value[0].conversation_id] || [] : []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useAgentStore } from './agent'
|
||||
import { useChatStore } from './chat'
|
||||
import { useTaskStore } from './task'
|
||||
import { usePluginStore } from './plugin'
|
||||
import { useSkillStore } from './skill'
|
||||
import { useProviderStore } from './provider'
|
||||
import { useSettingsStore } from './settings'
|
||||
import { listProviders } from '@/services/providerService'
|
||||
import { getStatus } from '@/services/systemService'
|
||||
|
||||
beforeEach(() => { setActivePinia(createPinia()); localStorage.clear() })
|
||||
afterEach(() => vi.unstubAllGlobals())
|
||||
|
||||
describe('runtime data sources', () => {
|
||||
it('starts with no fabricated domain records or healthy diagnostics', () => {
|
||||
expect(useAgentStore().runs).toEqual([])
|
||||
expect(useAgentStore().events).toEqual([])
|
||||
expect(useAgentStore().tools).toEqual([])
|
||||
expect(useAgentStore().permissionRequest).toBeNull()
|
||||
expect(useChatStore().conversations).toEqual([])
|
||||
expect(useChatStore().messages).toEqual([])
|
||||
expect(useTaskStore().tasks).toEqual([])
|
||||
expect(usePluginStore().plugins).toEqual([])
|
||||
expect(useSkillStore().skills).toEqual([])
|
||||
expect(useProviderStore().providers).toEqual([])
|
||||
expect(useProviderStore().defaultProviderId).toBe('')
|
||||
expect(useSettingsStore().aiCoreStatus).toBe('unknown')
|
||||
expect(useSettingsStore().indexStatus.total_notes).toBeNull()
|
||||
expect(useSettingsStore().permissionPolicy).toEqual({})
|
||||
})
|
||||
|
||||
it('keeps initial collections empty and exposes errors when the API is offline', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline')))
|
||||
const stores = [useTaskStore(), usePluginStore(), useSkillStore(), useProviderStore()] as const
|
||||
await Promise.all([stores[0].loadTasks(), stores[1].loadPlugins(), stores[2].loadSkills(), stores[3].loadProviders()])
|
||||
expect(stores.every(store => store.error)).toBe(true)
|
||||
await useSettingsStore().loadDiagnostics()
|
||||
expect(useSettingsStore().aiCoreStatus).toBe('error')
|
||||
expect(useSettingsStore().indexStatus.total_blocks).toBeNull()
|
||||
expect(useSettingsStore().diagnosticsError).toBeTruthy()
|
||||
await expect(getStatus()).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('renders backend counts and effective permissions and excludes the test provider', async () => {
|
||||
const data: Record<string, unknown> = {
|
||||
'/health': { status: 'ok' }, '/api/status': { version: '9.2.1' },
|
||||
'/api/index/status': { status: 'idle', pending_jobs: 0, total_notes: 7, total_blocks: 19 },
|
||||
'/api/permissions/policy': { 'attachments.read': 'allow' },
|
||||
'/api/providers': { items: [
|
||||
{ provider_id: 'mock', provider_type: 'mock', capabilities: [] },
|
||||
{ provider_id: 'real', name: 'Real', provider_type: 'ollama', capabilities: [], enabled: true, default_model: 'installed-model' },
|
||||
] },
|
||||
}
|
||||
vi.stubGlobal('fetch', vi.fn(async (url: string) => new Response(JSON.stringify(data[url]), { status: 200, headers: { "content-type": "application/json" } })))
|
||||
expect((await listProviders()).map(p => p.provider_id)).toEqual(['real'])
|
||||
await useProviderStore().loadProviders()
|
||||
expect(useProviderStore().defaultProviderId).toBe('real')
|
||||
await useSettingsStore().loadDiagnostics()
|
||||
expect(useSettingsStore().indexStatus.total_notes).toBe(7)
|
||||
expect(useSettingsStore().indexStatus.total_blocks).toBe(19)
|
||||
expect(useSettingsStore().aiCoreVersion).toBe('9.2.1')
|
||||
expect(useSettingsStore().permissionPolicy).toEqual({ 'attachments.read': 'allow' })
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import type { Plugin } from '@/contracts'
|
||||
import * as pluginService from '@/services/pluginService'
|
||||
|
||||
export const usePluginStore = defineStore('plugin', () => {
|
||||
const plugins = ref<Plugin[]>(pluginService.mockPlugins)
|
||||
const plugins = ref<Plugin[]>([])
|
||||
const selectedPluginId = ref<string | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -4,8 +4,6 @@ import { createPinia, setActivePinia } from 'pinia'
|
||||
import type { ProviderConfig, ProviderPreset } from '@/contracts'
|
||||
|
||||
vi.mock('@/services/providerService', () => ({
|
||||
mockProviders: [],
|
||||
mockModels: {},
|
||||
listProviders: vi.fn(),
|
||||
listProviderPresets: vi.fn(),
|
||||
getCredentialStatus: vi.fn(),
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { ProviderConfig, ModelInfo, ProviderPreset } from '@/contracts'
|
||||
import { createProvider, deleteProvider as deleteProviderRequest, getCredentialStatus, listModels, listProviderPresets, listProviders, mockProviders, mockModels, putCredential, testProvider as testProviderRequest, updateProvider as updateProviderRequest } from '@/services/providerService'
|
||||
import { createProvider, deleteProvider as deleteProviderRequest, getCredentialStatus, listModels, listProviderPresets, listProviders, putCredential, testProvider as testProviderRequest, updateProvider as updateProviderRequest } from '@/services/providerService'
|
||||
import { ApiErrorClass } from '@/services/apiClient'
|
||||
|
||||
export const useProviderStore = defineStore('provider', () => {
|
||||
const providers = ref<ProviderConfig[]>(mockProviders)
|
||||
const providers = ref<ProviderConfig[]>([])
|
||||
const presets = ref<ProviderPreset[]>([])
|
||||
const modelsByProvider = ref<Record<string, ModelInfo[]>>(mockModels)
|
||||
const modelsByProvider = ref<Record<string, ModelInfo[]>>({})
|
||||
const modelLoadingByProvider = ref<Record<string, boolean>>({})
|
||||
const modelErrorsByProvider = ref<Record<string, string>>({})
|
||||
const credentialConfiguredById = ref<Record<string, boolean>>({})
|
||||
const defaultProviderId = ref('mock')
|
||||
const defaultProviderId = ref('')
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -24,6 +24,9 @@ export const useProviderStore = defineStore('provider', () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
providers.value = await listProviders()
|
||||
if (!enabledProviders.value.some(p => p.provider_id === defaultProviderId.value)) {
|
||||
defaultProviderId.value = enabledProviders.value[0]?.provider_id ?? ''
|
||||
}
|
||||
error.value = null
|
||||
} catch (reason) {
|
||||
error.value = reason instanceof Error ? reason.message : 'Provider 加载失败'
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
import type { AiCoreStatus, IndexStatus } from '@/contracts'
|
||||
import { mockIndexStatus } from '@/services/indexService'
|
||||
import { resolveApiUrl } from '@/services/apiClient'
|
||||
import packageInfo from '../../package.json'
|
||||
import * as indexService from '@/services/indexService'
|
||||
import * as systemService from '@/services/systemService'
|
||||
|
||||
@@ -14,8 +15,8 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
const restoreLastVault = ref(saved.restoreLastVault !== false)
|
||||
const autoSaveInterval = ref(typeof saved.autoSaveInterval === 'number' ? saved.autoSaveInterval : 1500)
|
||||
const language = ref<'zh-CN' | 'en'>(saved.language === 'en' ? 'en' : 'zh-CN')
|
||||
const appVersion = ref('0.1.0')
|
||||
const aiCoreVersion = ref('0.1.0')
|
||||
const appVersion = ref(packageInfo.version)
|
||||
const aiCoreVersion = ref('未获取')
|
||||
|
||||
// Editor
|
||||
const defaultEditorMode = ref<'wysiwyg' | 'source'>(saved.defaultEditorMode === 'source' ? 'source' : 'wysiwyg')
|
||||
@@ -23,24 +24,15 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
const spellCheck = ref(saved.spellCheck === true)
|
||||
|
||||
// AI Core
|
||||
const aiCoreStatus = ref<AiCoreStatus>('running')
|
||||
const aiCoreAddress = ref('http://127.0.0.1:8000')
|
||||
const aiCoreStatus = ref<AiCoreStatus>('unknown')
|
||||
const aiCoreAddress = ref(resolveApiUrl('/api') || '/api')
|
||||
|
||||
// Index
|
||||
const indexStatus = ref<IndexStatus>(mockIndexStatus)
|
||||
const emptyIndex = (): IndexStatus => ({ status: 'unknown', pending_jobs: 0, total_notes: null, total_blocks: null })
|
||||
const indexStatus = ref<IndexStatus>(emptyIndex())
|
||||
|
||||
// Permissions
|
||||
const permissionPolicy = ref<Record<string, 'allow' | 'confirm' | 'deny'>>({
|
||||
'notes.read': 'allow',
|
||||
'notes.search': 'allow',
|
||||
'notes.write': 'confirm',
|
||||
'notes.delete': 'confirm',
|
||||
'tasks.read': 'allow',
|
||||
'tasks.write': 'confirm',
|
||||
'attachments.read': 'confirm',
|
||||
'network.request': 'confirm',
|
||||
'secrets.use': 'confirm',
|
||||
})
|
||||
const permissionPolicy = ref<Record<string, 'allow' | 'confirm' | 'deny'>>({})
|
||||
const diagnosticsError = ref<string | null>(null)
|
||||
|
||||
watch(() => ({
|
||||
@@ -50,18 +42,15 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
}), (value) => localStorage.setItem('app-settings', JSON.stringify(value)), { deep: true })
|
||||
|
||||
async function loadDiagnostics() {
|
||||
try {
|
||||
const [health, status, index] = await Promise.all([
|
||||
systemService.healthCheck(), systemService.getStatus(), indexService.getIndexStatus(),
|
||||
])
|
||||
aiCoreStatus.value = health.status === 'ok' ? 'running' : 'error'
|
||||
aiCoreVersion.value = status.version
|
||||
indexStatus.value = index
|
||||
diagnosticsError.value = null
|
||||
} catch (reason) {
|
||||
aiCoreStatus.value = 'error'
|
||||
diagnosticsError.value = reason instanceof Error ? reason.message : '诊断信息加载失败'
|
||||
}
|
||||
const results = await Promise.allSettled([
|
||||
systemService.healthCheck(), systemService.getStatus(), indexService.getIndexStatus(), systemService.getPermissionPolicy(),
|
||||
])
|
||||
const [health, status, index, policy] = results
|
||||
aiCoreStatus.value = health.status === 'fulfilled' && health.value.status === 'ok' ? 'running' : 'error'
|
||||
aiCoreVersion.value = status.status === 'fulfilled' ? status.value.version : '未获取'
|
||||
indexStatus.value = index.status === 'fulfilled' ? index.value : emptyIndex()
|
||||
permissionPolicy.value = policy.status === 'fulfilled' ? policy.value : {}
|
||||
diagnosticsError.value = results.filter(item => item.status === 'rejected').map(item => item.reason instanceof Error ? item.reason.message : '后端请求失败').join(';') || null
|
||||
}
|
||||
|
||||
function setAutoSaveInterval(ms: number) {
|
||||
@@ -72,21 +61,6 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
defaultEditorMode.value = mode
|
||||
}
|
||||
|
||||
function setPermission(permission: string, policy: 'allow' | 'confirm' | 'deny') {
|
||||
permissionPolicy.value[permission] = policy
|
||||
}
|
||||
|
||||
function setAiCoreStatus(status: AiCoreStatus) {
|
||||
aiCoreStatus.value = status
|
||||
}
|
||||
|
||||
async function restartAiCore(): Promise<boolean> {
|
||||
aiCoreStatus.value = 'starting'
|
||||
await new Promise((r) => setTimeout(r, 1500))
|
||||
aiCoreStatus.value = 'running'
|
||||
return true
|
||||
}
|
||||
|
||||
async function rebuildIndex(scope: 'full' | 'fts' | 'vector' = 'full') {
|
||||
indexStatus.value.status = 'indexing'
|
||||
try {
|
||||
@@ -115,9 +89,6 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
loadDiagnostics,
|
||||
setAutoSaveInterval,
|
||||
setDefaultEditorMode,
|
||||
setPermission,
|
||||
setAiCoreStatus,
|
||||
restartAiCore,
|
||||
rebuildIndex,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Skill } from '@/contracts'
|
||||
import * as skillService from '@/services/skillService'
|
||||
|
||||
export const useSkillStore = defineStore('skill', () => {
|
||||
const skills = ref<Skill[]>(skillService.mockSkills)
|
||||
const skills = ref<Skill[]>([])
|
||||
const selectedSkillId = ref<string | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { TaskItem, TaskStatus, TaskPriority, TaskSource } from '@/contracts'
|
||||
import { createTask as createTaskRequest, deleteTask as deleteTaskRequest, listTasks, mockTasks, updateTask as updateTaskRequest } from '@/services/taskService'
|
||||
import { createTask as createTaskRequest, deleteTask as deleteTaskRequest, listTasks, updateTask as updateTaskRequest } from '@/services/taskService'
|
||||
|
||||
export const useTaskStore = defineStore('task', () => {
|
||||
const tasks = ref<TaskItem[]>(mockTasks)
|
||||
const tasks = ref<TaskItem[]>([])
|
||||
const filterStatus = ref<TaskStatus | 'all'>('all')
|
||||
const filterPriority = ref<TaskPriority | 'all'>('all')
|
||||
const filterSource = ref<TaskSource | 'all'>('all')
|
||||
@@ -47,7 +47,7 @@ export const useTaskStore = defineStore('task', () => {
|
||||
const task = tasks.value.find((t) => t.task_id === taskId)
|
||||
if (task) {
|
||||
const updated = await updateTaskRequest(taskId, data)
|
||||
Object.assign(task, updated, data)
|
||||
Object.assign(task, updated)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user