修改了历史会话显示的问题和默认查看历史记录信息

This commit is contained in:
allen2fuc
2026-04-29 15:27:45 +08:00
parent 7936fea313
commit c7b340087a
5 changed files with 106 additions and 14 deletions
+5 -3
View File
@@ -131,9 +131,11 @@ export async function listMessages(chatId: string): Promise<MessageRead[]> {
} }
export async function clearChat(chatId: string): Promise<void> { export async function clearChat(chatId: string): Promise<void> {
await fetch(`${getAIBaseUrl()}/sse/clear?chat_id=${encodeURIComponent(chatId)}`, { const res = await fetch(
method: "POST", `${getAIBaseUrl()}/sse/clear?chat_id=${encodeURIComponent(chatId)}`,
}); { method: "POST" },
);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
} }
export async function updateMessageApplied(messageId: string, applied: boolean): Promise<void> { export async function updateMessageApplied(messageId: string, applied: boolean): Promise<void> {
+5 -1
View File
@@ -518,7 +518,11 @@
"title": "Chat history", "title": "Chat history",
"newChat": "Start a new chat", "newChat": "Start a new chat",
"empty": "No chat history yet", "empty": "No chat history yet",
"current": "Current" "current": "Current",
"delete": "Delete",
"deleteConfirm": "Delete this chat? This cannot be undone.",
"deleted": "Chat deleted",
"deleteFailed": "Delete failed: {message}"
}, },
"optimize": { "optimize": {
"badge": "Optimization", "badge": "Optimization",
+5 -1
View File
@@ -332,7 +332,11 @@
"title": "歷史會話", "title": "歷史會話",
"newChat": "開始新會話", "newChat": "開始新會話",
"empty": "暫無歷史會話", "empty": "暫無歷史會話",
"current": "目前" "current": "目前",
"delete": "刪除",
"deleteConfirm": "確定刪除這條對話嗎?刪除後無法復原。",
"deleted": "對話已刪除",
"deleteFailed": "刪除失敗:{message}"
}, },
"optimize": { "optimize": {
"badge": "優化記錄", "badge": "優化記錄",
+5 -1
View File
@@ -333,7 +333,11 @@
"title": "历史会话", "title": "历史会话",
"newChat": "开始新会话", "newChat": "开始新会话",
"empty": "暂无历史会话", "empty": "暂无历史会话",
"current": "当前" "current": "当前",
"delete": "删除",
"deleteConfirm": "确定删除这条会话吗?删除后无法恢复。",
"deleted": "会话已删除",
"deleteFailed": "删除失败:{message}"
}, },
"optimize": { "optimize": {
"badge": "优化记录", "badge": "优化记录",
+86 -8
View File
@@ -42,6 +42,7 @@ import {
Check, Check,
RotateCcw, RotateCcw,
Loader2, Loader2,
Trash2,
} from "lucide-react"; } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Streamdown } from "streamdown"; import { Streamdown } from "streamdown";
@@ -147,10 +148,13 @@ export default function AIPage() {
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [historyOpen, setHistoryOpen] = useState(false); const [historyOpen, setHistoryOpen] = useState(false);
const [chats, setChats] = useState<ChatRead[]>([]); const [chats, setChats] = useState<ChatRead[]>([]);
// 初次加载历史会话期间,避免先闪一下欢迎页再切到消息
const [initializing, setInitializing] = useState(true);
const scrollRef = useRef<HTMLDivElement | null>(null); const scrollRef = useRef<HTMLDivElement | null>(null);
const abortRef = useRef<AbortController | null>(null); const abortRef = useRef<AbortController | null>(null);
const textareaRef = useRef<HTMLTextAreaElement | null>(null); const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const initialLoadRef = useRef(false);
const isEmpty = messages.length === 0; const isEmpty = messages.length === 0;
@@ -227,6 +231,37 @@ export default function AIPage() {
[aiText, mapRowsToUI], [aiText, mapRowsToUI],
); );
// ── 进入页面时若有历史会话,自动打开最近一次 ──
useEffect(() => {
if (initialLoadRef.current) return;
if (!mac) return;
initialLoadRef.current = true;
(async () => {
try {
const list = await listChats(mac);
setChats(list);
if (list.length === 0) return;
const newest = list.reduce((a, b) =>
new Date(a.updated_at) > new Date(b.updated_at) ? a : b,
);
await loadChat(newest.id, { closePanel: false });
} catch {
// 静默失败:保留欢迎页
} finally {
setInitializing(false);
}
})();
}, [mac, loadChat]);
// 没有 mac 时(设备未识别完毕)不应该停在 initializingConnectScreen 会拦截
useEffect(() => {
if (!mac && initializing) {
// 给 mac 一段缓冲期,否则即便最终没有 mac 也要释放欢迎页
const t = setTimeout(() => setInitializing(false), 800);
return () => clearTimeout(t);
}
}, [mac, initializing]);
// ── 静默刷新当前会话(流式结束后用于拉取新增的 type=2 优化记录) ── // ── 静默刷新当前会话(流式结束后用于拉取新增的 type=2 优化记录) ──
const refreshCurrentChat = useCallback( const refreshCurrentChat = useCallback(
async (id: string) => { async (id: string) => {
@@ -257,6 +292,7 @@ export default function AIPage() {
} }
try { try {
await clearChat(chatId); await clearChat(chatId);
setChats((prev) => prev.filter((c) => c.id !== chatId));
newChat(); newChat();
toast.success(aiText.cleared); toast.success(aiText.cleared);
} catch (e: any) { } catch (e: any) {
@@ -264,6 +300,28 @@ export default function AIPage() {
} }
}, [aiText, chatId, newChat]); }, [aiText, chatId, newChat]);
// ── 历史面板里删除某条会话 ──
const deleteChatById = useCallback(
async (id: string) => {
const ok = window.confirm(aiText.history.deleteConfirm);
if (!ok) return;
try {
await clearChat(id);
setChats((prev) => prev.filter((c) => c.id !== id));
if (chatId === id) {
setChatId(null);
setMessages([]);
}
toast.success(aiText.history.deleted);
} catch (e: any) {
toast.error(
formatText(aiText.history.deleteFailed, { message: e?.message ?? e }),
);
}
},
[aiText, chatId],
);
// ── 发送提问 ── // ── 发送提问 ──
const send = useCallback( const send = useCallback(
(question: string) => { (question: string) => {
@@ -590,7 +648,11 @@ export default function AIPage() {
{/* ── Messages / Welcome ── */} {/* ── Messages / Welcome ── */}
<div ref={scrollRef} className="flex-1 min-h-0 overflow-y-auto px-4"> <div ref={scrollRef} className="flex-1 min-h-0 overflow-y-auto px-4">
{isEmpty ? ( {initializing ? (
<div className="h-full flex items-center justify-center">
<Loader2 size={20} className="animate-spin text-white/30" />
</div>
) : isEmpty ? (
<WelcomeView <WelcomeView
aiText={aiText} aiText={aiText}
deviceName={deviceName} deviceName={deviceName}
@@ -712,6 +774,7 @@ export default function AIPage() {
onClose={() => setHistoryOpen(false)} onClose={() => setHistoryOpen(false)}
onSelect={(id) => loadChat(id)} onSelect={(id) => loadChat(id)}
onNew={newChat} onNew={newChat}
onDelete={deleteChatById}
/> />
)} )}
@@ -895,6 +958,7 @@ function HistoryPanel({
onClose, onClose,
onSelect, onSelect,
onNew, onNew,
onDelete,
}: { }: {
aiText: AILocale; aiText: AILocale;
chats: ChatRead[]; chats: ChatRead[];
@@ -902,9 +966,10 @@ function HistoryPanel({
onClose: () => void; onClose: () => void;
onSelect: (id: string) => void; onSelect: (id: string) => void;
onNew: () => void; onNew: () => void;
onDelete: (id: string) => void;
}) { }) {
return ( return (
<div className="fixed inset-0 z-50 flex flex-col bg-black/70" onClick={onClose}> <div className="fixed inset-0 z-[60] flex flex-col bg-black/70" onClick={onClose}>
<div <div
className="mt-auto rounded-t-[22px] max-h-[70vh] flex flex-col" className="mt-auto rounded-t-[22px] max-h-[70vh] flex flex-col"
style={{ style={{
@@ -947,26 +1012,39 @@ function HistoryPanel({
) : ( ) : (
<div className="ios-list-group"> <div className="ios-list-group">
{chats.map((c) => ( {chats.map((c) => (
<button <div
key={c.id} key={c.id}
onClick={() => onSelect(c.id)}
className={cn( className={cn(
"ios-list-row w-full text-left active:bg-white/5", "ios-list-row w-full",
currentId === c.id && "bg-white/[0.04]", currentId === c.id && "bg-white/[0.04]",
)} )}
> >
<div className="flex-1 min-w-0"> <button
onClick={() => onSelect(c.id)}
className="flex-1 min-w-0 text-left active:opacity-60"
>
<div className="text-[15px] text-white truncate">{c.title}</div> <div className="text-[15px] text-white truncate">{c.title}</div>
<div className="text-[11px] text-white/35 mt-0.5"> <div className="text-[11px] text-white/35 mt-0.5">
{new Date(c.updated_at).toLocaleString()} {new Date(c.updated_at).toLocaleString()}
</div> </div>
</div> </button>
{currentId === c.id && ( {currentId === c.id && (
<span className="text-[11px] text-[#00FFF6] ml-2"> <span className="text-[11px] text-[#00FFF6] ml-2">
{aiText.history.current} {aiText.history.current}
</span> </span>
)} )}
</button> <button
onClick={(e) => {
e.stopPropagation();
onDelete(c.id);
}}
className="ml-2 w-8 h-8 rounded-full flex items-center justify-center text-white/40 hover:text-red-400 active:text-red-500 transition-colors"
style={{ background: "rgba(44,44,46,0.6)" }}
aria-label={aiText.history.delete}
>
<Trash2 size={14} />
</button>
</div>
))} ))}
</div> </div>
)} )}