feat: 多功能升级 — 流式逐字渲染、对话缓存、会话组织优化、记忆管理修复、性能仪表盘
- 前端消息流式逐字渲染 (AI-Core ChatStream → SSE → Gateway → WebSocket stream_chunk → fadeInUp + cursorBlink) - 后端对话缓存 (conversationCache sync.Map, GET /sessions/:id/messages) - 前端侧边栏历史多轮对话显示 - DevTools 性能监控图标移至首页仪表盘 - DevTools 用户记忆查询/删减功能修复 (补全 DELETE 数据链路) - 后端和 DevTools 按用户分类组织实时活动会话 (map[userID]map[sessionID]*Client) - 新增 docs/api-reference/ 路由参考文档 - 新增 docs/message-flow-architecture.md 消息链路架构文档
This commit is contained in:
@@ -137,6 +137,10 @@ export async function deleteSession(id: string) {
|
||||
return request(`/sessions/${id}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function fetchSessionMessages(id: string) {
|
||||
return request(`/sessions/${id}/messages`);
|
||||
}
|
||||
|
||||
// ========== 记忆API ==========
|
||||
|
||||
export async function searchMemory(query: string) {
|
||||
|
||||
@@ -4,4 +4,5 @@ export {
|
||||
listSessions,
|
||||
getSession,
|
||||
deleteSession,
|
||||
fetchSessionMessages,
|
||||
} from './client';
|
||||
|
||||
@@ -30,17 +30,19 @@ export function MessageBubble({ role, content, timestamp, isStreaming }: Message
|
||||
? 'bg-pink-400 text-white rounded-br-md'
|
||||
: 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 rounded-bl-md border border-pink-100 dark:border-pink-900'
|
||||
}
|
||||
${isStreaming ? 'animate-pulse' : ''}
|
||||
${isStreaming ? 'message-streaming' : ''}
|
||||
`}
|
||||
>
|
||||
<p className="whitespace-pre-wrap break-words">{content}</p>
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
isUser ? 'text-pink-100' : 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{time}
|
||||
</p>
|
||||
{!isStreaming && (
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
isUser ? 'text-pink-100' : 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{time}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 用户头像占位 */}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function MessageList({ messages, isTyping }: MessageListProps) {
|
||||
isStreaming={msg.isStreaming}
|
||||
/>
|
||||
))}
|
||||
{isTyping && <TypingIndicator />}
|
||||
{isTyping && !messages.some((m) => m.isStreaming) && <TypingIndicator />}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useSession } from '@/hooks/useSession';
|
||||
import { useSessionStore } from '@/store/sessionStore';
|
||||
import { useEffect } from 'react';
|
||||
import { CyreneAvatar } from '@/components/persona/CyreneAvatar';
|
||||
|
||||
interface SidebarProps {
|
||||
@@ -8,20 +7,51 @@ interface SidebarProps {
|
||||
}
|
||||
|
||||
export function Sidebar({ onClose }: SidebarProps) {
|
||||
const { sessions, currentSessionId, loadSessions, createSession, deleteSession, setCurrentSession } = useSession();
|
||||
const {
|
||||
sessions,
|
||||
currentSessionId,
|
||||
createSession,
|
||||
deleteSession,
|
||||
setCurrentSession,
|
||||
} = useSession();
|
||||
const storeSessions = useSessionStore((s) => s.sessions);
|
||||
|
||||
useEffect(() => {
|
||||
loadSessions();
|
||||
}, [loadSessions]);
|
||||
const storeCurrentSessionId = useSessionStore((s) => s.currentSessionId);
|
||||
|
||||
const displaySessions = sessions.length > 0 ? sessions : storeSessions;
|
||||
const activeSessionId = currentSessionId || storeCurrentSessionId;
|
||||
|
||||
const handleNewChat = async () => {
|
||||
const session = await createSession();
|
||||
if (session && onClose) onClose();
|
||||
};
|
||||
|
||||
const handleSelectSession = (id: string) => {
|
||||
setCurrentSession(id);
|
||||
if (onClose) onClose();
|
||||
};
|
||||
|
||||
const handleDeleteSession = (e: { stopPropagation: () => void }, id: string) => {
|
||||
e.stopPropagation();
|
||||
deleteSession(id);
|
||||
};
|
||||
|
||||
/** 格式化时间戳为可读字符串 */
|
||||
const formatTime = (ts: string | number): string => {
|
||||
if (!ts) return '';
|
||||
const date = new Date(typeof ts === 'string' ? parseInt(ts, 10) : ts);
|
||||
if (isNaN(date.getTime())) return '';
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMin = Math.floor(diffMs / 60000);
|
||||
if (diffMin < 1) return '刚刚';
|
||||
if (diffMin < 60) return `${diffMin}分钟前`;
|
||||
const diffHour = Math.floor(diffMin / 60);
|
||||
if (diffHour < 24) return `${diffHour}小时前`;
|
||||
const diffDay = Math.floor(diffHour / 24);
|
||||
if (diffDay < 7) return `${diffDay}天前`;
|
||||
return date.toLocaleDateString('zh-CN');
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="h-full bg-white/90 dark:bg-gray-900/90 border-r border-pink-100 dark:border-pink-900 flex flex-col">
|
||||
{/* 侧边栏头部 */}
|
||||
@@ -45,14 +75,11 @@ export function Sidebar({ onClose }: SidebarProps) {
|
||||
displaySessions.map((session) => (
|
||||
<div
|
||||
key={session.id}
|
||||
onClick={() => {
|
||||
setCurrentSession(session.id);
|
||||
if (onClose) onClose();
|
||||
}}
|
||||
onClick={() => handleSelectSession(session.id)}
|
||||
className={`
|
||||
group flex items-center justify-between px-4 py-2.5 mx-2 rounded-lg cursor-pointer transition-colors
|
||||
${
|
||||
currentSessionId === session.id || session.id === useSessionStore.getState().currentSessionId
|
||||
activeSessionId === session.id
|
||||
? 'bg-pink-50 dark:bg-pink-900/30 text-pink-600'
|
||||
: 'hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300'
|
||||
}
|
||||
@@ -63,15 +90,13 @@ export function Sidebar({ onClose }: SidebarProps) {
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium truncate">{session.title || '新的对话'}</p>
|
||||
<p className="text-xs text-gray-400 truncate">
|
||||
{session.message_count || 0} 条消息
|
||||
{session.message_count != null ? `${session.message_count} 条消息` : ''}
|
||||
{session.updated_at ? ` · ${formatTime(session.updated_at)}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteSession(session.id);
|
||||
}}
|
||||
onClick={(e) => handleDeleteSession(e, session.id)}
|
||||
className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all"
|
||||
title="删除会话"
|
||||
>
|
||||
|
||||
@@ -1,68 +1,72 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useSessionStore } from '@/store/sessionStore';
|
||||
import { useChatStore } from '@/store/chatStore';
|
||||
import { listSessions as apiListSessions, createSession as apiCreateSession, deleteSession as apiDeleteSession } from '@/api/client';
|
||||
import type { Session } from '@/types/session';
|
||||
|
||||
interface SessionState {
|
||||
sessions: Session[];
|
||||
currentSessionId: string | null;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export function useSession() {
|
||||
const [state, setState] = useState<SessionState>({
|
||||
sessions: [],
|
||||
currentSessionId: null,
|
||||
loading: false,
|
||||
});
|
||||
const {
|
||||
sessions,
|
||||
currentSessionId,
|
||||
loading,
|
||||
setSessions,
|
||||
addSession,
|
||||
removeSession,
|
||||
setCurrentSessionId,
|
||||
setLoading,
|
||||
} = useSessionStore();
|
||||
|
||||
const { clearMessages } = useChatStore();
|
||||
|
||||
const loadSessions = useCallback(async () => {
|
||||
setState(prev => ({ ...prev, loading: true }));
|
||||
setLoading(true);
|
||||
try {
|
||||
const resp = await apiListSessions();
|
||||
if (resp.data) {
|
||||
const data = resp.data as { sessions: Session[] };
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
sessions: data.sessions || [],
|
||||
loading: false,
|
||||
}));
|
||||
} else {
|
||||
setState(prev => ({ ...prev, loading: false }));
|
||||
setSessions(data.sessions || []);
|
||||
}
|
||||
} catch {
|
||||
setState(prev => ({ ...prev, loading: false }));
|
||||
// ignore
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [setSessions, setLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
loadSessions();
|
||||
}, [loadSessions]);
|
||||
|
||||
const createSession = useCallback(async (title?: string) => {
|
||||
const resp = await apiCreateSession(title);
|
||||
if (resp.data) {
|
||||
const session = resp.data as Session;
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
sessions: [session, ...prev.sessions],
|
||||
currentSessionId: session.id,
|
||||
}));
|
||||
addSession(session);
|
||||
// 切换会话会触发历史消息加载
|
||||
setCurrentSessionId(session.id);
|
||||
clearMessages();
|
||||
return session;
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
}, [addSession, setCurrentSessionId, clearMessages]);
|
||||
|
||||
const deleteSession = useCallback(async (id: string) => {
|
||||
await apiDeleteSession(id);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
sessions: prev.sessions.filter(s => s.id !== id),
|
||||
currentSessionId: prev.currentSessionId === id ? null : prev.currentSessionId,
|
||||
}));
|
||||
}, []);
|
||||
removeSession(id);
|
||||
if (currentSessionId === id) {
|
||||
clearMessages();
|
||||
}
|
||||
}, [removeSession, currentSessionId, clearMessages]);
|
||||
|
||||
const setCurrentSession = useCallback((id: string) => {
|
||||
setState(prev => ({ ...prev, currentSessionId: id }));
|
||||
}, []);
|
||||
clearMessages();
|
||||
setCurrentSessionId(id);
|
||||
}, [setCurrentSessionId, clearMessages]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
sessions,
|
||||
currentSessionId,
|
||||
loading,
|
||||
loadSessions,
|
||||
createSession,
|
||||
deleteSession,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { useChatStore } from '@/store/chatStore';
|
||||
import { useSessionStore } from '@/store/sessionStore';
|
||||
import { getToken } from '@/api/client';
|
||||
import type { WSClientMessage, WSServerMessage } from '@/types/chat';
|
||||
|
||||
@@ -9,13 +10,23 @@ const WS_BASE_URL =
|
||||
export function useWebSocket() {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const { addMessage, setTyping } = useChatStore();
|
||||
const sessionIdRef = useRef<string | null>(null);
|
||||
|
||||
// 订阅 sessionStore 中的 currentSessionId 变化
|
||||
const currentSessionId = useSessionStore((s) => s.currentSessionId);
|
||||
useEffect(() => {
|
||||
sessionIdRef.current = currentSessionId;
|
||||
}, [currentSessionId]);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
const url = `${WS_BASE_URL}?token=${token}`;
|
||||
const sessionID = useSessionStore.getState().currentSessionId || '';
|
||||
const url = sessionID
|
||||
? `${WS_BASE_URL}?token=${token}&session_id=${sessionID}`
|
||||
: `${WS_BASE_URL}?token=${token}`;
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = () => {
|
||||
@@ -26,7 +37,6 @@ export function useWebSocket() {
|
||||
ws.onclose = () => {
|
||||
setIsConnected(false);
|
||||
console.log('[WS] 已断开,3秒后重连...');
|
||||
// 自动重连
|
||||
setTimeout(() => connect(), 3000);
|
||||
};
|
||||
|
||||
@@ -44,7 +54,7 @@ export function useWebSocket() {
|
||||
};
|
||||
|
||||
wsRef.current = ws;
|
||||
}, [addMessage, setTyping]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
@@ -55,7 +65,12 @@ export function useWebSocket() {
|
||||
|
||||
const sendMessage = useCallback((msg: WSClientMessage) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify(msg));
|
||||
// 自动附上 session_id
|
||||
const sessionID = sessionIdRef.current || useSessionStore.getState().currentSessionId;
|
||||
wsRef.current.send(JSON.stringify({
|
||||
...msg,
|
||||
session_id: msg.session_id || sessionID || undefined,
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -63,13 +78,14 @@ export function useWebSocket() {
|
||||
}
|
||||
|
||||
function handleServerMessage(msg: WSServerMessage) {
|
||||
const { addMessage, setTyping } = useChatStore.getState();
|
||||
const { addMessage, appendToLastMessage, finishStreaming, setTyping } = useChatStore.getState();
|
||||
const { setMessages } = useSessionStore.getState();
|
||||
|
||||
switch (msg.type) {
|
||||
case 'response':
|
||||
if (msg.text) {
|
||||
addMessage({
|
||||
id: msg.message_id,
|
||||
id: msg.message_id || '',
|
||||
role: 'assistant',
|
||||
content: msg.text,
|
||||
timestamp: msg.timestamp,
|
||||
@@ -78,6 +94,40 @@ function handleServerMessage(msg: WSServerMessage) {
|
||||
setTyping(false);
|
||||
break;
|
||||
|
||||
case 'stream_chunk':
|
||||
if (msg.content) {
|
||||
// 首个 chunk 到达时创建消息并隐藏 typing indicator
|
||||
const { messages } = useChatStore.getState();
|
||||
const lastMsg = messages[messages.length - 1];
|
||||
if (!lastMsg || lastMsg.role !== 'assistant' || !lastMsg.isStreaming) {
|
||||
// 创建新的流式消息
|
||||
addMessage({
|
||||
id: msg.message_id || ('msg_' + Date.now()),
|
||||
role: 'assistant',
|
||||
content: msg.content,
|
||||
timestamp: msg.timestamp,
|
||||
isStreaming: true,
|
||||
});
|
||||
setTyping(false); // 首个 chunk 到达,隐藏 typing 指示器
|
||||
} else {
|
||||
// 追加到现有流式消息
|
||||
appendToLastMessage(msg.content);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stream_end':
|
||||
finishStreaming();
|
||||
break;
|
||||
|
||||
case 'history_response':
|
||||
if (msg.messages) {
|
||||
// 同步历史消息到两个 store
|
||||
setMessages(msg.messages);
|
||||
useChatStore.getState().setMessages(msg.messages);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.error('[WS] 服务端错误:', msg.error);
|
||||
setTyping(false);
|
||||
|
||||
@@ -69,6 +69,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 流式渲染动画 ===== */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cursorBlink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* 流式消息容器:每个新 chunk 有微淡入效果 */
|
||||
.message-streaming {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 流式消息尾部闪烁光标 */
|
||||
.message-streaming::after {
|
||||
content: '▊';
|
||||
animation: cursorBlink 1s step-end infinite;
|
||||
color: #7c3aed;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* 独立的闪烁光标工具类,可用于显式 span 元素 */
|
||||
.animate-streaming-cursor {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.animate-streaming-cursor::after {
|
||||
content: '▊';
|
||||
animation: cursorBlink 1s step-end infinite;
|
||||
color: #7c3aed;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* 深色模式 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
|
||||
@@ -6,6 +6,8 @@ interface ChatStore {
|
||||
isTyping: boolean;
|
||||
|
||||
addMessage: (message: Message) => void;
|
||||
appendToLastMessage: (content: string) => void;
|
||||
finishStreaming: () => void;
|
||||
setMessages: (messages: Message[]) => void;
|
||||
setTyping: (typing: boolean) => void;
|
||||
clearMessages: () => void;
|
||||
@@ -17,9 +19,41 @@ export const useChatStore = create<ChatStore>((set) => ({
|
||||
|
||||
addMessage: (message) =>
|
||||
set((state) => ({
|
||||
messages: [...state.messages, message],
|
||||
messages: [
|
||||
...state.messages,
|
||||
{
|
||||
...message,
|
||||
isStreaming: message.role === 'assistant' ? (message.isStreaming ?? true) : message.isStreaming,
|
||||
},
|
||||
],
|
||||
})),
|
||||
|
||||
appendToLastMessage: (content) =>
|
||||
set((state) => {
|
||||
const msgs = [...state.messages];
|
||||
const last = msgs[msgs.length - 1];
|
||||
if (last && last.role === 'assistant' && last.isStreaming) {
|
||||
msgs[msgs.length - 1] = {
|
||||
...last,
|
||||
content: last.content + content,
|
||||
};
|
||||
}
|
||||
return { messages: msgs };
|
||||
}),
|
||||
|
||||
finishStreaming: () =>
|
||||
set((state) => {
|
||||
const msgs = [...state.messages];
|
||||
const last = msgs[msgs.length - 1];
|
||||
if (last && last.role === 'assistant' && last.isStreaming) {
|
||||
msgs[msgs.length - 1] = {
|
||||
...last,
|
||||
isStreaming: false,
|
||||
};
|
||||
}
|
||||
return { messages: msgs, isTyping: false };
|
||||
}),
|
||||
|
||||
setMessages: (messages) => set({ messages }),
|
||||
|
||||
setTyping: (typing) => set({ isTyping: typing }),
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { create } from 'zustand';
|
||||
import type { Session } from '@/types/session';
|
||||
import type { Message } from '@/types/chat';
|
||||
import { fetchSessionMessages as apiFetchMessages } from '@/api/client';
|
||||
import { useChatStore } from '@/store/chatStore';
|
||||
|
||||
interface SessionStore {
|
||||
sessions: Session[];
|
||||
currentSessionId: string | null;
|
||||
loading: boolean;
|
||||
messages: Message[];
|
||||
|
||||
setSessions: (sessions: Session[]) => void;
|
||||
addSession: (session: Session) => void;
|
||||
removeSession: (id: string) => void;
|
||||
setCurrentSessionId: (id: string | null) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setMessages: (messages: Message[]) => void;
|
||||
clearMessages: () => void;
|
||||
}
|
||||
|
||||
export const useSessionStore = create<SessionStore>((set) => ({
|
||||
sessions: [],
|
||||
currentSessionId: null,
|
||||
loading: false,
|
||||
messages: [],
|
||||
|
||||
setSessions: (sessions) => set({ sessions }),
|
||||
addSession: (session) =>
|
||||
@@ -25,7 +32,44 @@ export const useSessionStore = create<SessionStore>((set) => ({
|
||||
set((state) => ({
|
||||
sessions: state.sessions.filter((s) => s.id !== id),
|
||||
currentSessionId: state.currentSessionId === id ? null : state.currentSessionId,
|
||||
messages: state.currentSessionId === id ? [] : state.messages,
|
||||
})),
|
||||
setCurrentSessionId: (id) => set({ currentSessionId: id }),
|
||||
setCurrentSessionId: async (id) => {
|
||||
set({ currentSessionId: id, loading: true });
|
||||
|
||||
// 清除旧消息(同时清 chatStore)
|
||||
if (id === null) {
|
||||
set({ messages: [], loading: false });
|
||||
useChatStore.getState().clearMessages();
|
||||
return;
|
||||
}
|
||||
|
||||
// 从后端加载历史消息
|
||||
try {
|
||||
const resp = await apiFetchMessages(id);
|
||||
if (resp.data) {
|
||||
const data = resp.data as { messages: Message[] };
|
||||
const msgs = data.messages || [];
|
||||
set({ messages: msgs, loading: false });
|
||||
// 同步到 chatStore 以便 ChatContainer 渲染
|
||||
useChatStore.getState().setMessages(msgs);
|
||||
} else {
|
||||
set({ messages: [], loading: false });
|
||||
useChatStore.getState().clearMessages();
|
||||
}
|
||||
} catch {
|
||||
set({ messages: [], loading: false });
|
||||
useChatStore.getState().clearMessages();
|
||||
}
|
||||
},
|
||||
setLoading: (loading) => set({ loading }),
|
||||
setMessages: (messages) => {
|
||||
set({ messages });
|
||||
// 同步到 chatStore
|
||||
useChatStore.getState().setMessages(messages);
|
||||
},
|
||||
clearMessages: () => {
|
||||
set({ messages: [] });
|
||||
useChatStore.getState().clearMessages();
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface Message {
|
||||
|
||||
/** WebSocket 客户端消息 */
|
||||
export interface WSClientMessage {
|
||||
type: 'message' | 'voice_input' | 'ping';
|
||||
type: 'message' | 'voice_input' | 'ping' | 'history';
|
||||
session_id?: string;
|
||||
mode?: ChatMode;
|
||||
content?: string;
|
||||
@@ -37,14 +37,18 @@ export interface WSClientMessage {
|
||||
|
||||
/** WebSocket 服务端消息 */
|
||||
export interface WSServerMessage {
|
||||
type: 'response' | 'segment' | 'audio' | 'error' | 'device_update' | 'pong';
|
||||
message_id: string;
|
||||
type: 'response' | 'segment' | 'audio' | 'error' | 'device_update' | 'pong' | 'history_response' | 'stream_chunk' | 'stream_end';
|
||||
message_id?: string;
|
||||
text?: string;
|
||||
content?: string;
|
||||
role?: string;
|
||||
session_id?: string;
|
||||
segments?: VoiceSegment[];
|
||||
full_audio_url?: string;
|
||||
response_mode?: ChatMode;
|
||||
tool_calls?: ToolCall[];
|
||||
error?: string;
|
||||
messages?: Message[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,19 +5,15 @@ export interface Session {
|
||||
id: string;
|
||||
user_id: string;
|
||||
title: string;
|
||||
persona: string;
|
||||
mode: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
message_count: number;
|
||||
is_active: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
/** 创建会话参数 */
|
||||
export interface CreateSessionParams {
|
||||
title?: string;
|
||||
persona?: string;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
/** 会话列表响应 */
|
||||
@@ -25,6 +21,11 @@ export interface SessionListResponse {
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
/** 会话消息列表响应 */
|
||||
export interface SessionMessagesResponse {
|
||||
messages: import('@/types/chat').Message[];
|
||||
}
|
||||
|
||||
/** 认证相关 */
|
||||
export interface AuthResponse {
|
||||
user_id: string;
|
||||
|
||||
Reference in New Issue
Block a user