From 65a91b22e1cc6ffd1db9fc74f8a65f181ae255a7 Mon Sep 17 00:00:00 2001 From: HouYunFei <1844025705@qq.com> Date: Sun, 5 Jul 2026 15:12:42 +0800 Subject: [PATCH] feat: add Codex App plugin support with dedicated configuration page and connection settings --- CHANGELOG.md | 1 + docs/content/docs/progress/pending-test.mdx | 2 + .../components/layout/app-config-modal.tsx | 123 ++++++++++++++---- web/src/components/layout/app-top-nav.tsx | 6 +- web/src/constant/navigation-tools.ts | 7 +- web/src/pages/config/index.tsx | 15 +++ web/src/router.tsx | 2 + 7 files changed, 130 insertions(+), 26 deletions(-) create mode 100644 web/src/pages/config/index.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 271324c..fe2b4e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased + [新增] 新增Codex App插件支持。 ++ [新增] 配置与用户偏好新增独立页面和 Codex 连接配置 Tab。 + [修复] 修复生图工作台重试成功结果刷新后丢失的问题。 ## v0.5.0 - 2026-07-05 diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index d8bcea3..ae5de8d 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -6,6 +6,8 @@ description: 当前版本已实现但仍需人工验证的变更项 # 待测试 - Codex App 插件文档:新增插件安装教程,区分 AI 自动安装、手动安装和本仓库开发调试安装;同步优化插件 README 与技能说明。 +- 顶部导航:缩小桌面顶部导航栏高度,导航项下划线和内容垂直对齐需确认。 +- 配置与用户偏好:新增独立顶部导航页面,并在页面和弹窗中复用同一套配置面板;新增 Codex Tab,展示插件安装和本地 Agent 启动步骤,可配置本地 Agent 地址、Connect token 和工具执行确认开关。 - 配置与用户偏好:每个模型渠道新增 OpenAI / Gemini 调用格式选择,默认 OpenAI,切换默认格式时同步更新默认 Base URL;渠道 Tab 增加紧凑醒目的提醒,说明模型是否可选需要到“模型”Tab 选择。 - Gemini 调用格式:支持拉取 Gemini 模型列表,并用于文本问答、在线 Agent 工具调用、基础生图和图生图请求。 - 音频和视频生成:选择 Gemini 调用格式时先给出不支持提示,仍需使用 OpenAI 兼容渠道。 diff --git a/web/src/components/layout/app-config-modal.tsx b/web/src/components/layout/app-config-modal.tsx index 04b26b9..7a52513 100644 --- a/web/src/components/layout/app-config-modal.tsx +++ b/web/src/components/layout/app-config-modal.tsx @@ -1,5 +1,5 @@ -import { App, Button, Form, Input, Modal, Progress, Select, Tabs } from "antd"; -import { CircleAlert, Cloud, Plus, RefreshCw, Trash2, Wifi } from "lucide-react"; +import { App, Button, Form, Input, Modal, Progress, Select, Switch, Tabs } from "antd"; +import { CircleAlert, Cloud, KeyRound, Link2, Plus, RefreshCw, ShieldCheck, Trash2, Wifi } from "lucide-react"; import { useState } from "react"; import { ModelPicker } from "@/components/model-picker"; @@ -7,6 +7,7 @@ import { fetchChannelModels } from "@/services/api/image"; import { syncAppDataToWebdav, type AppSyncDomainKey, type AppSyncProgressEvent } from "@/services/app-sync"; import { testWebdavConnection, WEBDAV_MANIFEST_FILE_NAME } from "@/services/webdav-sync"; import { audioFormatOptions, audioVoiceOptions, normalizeAudioSpeedValue } from "@/lib/audio-generation"; +import { useCanvasAgentStore } from "@/stores/canvas/use-canvas-agent-store"; import { createModelChannel, defaultBaseUrlForApiFormat, filterModelsByCapability, modelOptionLabel, modelOptionsFromChannels, normalizeModelOptionValue, useConfigStore, type AiConfig, type ApiCallFormat, type ModelCapability, type ModelChannel } from "@/stores/use-config-store"; type ModelGroup = { @@ -44,6 +45,11 @@ const webdavDomainLabels: Record = { "image-workbench": "生图工作台", "video-workbench": "视频创作台", }; +const codexSetupSteps = [ + { title: "安装 Codex 插件", text: "先在 Codex App 安装 Infinite Canvas 插件,插件会注册 MCP 并尝试启动本地 Canvas Agent。" }, + { title: "打开画布 Agent", text: "回到画布右侧 Agent 面板选择本地模式,网页会自动读取 Local URL 和 Connect token。" }, + { title: "手动启动备用", text: "如果插件没有自动启动本地服务,在终端运行下面命令。", command: "npx -y @basketikun/canvas-agent" }, +]; function createWebdavDomainProgress(): Record { return webdavDomainKeys.reduce( @@ -55,7 +61,7 @@ function createWebdavDomainProgress(): Record state.webdav); const updateConfig = useConfigStore((state) => state.updateConfig); const updateWebdavConfig = useConfigStore((state) => state.updateWebdavConfig); - const isConfigOpen = useConfigStore((state) => state.isConfigOpen); const shouldPromptContinue = useConfigStore((state) => state.shouldPromptContinue); const setConfigDialogOpen = useConfigStore((state) => state.setConfigDialogOpen); const clearPromptContinue = useConfigStore((state) => state.clearPromptContinue); + const agentUrl = useCanvasAgentStore((state) => state.url); + const agentToken = useCanvasAgentStore((state) => state.token); + const agentConnected = useCanvasAgentStore((state) => state.connected); + const agentEnabled = useCanvasAgentStore((state) => state.enabled); + const agentActivity = useCanvasAgentStore((state) => state.activity); + const agentConfirmTools = useCanvasAgentStore((state) => state.confirmTools); + const setAgentState = useCanvasAgentStore((state) => state.setAgentState); const modelOptions = config.models.map((model) => ({ label: modelOptionLabel(config, model), value: model })); const webdavReady = Boolean(webdav.url.trim()); @@ -205,25 +217,14 @@ export function AppConfigModal() { } }; + const updateAgentConfig = (patch: { url?: string; token?: string }) => { + setAgentState(patch); + if (patch.url !== undefined) localStorage.setItem("canvas-agent-url", patch.url.trim().replace(/\/$/, "")); + if (patch.token !== undefined) localStorage.setItem("canvas-agent-token", patch.token); + }; + return ( - -
配置与用户偏好
-
渠道聚合、模型选择和同步偏好
- - } - open={isConfigOpen} - width={980} - centered - onCancel={() => setConfigDialogOpen(false)} - styles={{ body: { maxHeight: "72vh", overflowY: "auto", paddingRight: 12 } }} - footer={ - - } - > + <> ), }, + { + key: "codex", + label: "Codex", + children: ( +
+
+
+
+
+ + 连接本地 Codex +
+
用于画布 Agent 连接本机 Codex 插件启动的 Canvas Agent。
+
+
{agentConnected ? agentActivity || "已连接" : agentEnabled ? "连接中" : "未连接"}
+
+
+ {codexSetupSteps.map((step, index) => ( +
+
步骤 {index + 1}
+
{step.title}
+
{step.text}
+ {step.command ? {step.command} : null} +
+ ))} +
+
+ + } value={agentUrl} placeholder="http://127.0.0.1:17371" onChange={(event) => updateAgentConfig({ url: event.target.value })} /> + + + } value={agentToken} placeholder="自动发现,或手动填入 Connect token" onChange={(event) => updateAgentConfig({ token: event.target.value })} /> + +
+
+
+ +
+
执行画布操作前确认
+
关闭后,本地 Codex 可直接执行画布工具调用。
+
+
+ setAgentState({ confirmTools })} /> +
+
+
+ ), + }, ]} /> + {showDoneButton ? ( +
+ +
+ ) : null} + + ); +} + +export function AppConfigModal() { + const isConfigOpen = useConfigStore((state) => state.isConfigOpen); + const setConfigDialogOpen = useConfigStore((state) => state.setConfigDialogOpen); + return ( + +
配置与用户偏好
+
渠道聚合、模型选择和同步偏好
+ + } + open={isConfigOpen} + width={980} + centered + onCancel={() => setConfigDialogOpen(false)} + styles={{ body: { maxHeight: "72vh", overflowY: "auto", paddingRight: 12 } }} + footer={null} + > +
); } diff --git a/web/src/components/layout/app-top-nav.tsx b/web/src/components/layout/app-top-nav.tsx index 326bf41..ea3cf7a 100644 --- a/web/src/components/layout/app-top-nav.tsx +++ b/web/src/components/layout/app-top-nav.tsx @@ -18,7 +18,7 @@ export function AppTopNav() { return ( <> {!hideHeader ? ( -
+
@@ -42,7 +42,7 @@ export function AppTopNav() { -