Compare commits

..
3 Commits
3 changed files with 48 additions and 21 deletions
+9 -6
View File
@@ -458,12 +458,12 @@ pub async fn chat_send(
app: AppHandle, app: AppHandle,
state: State<'_, App>, state: State<'_, App>,
payload: ChatSendPayload, payload: ChatSendPayload,
) -> Result<(), String> { ) -> Result<serde_json::Value, String> {
let core = state.core.clone(); let core = state.core.clone();
let engine = state.engine.clone(); let engine = state.engine.clone();
let base = state.engine_base.clone(); let base = state.engine_base.clone();
let (history, model, engine_bin) = { let (history, model, engine_bin, user_message_id) = {
let db = core.db.lock().unwrap(); let db = core.db.lock().unwrap();
if sessions_db::get_conversation(&db, &payload.conversation_id) if sessions_db::get_conversation(&db, &payload.conversation_id)
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
@@ -479,7 +479,7 @@ pub async fn chat_send(
) )
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
} }
sessions_db::add_message( let user_message_id = sessions_db::add_message(
&db, &db,
&payload.conversation_id, &payload.conversation_id,
"user", "user",
@@ -508,7 +508,7 @@ pub async fn chat_send(
let bin = settings_db::get(&db, "engine_bin") let bin = settings_db::get(&db, "engine_bin")
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
.unwrap_or_default(); .unwrap_or_default();
(history, model, bin) (history, model, bin, user_message_id)
}; };
let conversation_id = payload.conversation_id.clone(); let conversation_id = payload.conversation_id.clone();
@@ -523,13 +523,16 @@ pub async fn chat_send(
core, core,
engine, engine,
base, base,
conversation_id, conversation_id.clone(),
history, history,
model, model,
engine_bin, engine_bin,
payload.params, payload.params,
)); ));
Ok(()) Ok(serde_json::json!({
"conversation_id": conversation_id,
"message_id": user_message_id,
}))
} }
/// 统一的生成与流式转发:支持本地引擎 / 远程 API,统计首字延迟、token 数与耗时。 /// 统一的生成与流式转发:支持本地引擎 / 远程 API,统计首字延迟、token 数与耗时。
+22 -8
View File
@@ -188,18 +188,30 @@ export const api = {
invoke<void>("deploy_model", { modelId, params }), invoke<void>("deploy_model", { modelId, params }),
engineStop: () => invoke<void>("engine_stop"), engineStop: () => invoke<void>("engine_stop"),
engineStatus: () => invoke<EngineStatus>("engine_status"), engineStatus: () => invoke<EngineStatus>("engine_status"),
chatSend: (payload: ChatSendPayload) => invoke<string>("chat_send", { payload }), chatSend: (payload: ChatSendPayload) =>
invoke<{ conversation_id: string; message_id: string }>("chat_send", { payload }),
regenerateMessage: ( regenerateMessage: (
conversationId: string, conversationId: string,
messageId: string, messageId: string,
params: ChatParams, params: ChatParams,
) => invoke<void>("regenerate_message", { conversationId, messageId, params }), ) =>
invoke<void>("regenerate_message", {
payload: { conversation_id: conversationId, message_id: messageId, params },
}),
editMessage: ( editMessage: (
conversationId: string, conversationId: string,
messageId: string, messageId: string,
content: string, content: string,
params: ChatParams, params: ChatParams,
) => invoke<void>("edit_message", { conversationId, messageId, content, params }), ) =>
invoke<void>("edit_message", {
payload: {
conversation_id: conversationId,
message_id: messageId,
content,
params,
},
}),
listMessageVersions: (messageId: string) => listMessageVersions: (messageId: string) =>
invoke<MessageVersion[]>("list_message_versions", { messageId }), invoke<MessageVersion[]>("list_message_versions", { messageId }),
applyMessageVersion: ( applyMessageVersion: (
@@ -215,11 +227,13 @@ export const api = {
source?: string, source?: string,
) => ) =>
invoke<string>("download_enqueue", { invoke<string>("download_enqueue", {
url, payload: {
fileName, url,
sha256: sha256 ?? null, file_name: fileName,
repoId: repoId ?? null, sha256: sha256 ?? null,
source: source ?? null, repo_id: repoId ?? null,
source: source ?? null,
},
}), }),
serverStart: (port: number, apiKey: string) => serverStart: (port: number, apiKey: string) =>
invoke<ServerStatus>("server_start", { port, apiKey }), invoke<ServerStatus>("server_start", { port, apiKey }),
+17 -7
View File
@@ -219,21 +219,27 @@ export default function ChatPage() {
await refreshConversations(); await refreshConversations();
} }
const tempUserId = `user-${Date.now()}`;
setMessages((prev) => [ setMessages((prev) => [
...prev, ...prev,
{ id: `user-${Date.now()}`, role: "user", content, images }, { id: tempUserId, role: "user", content, images },
{ id: `assistant-${Date.now()}`, role: "assistant", content: "", streaming: true }, { id: `assistant-${Date.now()}`, role: "assistant", content: "", streaming: true },
]); ]);
setStreaming(true); setStreaming(true);
try { try {
await api.chatSend({ const result = await api.chatSend({
conversation_id: convId, conversation_id: convId,
model_id: selectedModelId, model_id: selectedModelId,
content, content,
params, params,
images, images,
}); });
if (result?.message_id) {
setMessages((prev) =>
prev.map((m) => (m.id === tempUserId ? { ...m, id: result.message_id } : m)),
);
}
} catch (e) { } catch (e) {
setStreaming(false); setStreaming(false);
setError(String(e)); setError(String(e));
@@ -718,15 +724,19 @@ function MessageBubble({
) : isUser ? ( ) : isUser ? (
<> <>
<div className="whitespace-pre-wrap">{message.content}</div> <div className="whitespace-pre-wrap">{message.content}</div>
{!streaming ? ( <div
className={`mt-1 flex justify-end transition-opacity ${
streaming ? "opacity-0" : "opacity-0 group-hover:opacity-100"
}`}
>
<button <button
className="absolute -left-8 top-2 hidden rounded p-1 text-slate-500 hover:text-slate-200 group-hover:block" className="rounded px-1.5 py-0.5 text-[10px] text-white/60 hover:bg-white/10 hover:text-white"
onClick={() => onEditStart(message)} onClick={() => onEditStart(message)}
title="编辑并重新提交" title="编辑并重新提交(清空之后的生成)"
> >
</button> </button>
) : null} </div>
</> </>
) : message.content ? ( ) : message.content ? (
<ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown> <ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>