mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 17:04:27 +08:00
feat(agent): add support for selecting Codex model and reasoning effort in the canvas agent, retaining choices for subsequent tasks
This commit is contained in:
@@ -13,7 +13,7 @@ import { uploadImage } from "@/services/image-storage";
|
||||
import { deleteAgentThreadMessages, readAgentUserMessages, saveAgentUserMessage } from "@/services/agent-chat-storage";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { useAgentStore, type AgentCanvasContext, type AgentChatItem, type AgentPendingApproval, type AgentPendingToolCall, type AgentPermissionMode, type AgentThreadSummary } from "@/stores/use-agent-store";
|
||||
import { useAgentStore, type AgentCanvasContext, type AgentChatItem, type AgentModel, type AgentPendingApproval, type AgentPendingToolCall, type AgentPermissionMode, type AgentReasoningEffort, type AgentThreadSummary } from "@/stores/use-agent-store";
|
||||
import { summarizeCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops";
|
||||
import { isSiteTool, runSiteTool } from "@/lib/agent/agent-site-tools";
|
||||
import { activateAgentClient, discoverAgentConfig, fetchAgentJson, postCodexApproval, postState, postToolResult } from "./agent-api";
|
||||
@@ -65,6 +65,7 @@ const DEFAULT_AGENT_URL = "http://127.0.0.1:17371";
|
||||
type AgentWorkspace = { workspacePath: string; activeThreadId?: string };
|
||||
type AgentThreadsResponse = { ok?: boolean; workspace?: AgentWorkspace; data?: AgentThreadSummary[] };
|
||||
type AgentThreadResponse = { ok?: boolean; workspace?: AgentWorkspace; thread?: AgentThreadSummary; messages?: AgentChatItem[] };
|
||||
type AgentModelsResponse = { ok?: boolean; data?: AgentModel[] };
|
||||
type AgentCodexState = { busy?: boolean; threadId?: string; turnId?: string };
|
||||
type AgentHelloEvent = { ok?: boolean; clientId?: string; codex?: AgentCodexState };
|
||||
type AgentWorkspaceEvent = { activeThreadId?: string; threadId?: string; emptyThread?: boolean; draftThread?: boolean };
|
||||
@@ -79,7 +80,7 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
|
||||
// 注意:canvasContext 不在此订阅内 —— 它在拖拽/resize 时会被 project 每帧写入,
|
||||
// 但面板只在 ref 同步与防抖 postState 中用到它、渲染层从不读它。若把它放进订阅,
|
||||
// 面板会随画布每帧重渲染(性能问题,也是 #185 崩溃的放大器)。改为下方 subscribe 命令式监听。
|
||||
const { width, url, token, connected, enabled, prompt, attachments, sending, waiting, tokenUsage, eventLogs, threads, activeThreadId, workspacePath, loadingThreads, activeTab, confirmTools, permissionMode, activity, connectError, pendingTool, pendingApprovals } = useAgentStore(
|
||||
const { width, url, token, connected, enabled, prompt, attachments, sending, waiting, tokenUsage, eventLogs, threads, activeThreadId, workspacePath, loadingThreads, activeTab, confirmTools, permissionMode, models, model, reasoningEffort, activity, connectError, pendingTool, pendingApprovals } = useAgentStore(
|
||||
useShallow((state) => ({
|
||||
width: state.width,
|
||||
url: state.url,
|
||||
@@ -99,6 +100,9 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
|
||||
activeTab: state.activeTab,
|
||||
confirmTools: state.confirmTools,
|
||||
permissionMode: state.permissionMode,
|
||||
models: state.models,
|
||||
model: state.model,
|
||||
reasoningEffort: state.reasoningEffort,
|
||||
activity: state.activity,
|
||||
connectError: state.connectError,
|
||||
pendingTool: state.pendingTool,
|
||||
@@ -278,6 +282,21 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
|
||||
if (connected) void loadThreads();
|
||||
}, [connected, loadThreads]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connected) return;
|
||||
void fetchAgentJson<AgentModelsResponse>(endpoint, token, "/agent/codex/models").then(({ data = [] }) => {
|
||||
if (!data.length) return;
|
||||
const savedModel = useAgentStore.getState().model;
|
||||
const current = data.find((item) => item.model === savedModel) || data.find((item) => item.isDefault) || data[0];
|
||||
const savedEffort = useAgentStore.getState().reasoningEffort;
|
||||
const efforts = current.supportedReasoningEfforts.map((item) => item.reasoningEffort);
|
||||
const nextEffort = efforts.includes(savedEffort as AgentReasoningEffort) ? savedEffort as AgentReasoningEffort : current.defaultReasoningEffort || efforts[0];
|
||||
localStorage.setItem("canvas-agent-model", current.model);
|
||||
localStorage.setItem("canvas-agent-reasoning-effort", nextEffort);
|
||||
setAgentState({ models: data, model: current.model, reasoningEffort: nextEffort });
|
||||
}).catch((error) => addEventLog("读取模型列表失败", error));
|
||||
}, [connected, endpoint, setAgentState, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connected) return;
|
||||
const activate = () => void activateAgentClient(endpoint, token, clientIdRef.current);
|
||||
@@ -325,6 +344,8 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
|
||||
clientId: clientIdRef.current,
|
||||
threadId,
|
||||
permissionMode,
|
||||
model,
|
||||
effort: reasoningEffort,
|
||||
attachments: files.map(({ id, name, type, size, width, height, dataUrl }) => ({ id, name, type, size, width, height, dataUrl })),
|
||||
}),
|
||||
});
|
||||
@@ -904,6 +925,21 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
|
||||
onConfirmToolsChange={(confirmTools) => setAgentState({ confirmTools })}
|
||||
permissionMode={permissionMode}
|
||||
onPermissionModeChange={changePermissionMode}
|
||||
models={models}
|
||||
model={model}
|
||||
reasoningEffort={reasoningEffort}
|
||||
onModelChange={(model) => {
|
||||
const selected = models.find((item) => item.model === model);
|
||||
if (!selected) return;
|
||||
const effort = selected.defaultReasoningEffort || selected.supportedReasoningEfforts[0]?.reasoningEffort;
|
||||
localStorage.setItem("canvas-agent-model", model);
|
||||
if (effort) localStorage.setItem("canvas-agent-reasoning-effort", effort);
|
||||
setAgentState({ model, ...(effort ? { reasoningEffort: effort } : {}) });
|
||||
}}
|
||||
onReasoningEffortChange={(reasoningEffort) => {
|
||||
localStorage.setItem("canvas-agent-reasoning-effort", reasoningEffort);
|
||||
setAgentState({ reasoningEffort });
|
||||
}}
|
||||
left={
|
||||
attachments.length ? (
|
||||
<span className="text-[11px]" style={{ color: theme.node.muted }}>
|
||||
|
||||
Reference in New Issue
Block a user