修改了历史会话显示的问题和默认查看历史记录信息
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> {
|
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> {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -332,7 +332,11 @@
|
|||||||
"title": "歷史會話",
|
"title": "歷史會話",
|
||||||
"newChat": "開始新會話",
|
"newChat": "開始新會話",
|
||||||
"empty": "暫無歷史會話",
|
"empty": "暫無歷史會話",
|
||||||
"current": "目前"
|
"current": "目前",
|
||||||
|
"delete": "刪除",
|
||||||
|
"deleteConfirm": "確定刪除這條對話嗎?刪除後無法復原。",
|
||||||
|
"deleted": "對話已刪除",
|
||||||
|
"deleteFailed": "刪除失敗:{message}"
|
||||||
},
|
},
|
||||||
"optimize": {
|
"optimize": {
|
||||||
"badge": "優化記錄",
|
"badge": "優化記錄",
|
||||||
|
|||||||
@@ -333,7 +333,11 @@
|
|||||||
"title": "历史会话",
|
"title": "历史会话",
|
||||||
"newChat": "开始新会话",
|
"newChat": "开始新会话",
|
||||||
"empty": "暂无历史会话",
|
"empty": "暂无历史会话",
|
||||||
"current": "当前"
|
"current": "当前",
|
||||||
|
"delete": "删除",
|
||||||
|
"deleteConfirm": "确定删除这条会话吗?删除后无法恢复。",
|
||||||
|
"deleted": "会话已删除",
|
||||||
|
"deleteFailed": "删除失败:{message}"
|
||||||
},
|
},
|
||||||
"optimize": {
|
"optimize": {
|
||||||
"badge": "优化记录",
|
"badge": "优化记录",
|
||||||
|
|||||||
@@ -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 时(设备未识别完毕)不应该停在 initializing;ConnectScreen 会拦截
|
||||||
|
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
|
||||||
|
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>
|
</button>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user