0.5 缩减重构
This commit is contained in:
@@ -9,10 +9,12 @@ import { clamp } from "@/lib/constants";
|
||||
import { useAppPreferencesStore } from "@/store/app-preferences";
|
||||
|
||||
type AgentMessage = { role: "user" | "assistant"; content: string };
|
||||
type AiProvider = "online" | "local" | "ollama" | "codex";
|
||||
|
||||
type ToolLog =
|
||||
| { type: "tool_call"; id: string; tool: string; args: Record<string, unknown> }
|
||||
| { type: "tool_result"; id: string; tool: string; ok: boolean; ms: number; result: unknown }
|
||||
| { type: "info"; message: string }
|
||||
| { type: "error"; message: string };
|
||||
|
||||
type PanelPage = "chat" | "tools" | "settings";
|
||||
@@ -37,6 +39,8 @@ const ONLINE_MODELS = [
|
||||
"gemini-3-flash-preview",
|
||||
] as const;
|
||||
|
||||
const OLLAMA_QWEN3_30B = "qwen3:30b-a3b-instruct-2507-q4_K_M";
|
||||
|
||||
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
||||
typeof v === "object" && v !== null && !Array.isArray(v);
|
||||
|
||||
@@ -95,10 +99,13 @@ export function OnlyOfficeAiAgentPanel({
|
||||
|
||||
const [networkOn, setNetworkOn] = useState(() => !flightMode);
|
||||
const [toolAuto, setToolAuto] = useState(true);
|
||||
const [aiProvider, setAiProvider] = useState<"online" | "local">("online");
|
||||
const [aiProvider, setAiProvider] = useState<AiProvider>("online");
|
||||
const [aiModel, setAiModel] = useState<string>("");
|
||||
const [maxSteps, setMaxSteps] = useState<number>(10);
|
||||
|
||||
// Codex:每个面板对话维护一个 session,支持连续对话与“暂停(类似 ESC)”
|
||||
const [codexSessionId, setCodexSessionId] = useState<string | null>(null);
|
||||
|
||||
const [pluginReady, setPluginReady] = useState(false);
|
||||
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
@@ -131,7 +138,7 @@ export function OnlyOfficeAiAgentPanel({
|
||||
const providerRaw = (window.localStorage.getItem("onlyoffice_ai_provider") || "").trim();
|
||||
const modelRaw = window.localStorage.getItem("onlyoffice_ai_model") || "";
|
||||
const parsed = Number(stepsRaw);
|
||||
if (providerRaw === "online" || providerRaw === "local") setAiProvider(providerRaw);
|
||||
if (providerRaw === "online" || providerRaw === "local" || providerRaw === "ollama" || providerRaw === "codex") setAiProvider(providerRaw);
|
||||
if (typeof modelRaw === "string") setAiModel(modelRaw);
|
||||
if (Number.isFinite(parsed) && parsed >= 1) setMaxSteps(clamp(Math.floor(parsed), 1, 24));
|
||||
} catch {
|
||||
@@ -281,6 +288,9 @@ export function OnlyOfficeAiAgentPanel({
|
||||
abortRef.current?.abort();
|
||||
abortRef.current = null;
|
||||
setLoading(false);
|
||||
if (aiProvider === "codex") {
|
||||
setToolLogs((prev) => [...prev, { type: "info", message: "已暂停(可继续对话)" }]);
|
||||
}
|
||||
};
|
||||
|
||||
const send = async () => {
|
||||
@@ -288,6 +298,9 @@ export function OnlyOfficeAiAgentPanel({
|
||||
if (!text) return;
|
||||
if (loading) return;
|
||||
|
||||
const codexSessionIdForRequest =
|
||||
aiProvider === "codex" ? String(codexSessionId ?? "").trim() || null : null;
|
||||
|
||||
const nextMessages: AgentMessage[] = [...messages, { role: "user", content: text }];
|
||||
setMessages(nextMessages);
|
||||
setInput("");
|
||||
@@ -318,7 +331,14 @@ export function OnlyOfficeAiAgentPanel({
|
||||
"toolset.onlyoffice_editor",
|
||||
],
|
||||
},
|
||||
options: { searxng: networkOn, ai: { provider: aiProvider, model: aiModel } },
|
||||
options: {
|
||||
searxng: networkOn,
|
||||
ai: {
|
||||
provider: aiProvider,
|
||||
...(aiProvider === "codex" && codexSessionIdForRequest ? { sessionId: codexSessionIdForRequest } : {}),
|
||||
...(aiProvider !== "codex" && String(aiModel || "").trim() ? { model: String(aiModel || "").trim() } : {}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -329,6 +349,19 @@ export function OnlyOfficeAiAgentPanel({
|
||||
}
|
||||
|
||||
await parseSseChunks(res, (event, dataText) => {
|
||||
if (event === "codex_session") {
|
||||
try {
|
||||
const d = JSON.parse(dataText || "null") as unknown;
|
||||
const obj = isRecord(d) ? d : ({} as Record<string, unknown>);
|
||||
const sid = String(obj.sessionId ?? "").trim();
|
||||
if (sid) {
|
||||
setCodexSessionId(sid);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event === "assistant_message") {
|
||||
try {
|
||||
const d = JSON.parse(dataText || "null") as unknown;
|
||||
@@ -386,6 +419,7 @@ export function OnlyOfficeAiAgentPanel({
|
||||
}
|
||||
|
||||
if (event === "error") {
|
||||
if (controller.signal.aborted && aiProvider === "codex") return;
|
||||
try {
|
||||
const d = JSON.parse(dataText || "null") as unknown;
|
||||
const msg = isRecord(d) && "message" in d ? String(d.message ?? "") : "";
|
||||
@@ -397,6 +431,7 @@ export function OnlyOfficeAiAgentPanel({
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (controller.signal.aborted && aiProvider === "codex") return;
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
setToolLogs((prev) => [...prev, { type: "error", message: msg }]);
|
||||
} finally {
|
||||
@@ -495,6 +530,13 @@ export function OnlyOfficeAiAgentPanel({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (l.type === "info") {
|
||||
return (
|
||||
<div key={idx} className="rounded border bg-muted p-2 text-muted-foreground">
|
||||
{l.message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (l.type === "tool_call") {
|
||||
return (
|
||||
<div key={idx} className="rounded border p-2">
|
||||
@@ -544,29 +586,65 @@ export function OnlyOfficeAiAgentPanel({
|
||||
<select
|
||||
className="rounded border px-2 py-1 text-xs"
|
||||
value={aiProvider}
|
||||
onChange={(e) => setAiProvider(e.target.value === "local" ? "local" : "online")}
|
||||
onChange={(e) => {
|
||||
const v = String(e.target.value || "").trim();
|
||||
if (v === "online" || v === "local" || v === "ollama" || v === "codex") setAiProvider(v);
|
||||
else setAiProvider("online");
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
<option value="online">在线</option>
|
||||
<option value="local">本地</option>
|
||||
<option value="ollama">Ollama</option>
|
||||
<option value="codex">Codex</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center justify-between gap-2">
|
||||
<span className="font-medium">模型</span>
|
||||
<select
|
||||
className="w-[220px] rounded border px-2 py-1 text-xs"
|
||||
value={aiModel}
|
||||
onChange={(e) => setAiModel(e.target.value)}
|
||||
disabled={loading || aiProvider !== "online"}
|
||||
>
|
||||
{ONLINE_MODELS.map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m || "默认"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{aiProvider === "codex" ? (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
消息开头加 <code className="rounded bg-muted px-1 py-0.5">#chat</code> /{" "}
|
||||
<code className="rounded bg-muted px-1 py-0.5">#test</code> /{" "}
|
||||
<code className="rounded bg-muted px-1 py-0.5">#dev</code>(默认 <code className="rounded bg-muted px-1 py-0.5">#chat</code>)。
|
||||
</div>
|
||||
) : aiProvider === "online" ? (
|
||||
<select
|
||||
className="w-[220px] rounded border px-2 py-1 text-xs"
|
||||
value={ONLINE_MODELS.includes(aiModel as any) ? aiModel : ""}
|
||||
onChange={(e) => setAiModel(e.target.value)}
|
||||
disabled={loading}
|
||||
>
|
||||
{ONLINE_MODELS.map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m || "默认"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : aiProvider === "ollama" ? (
|
||||
<select
|
||||
className="w-[320px] rounded border px-2 py-1 text-xs"
|
||||
value={aiModel === "" || aiModel === OLLAMA_QWEN3_30B ? aiModel : ""}
|
||||
onChange={(e) => setAiModel(e.target.value)}
|
||||
disabled={loading}
|
||||
>
|
||||
<option value="">默认:{OLLAMA_QWEN3_30B}</option>
|
||||
<option value={OLLAMA_QWEN3_30B}>{OLLAMA_QWEN3_30B}</option>
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
className="w-[320px] rounded border px-2 py-1 text-xs"
|
||||
value={aiModel}
|
||||
onChange={(e) => setAiModel(e.target.value)}
|
||||
placeholder="默认(ai.local.md/环境变量)"
|
||||
disabled={loading}
|
||||
list="oo-local-model-suggestions"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<datalist id="oo-local-model-suggestions">
|
||||
<option value={OLLAMA_QWEN3_30B} />
|
||||
</datalist>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -602,7 +680,7 @@ export function OnlyOfficeAiAgentPanel({
|
||||
发送
|
||||
</Button>
|
||||
<Button variant="secondary" disabled={!loading} onClick={stop}>
|
||||
停止
|
||||
{aiProvider === "codex" ? "暂停" : "停止"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user