80 lines
1.8 KiB
TypeScript
80 lines
1.8 KiB
TypeScript
import { defineStore } from 'pinia'
|
||
import { computed, ref } from 'vue'
|
||
|
||
export type AiRole = 'user' | 'assistant' | 'system'
|
||
|
||
export interface AiMessage {
|
||
id: string
|
||
role: AiRole
|
||
content: string
|
||
/** 是否仍在流式生成中,用于 UI 展示打字光标 */
|
||
pending?: boolean
|
||
/** 标记本条消息是否出错,UI 显示重试按钮 */
|
||
error?: boolean
|
||
/** 仅 assistant 消息使用:本轮回答结束后由大模型给出的追问建议 */
|
||
recommendQuestions?: string[]
|
||
createdAt: number
|
||
}
|
||
|
||
export const useAiStore = defineStore(
|
||
'ai',
|
||
() => {
|
||
const messages = ref<AiMessage[]>([])
|
||
/** 一组对话共用同一个 thread,跨多轮维持上下文 */
|
||
const threadId = ref('')
|
||
|
||
const messageCount = computed(() => messages.value.length)
|
||
|
||
function ensureThreadId() {
|
||
if (!threadId.value) {
|
||
threadId.value = `thread_id_${Date.now()}${Math.floor(Math.random() * 10000)}`
|
||
}
|
||
return threadId.value
|
||
}
|
||
|
||
function addMessage(message: AiMessage) {
|
||
messages.value.push(message)
|
||
}
|
||
|
||
function updateMessage(id: string, patch: Partial<AiMessage>) {
|
||
const target = messages.value.find(m => m.id === id)
|
||
if (!target) {
|
||
return
|
||
}
|
||
Object.assign(target, patch)
|
||
}
|
||
|
||
function appendDelta(id: string, delta: string) {
|
||
const target = messages.value.find(m => m.id === id)
|
||
if (!target) {
|
||
return
|
||
}
|
||
target.content += delta
|
||
}
|
||
|
||
function removeMessage(id: string) {
|
||
messages.value = messages.value.filter(m => m.id !== id)
|
||
}
|
||
|
||
function clear() {
|
||
messages.value = []
|
||
threadId.value = ''
|
||
}
|
||
|
||
return {
|
||
messages,
|
||
threadId,
|
||
messageCount,
|
||
ensureThreadId,
|
||
addMessage,
|
||
updateMessage,
|
||
appendDelta,
|
||
removeMessage,
|
||
clear,
|
||
}
|
||
},
|
||
{
|
||
persist: true,
|
||
},
|
||
)
|