修改了历史会话显示的问题和默认查看历史记录信息
This commit is contained in:
@@ -131,9 +131,11 @@ export async function listMessages(chatId: string): Promise<MessageRead[]> {
|
||||
}
|
||||
|
||||
export async function clearChat(chatId: string): Promise<void> {
|
||||
await fetch(`${getAIBaseUrl()}/sse/clear?chat_id=${encodeURIComponent(chatId)}`, {
|
||||
method: "POST",
|
||||
});
|
||||
const res = await fetch(
|
||||
`${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> {
|
||||
|
||||
@@ -518,7 +518,11 @@
|
||||
"title": "Chat history",
|
||||
"newChat": "Start a new chat",
|
||||
"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": {
|
||||
"badge": "Optimization",
|
||||
|
||||
@@ -332,7 +332,11 @@
|
||||
"title": "歷史會話",
|
||||
"newChat": "開始新會話",
|
||||
"empty": "暫無歷史會話",
|
||||
"current": "目前"
|
||||
"current": "目前",
|
||||
"delete": "刪除",
|
||||
"deleteConfirm": "確定刪除這條對話嗎?刪除後無法復原。",
|
||||
"deleted": "對話已刪除",
|
||||
"deleteFailed": "刪除失敗:{message}"
|
||||
},
|
||||
"optimize": {
|
||||
"badge": "優化記錄",
|
||||
|
||||
@@ -333,7 +333,11 @@
|
||||
"title": "历史会话",
|
||||
"newChat": "开始新会话",
|
||||
"empty": "暂无历史会话",
|
||||
"current": "当前"
|
||||
"current": "当前",
|
||||
"delete": "删除",
|
||||
"deleteConfirm": "确定删除这条会话吗?删除后无法恢复。",
|
||||
"deleted": "会话已删除",
|
||||
"deleteFailed": "删除失败:{message}"
|
||||
},
|
||||
"optimize": {
|
||||
"badge": "优化记录",
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
Check,
|
||||
RotateCcw,
|
||||
Loader2,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Streamdown } from "streamdown";
|
||||
@@ -147,10 +148,13 @@ export default function AIPage() {
|
||||
const [sending, setSending] = useState(false);
|
||||
const [historyOpen, setHistoryOpen] = useState(false);
|
||||
const [chats, setChats] = useState<ChatRead[]>([]);
|
||||
// 初次加载历史会话期间,避免先闪一下欢迎页再切到消息
|
||||
const [initializing, setInitializing] = useState(true);
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const initialLoadRef = useRef(false);
|
||||
|
||||
const isEmpty = messages.length === 0;
|
||||
|
||||
@@ -227,6 +231,37 @@ export default function AIPage() {
|
||||
[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 时(设备未识别完毕)不应该停在 initializing;ConnectScreen 会拦截
|
||||
useEffect(() => {
|
||||
if (!mac && initializing) {
|
||||
// 给 mac 一段缓冲期,否则即便最终没有 mac 也要释放欢迎页
|
||||
const t = setTimeout(() => setInitializing(false), 800);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
}, [mac, initializing]);
|
||||
|
||||
// ── 静默刷新当前会话(流式结束后用于拉取新增的 type=2 优化记录) ──
|
||||
const refreshCurrentChat = useCallback(
|
||||
async (id: string) => {
|
||||
@@ -257,6 +292,7 @@ export default function AIPage() {
|
||||
}
|
||||
try {
|
||||
await clearChat(chatId);
|
||||
setChats((prev) => prev.filter((c) => c.id !== chatId));
|
||||
newChat();
|
||||
toast.success(aiText.cleared);
|
||||
} catch (e: any) {
|
||||
@@ -264,6 +300,28 @@ export default function AIPage() {
|
||||
}
|
||||
}, [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(
|
||||
(question: string) => {
|
||||
@@ -590,7 +648,11 @@ export default function AIPage() {
|
||||
|
||||
{/* ── Messages / Welcome ── */}
|
||||
<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
|
||||
aiText={aiText}
|
||||
deviceName={deviceName}
|
||||
@@ -712,6 +774,7 @@ export default function AIPage() {
|
||||
onClose={() => setHistoryOpen(false)}
|
||||
onSelect={(id) => loadChat(id)}
|
||||
onNew={newChat}
|
||||
onDelete={deleteChatById}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -895,6 +958,7 @@ function HistoryPanel({
|
||||
onClose,
|
||||
onSelect,
|
||||
onNew,
|
||||
onDelete,
|
||||
}: {
|
||||
aiText: AILocale;
|
||||
chats: ChatRead[];
|
||||
@@ -902,9 +966,10 @@ function HistoryPanel({
|
||||
onClose: () => void;
|
||||
onSelect: (id: string) => void;
|
||||
onNew: () => void;
|
||||
onDelete: (id: string) => void;
|
||||
}) {
|
||||
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
|
||||
className="mt-auto rounded-t-[22px] max-h-[70vh] flex flex-col"
|
||||
style={{
|
||||
@@ -947,26 +1012,39 @@ function HistoryPanel({
|
||||
) : (
|
||||
<div className="ios-list-group">
|
||||
{chats.map((c) => (
|
||||
<button
|
||||
<div
|
||||
key={c.id}
|
||||
onClick={() => onSelect(c.id)}
|
||||
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]",
|
||||
)}
|
||||
>
|
||||
<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-[11px] text-white/35 mt-0.5">
|
||||
{new Date(c.updated_at).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{currentId === c.id && (
|
||||
<span className="text-[11px] text-[#00FFF6] ml-2">
|
||||
{aiText.history.current}
|
||||
</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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user