|
|
@@ -7,7 +7,7 @@
|
|
|
type="warning"
|
|
|
show-icon
|
|
|
message="本功能仅在桌面客户端可用"
|
|
|
- description="Skill 与 MCP 由本机 Codex 运行时管理,需要通过桌面客户端的主进程通道读写。当前是浏览器环境,未发起任何调用。"
|
|
|
+ description="Skill 与 MCP 由本机运行时管理,需要通过桌面客户端的主进程通道读写。当前是浏览器环境,未发起任何调用。"
|
|
|
/>
|
|
|
|
|
|
<!-- 页签栏即顶部条:页签居左,摘要与操作经 rightExtra 落在同一行右侧 -->
|
|
|
@@ -30,7 +30,7 @@
|
|
|
</template>
|
|
|
|
|
|
<div class="ai-plugin__hint">
|
|
|
- MCP 服务写在本机 Codex 配置(<b>CODEX_HOME/config.toml</b> 的 <b>mcp_servers</b> 段),保存后
|
|
|
+ MCP 服务写在本机配置文件(<b>config.toml</b> 的 <b>mcp_servers</b> 段),保存后
|
|
|
<b>热生效、无需重启</b>。出于安全考虑只接受 argv 数组形式的命令,HTTP 端点的凭据只能引用
|
|
|
<b>环境变量名</b>,不接受明文 token。
|
|
|
</div>
|
|
|
@@ -89,7 +89,7 @@
|
|
|
</article>
|
|
|
</div>
|
|
|
<div v-else-if="!loading" class="ai-plugin__empty">
|
|
|
- 还没有配置 MCP 服务。点击「新增 MCP」添加一个 stdio 命令或 HTTP 端点,Codex 就能调用它的工具。
|
|
|
+ 还没有配置 MCP 服务。点击「新增 MCP」添加一个 stdio 命令或 HTTP 端点,对话模型就能调用它的工具。
|
|
|
</div>
|
|
|
</a-spin>
|
|
|
</a-tab-pane>
|
|
|
@@ -102,8 +102,8 @@
|
|
|
</template>
|
|
|
|
|
|
<div class="ai-plugin__hint">
|
|
|
- Skill 是带 <b>SKILL.md</b> 的目录,装在 <b>{{ status?.codexHome || 'CODEX_HOME' }}/skills</b> 下。 Codex
|
|
|
- 原生只提供「列出 / 启停」,<b>安装与卸载由本客户端负责</b>:目录名需为小写连字符命名, SKILL.md 的 frontmatter
|
|
|
+ Skill 是带 <b>SKILL.md</b> 的目录,装在本机 <b>skills</b> 目录下。
|
|
|
+ <b>安装与卸载由本客户端负责</b>:目录名需为小写连字符命名, SKILL.md 的 frontmatter
|
|
|
必须包含 <b>name</b> 与 <b>description</b>。标记为「内置」的 skill 不可删除。
|
|
|
</div>
|
|
|
|
|
|
@@ -150,12 +150,13 @@
|
|
|
</article>
|
|
|
</div>
|
|
|
<div v-else-if="!loading" class="ai-plugin__empty">
|
|
|
- Codex 当前没有可见的 skill。可以从目录或 zip 安装一个。
|
|
|
+ 当前没有可见的 skill。可以从目录或 zip 安装一个。
|
|
|
</div>
|
|
|
</a-spin>
|
|
|
</a-tab-pane>
|
|
|
|
|
|
- <!-- ── 运行时 ──────────────────────────────────────────── -->
|
|
|
+ <!-- 【已停用】「Codex 运行时」页签:工作台已与 Codex 解绑,整块功能与文案一并注释停用(代码保留,去掉注释即可恢复) -->
|
|
|
+ <!--
|
|
|
<a-tab-pane key="runtime" tab="Codex 运行时">
|
|
|
<div class="ai-plugin__hint">
|
|
|
本客户端<b>不做任何 Codex / OpenAI 账号登录</b>:模型来自后端「模型管理」, 选中后由主进程写入本机 Codex
|
|
|
@@ -280,6 +281,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</a-tab-pane>
|
|
|
+ -->
|
|
|
</a-tabs>
|
|
|
|
|
|
<!-- MCP 表单 -->
|
|
|
@@ -427,7 +429,7 @@
|
|
|
const bridgeAvailable = ref(codexApi.isCodexBridgeAvailable());
|
|
|
const activeTab = ref<'mcp' | 'skill' | 'runtime'>('mcp');
|
|
|
const loading = ref(false);
|
|
|
- const busy = ref(false);
|
|
|
+ // const busy = ref(false);
|
|
|
const installing = ref(false);
|
|
|
|
|
|
const status = ref<codexApi.CodexStatus | null>(null);
|
|
|
@@ -435,12 +437,12 @@
|
|
|
const skills = ref<codexApi.CodexSkill[]>([]);
|
|
|
const diagnostics = ref<codexApi.CodexDiagnostic[]>([]);
|
|
|
|
|
|
- const models = ref<AiModel[]>([]);
|
|
|
- const modelsLoading = ref(false);
|
|
|
- const selectedModelId = ref<string | undefined>(undefined);
|
|
|
- const probing = ref(false);
|
|
|
- const applying = ref(false);
|
|
|
- const probeResult = ref<codexApi.ProviderProbeResult | null>(null);
|
|
|
+ // const models = ref<AiModel[]>([]);
|
|
|
+ // const modelsLoading = ref(false);
|
|
|
+ // const selectedModelId = ref<string | undefined>(undefined);
|
|
|
+ // const probing = ref(false);
|
|
|
+ // const applying = ref(false);
|
|
|
+ // const probeResult = ref<codexApi.ProviderProbeResult | null>(null);
|
|
|
|
|
|
const zipInputRef = ref<HTMLInputElement | null>(null);
|
|
|
const skillPreviewOpen = ref(false);
|
|
|
@@ -485,56 +487,56 @@
|
|
|
const summary = computed(() => {
|
|
|
if (!bridgeAvailable.value) return '仅桌面客户端可用';
|
|
|
const parts = [`${mcpServers.value.length} 个 MCP`, `${skills.value.length} 个 Skill`];
|
|
|
- parts.push(status.value?.running ? `运行中 · ${status.value.defaultModel || '未配置模型'}` : '未启动');
|
|
|
+ // parts.push(status.value?.running ? `运行中 · ${status.value.defaultModel || '未配置模型'}` : '未启动');
|
|
|
return parts.join(' · ');
|
|
|
});
|
|
|
|
|
|
- const stateLabel = computed(() => {
|
|
|
- const state = status.value?.state;
|
|
|
- if (state === 'ready') return '运行中';
|
|
|
- if (state === 'starting') return '启动中';
|
|
|
- if (state === 'error') return '异常';
|
|
|
- return '已停止';
|
|
|
- });
|
|
|
-
|
|
|
- const stateClass = computed(() => {
|
|
|
- const state = status.value?.state;
|
|
|
- if (state === 'ready') return 'is-on';
|
|
|
- if (state === 'error') return 'is-err';
|
|
|
- return 'is-off';
|
|
|
- });
|
|
|
-
|
|
|
- /** 只列对话模型:Codex 是 agent,向量模型用不上 */
|
|
|
- const modelOptions = computed(() =>
|
|
|
- models.value
|
|
|
- .filter((item) => !item.type || item.type === 'llm')
|
|
|
- .map((item) => ({
|
|
|
- label: `${item.name || item.modelId || item.id}(${item.modelId || '—'})`,
|
|
|
- value: item.id,
|
|
|
- })),
|
|
|
- );
|
|
|
-
|
|
|
- const selectedModel = computed(() => models.value.find((item) => item.id === selectedModelId.value) || null);
|
|
|
-
|
|
|
- /** 探测结果:responses 直连(绿)/ chat-only 走内置路由层(蓝)/ 其余不能对接(红) */
|
|
|
- const probeAlertType = computed<'success' | 'info' | 'error'>(() => {
|
|
|
- const protocol = probeResult.value?.protocol;
|
|
|
- if (protocol === 'responses') return 'success';
|
|
|
- if (protocol === 'chat-only') return 'info';
|
|
|
- return 'error';
|
|
|
- });
|
|
|
- const probeAlertTitle = computed(() => {
|
|
|
- switch (probeResult.value?.protocol) {
|
|
|
- case 'responses':
|
|
|
- return '端点可用(Responses 协议,Codex 直连)';
|
|
|
- case 'chat-only':
|
|
|
- return '端点只会 Chat 协议,应用后将经内置路由层桥接对接';
|
|
|
- case 'unsupported':
|
|
|
- return '端点没有 Responses 协议路由,也无法按 Chat 对接,无法使用';
|
|
|
- default:
|
|
|
- return '端点不可达';
|
|
|
- }
|
|
|
- });
|
|
|
+ // const stateLabel = computed(() => {
|
|
|
+ // const state = status.value?.state;
|
|
|
+ // if (state === 'ready') return '运行中';
|
|
|
+ // if (state === 'starting') return '启动中';
|
|
|
+ // if (state === 'error') return '异常';
|
|
|
+ // return '已停止';
|
|
|
+ // });
|
|
|
+
|
|
|
+ // const stateClass = computed(() => {
|
|
|
+ // const state = status.value?.state;
|
|
|
+ // if (state === 'ready') return 'is-on';
|
|
|
+ // if (state === 'error') return 'is-err';
|
|
|
+ // return 'is-off';
|
|
|
+ // });
|
|
|
+
|
|
|
+ // /** 只列对话模型:Codex 是 agent,向量模型用不上 */
|
|
|
+ // const modelOptions = computed(() =>
|
|
|
+ // models.value
|
|
|
+ // .filter((item) => !item.type || item.type === 'llm')
|
|
|
+ // .map((item) => ({
|
|
|
+ // label: `${item.name || item.modelId || item.id}(${item.modelId || '—'})`,
|
|
|
+ // value: item.id,
|
|
|
+ // })),
|
|
|
+ // );
|
|
|
+
|
|
|
+ // const selectedModel = computed(() => models.value.find((item) => item.id === selectedModelId.value) || null);
|
|
|
+
|
|
|
+ // /** 探测结果:responses 直连(绿)/ chat-only 走内置路由层(蓝)/ 其余不能对接(红) */
|
|
|
+ // const probeAlertType = computed<'success' | 'info' | 'error'>(() => {
|
|
|
+ // const protocol = probeResult.value?.protocol;
|
|
|
+ // if (protocol === 'responses') return 'success';
|
|
|
+ // if (protocol === 'chat-only') return 'info';
|
|
|
+ // return 'error';
|
|
|
+ // });
|
|
|
+ // const probeAlertTitle = computed(() => {
|
|
|
+ // switch (probeResult.value?.protocol) {
|
|
|
+ // case 'responses':
|
|
|
+ // return '端点可用(Responses 协议,Codex 直连)';
|
|
|
+ // case 'chat-only':
|
|
|
+ // return '端点只会 Chat 协议,应用后将经内置路由层桥接对接';
|
|
|
+ // case 'unsupported':
|
|
|
+ // return '端点没有 Responses 协议路由,也无法按 Chat 对接,无法使用';
|
|
|
+ // default:
|
|
|
+ // return '端点不可达';
|
|
|
+ // }
|
|
|
+ // });
|
|
|
|
|
|
function mcpTarget(record: codexApi.CodexMcpServer) {
|
|
|
if (record.transport === 'http') return record.url || '—';
|
|
|
@@ -565,7 +567,7 @@
|
|
|
}),
|
|
|
);
|
|
|
await reload();
|
|
|
- await loadModels();
|
|
|
+ // await loadModels();
|
|
|
});
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
@@ -594,124 +596,124 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async function loadModels() {
|
|
|
- modelsLoading.value = true;
|
|
|
- try {
|
|
|
- const list = await modelApi.listModels();
|
|
|
- models.value = list || [];
|
|
|
- const applied = status.value?.applied;
|
|
|
- selectedModelId.value = applied?.modelRecordId || models.value.find((m) => m.defaultModel)?.id || undefined;
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '加载模型列表失败(需要登录后端)', 'error');
|
|
|
- } finally {
|
|
|
- modelsLoading.value = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /** 把后端模型记录翻译成主进程要的入参 */
|
|
|
- function buildApplyInput(model: AiModel): codexApi.ApplyProviderInput {
|
|
|
- return {
|
|
|
- modelId: model.modelId || '',
|
|
|
- name: model.name || null,
|
|
|
- baseUrl: model.baseUrl || null,
|
|
|
- apiKey: model.apiKey || null,
|
|
|
- headersJson: model.headersJson || null,
|
|
|
- config: model.config || null,
|
|
|
- modelRecordId: model.id,
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- async function probeSelected() {
|
|
|
- const model = selectedModel.value;
|
|
|
- if (!model) return;
|
|
|
- probing.value = true;
|
|
|
- probeResult.value = null;
|
|
|
- try {
|
|
|
- const input = buildApplyInput(model);
|
|
|
- probeResult.value = await codexApi.codexProbeProvider({
|
|
|
- baseUrl: input.baseUrl || '',
|
|
|
- apiKey: input.apiKey,
|
|
|
- modelId: input.modelId,
|
|
|
- });
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '探测失败', 'error');
|
|
|
- } finally {
|
|
|
- probing.value = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- async function applySelected() {
|
|
|
- const model = selectedModel.value;
|
|
|
- if (!model) return;
|
|
|
- applying.value = true;
|
|
|
- try {
|
|
|
- const result = await codexApi.codexApplyProvider(buildApplyInput(model));
|
|
|
- status.value = result.status;
|
|
|
- probeResult.value = null;
|
|
|
- showMessage(`已应用模型「${result.applied.model}」`, 'success');
|
|
|
- if (result.droppedConfigKeys.length) {
|
|
|
- showMessage(`config 中不支持的键已忽略:${result.droppedConfigKeys.join('、')}`, 'warning');
|
|
|
- }
|
|
|
- if (result.hint) showMessage(result.hint, 'warning');
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '应用模型失败', 'error');
|
|
|
- } finally {
|
|
|
- applying.value = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- async function clearApplied() {
|
|
|
- try {
|
|
|
- status.value = await codexApi.codexClearProvider();
|
|
|
- showMessage('已清除模型配置', 'success');
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '清除失败', 'error');
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- async function startRuntime() {
|
|
|
- busy.value = true;
|
|
|
- try {
|
|
|
- status.value = await codexApi.codexStart();
|
|
|
- showMessage('Codex 运行时已启动', 'success');
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '启动失败', 'error');
|
|
|
- } finally {
|
|
|
- busy.value = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- async function restartRuntime() {
|
|
|
- busy.value = true;
|
|
|
- try {
|
|
|
- status.value = await codexApi.codexRestart();
|
|
|
- showMessage('Codex 运行时已重启', 'success');
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '重启失败', 'error');
|
|
|
- } finally {
|
|
|
- busy.value = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- async function stopRuntime() {
|
|
|
- busy.value = true;
|
|
|
- try {
|
|
|
- status.value = await codexApi.codexStop();
|
|
|
- showMessage('Codex 运行时已停止', 'success');
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '停止失败', 'error');
|
|
|
- } finally {
|
|
|
- busy.value = false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- async function loadDiagnostics() {
|
|
|
- try {
|
|
|
- diagnostics.value = await codexApi.codexLogsTail(200);
|
|
|
- } catch (e: any) {
|
|
|
- showMessage(e?.message || '读取诊断日志失败', 'error');
|
|
|
- }
|
|
|
- }
|
|
|
+ // async function loadModels() {
|
|
|
+ // modelsLoading.value = true;
|
|
|
+ // try {
|
|
|
+ // const list = await modelApi.listModels();
|
|
|
+ // models.value = list || [];
|
|
|
+ // const applied = status.value?.applied;
|
|
|
+ // selectedModelId.value = applied?.modelRecordId || models.value.find((m) => m.defaultModel)?.id || undefined;
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '加载模型列表失败(需要登录后端)', 'error');
|
|
|
+ // } finally {
|
|
|
+ // modelsLoading.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // /** 把后端模型记录翻译成主进程要的入参 */
|
|
|
+ // function buildApplyInput(model: AiModel): codexApi.ApplyProviderInput {
|
|
|
+ // return {
|
|
|
+ // modelId: model.modelId || '',
|
|
|
+ // name: model.name || null,
|
|
|
+ // baseUrl: model.baseUrl || null,
|
|
|
+ // apiKey: model.apiKey || null,
|
|
|
+ // headersJson: model.headersJson || null,
|
|
|
+ // config: model.config || null,
|
|
|
+ // modelRecordId: model.id,
|
|
|
+ // };
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function probeSelected() {
|
|
|
+ // const model = selectedModel.value;
|
|
|
+ // if (!model) return;
|
|
|
+ // probing.value = true;
|
|
|
+ // probeResult.value = null;
|
|
|
+ // try {
|
|
|
+ // const input = buildApplyInput(model);
|
|
|
+ // probeResult.value = await codexApi.codexProbeProvider({
|
|
|
+ // baseUrl: input.baseUrl || '',
|
|
|
+ // apiKey: input.apiKey,
|
|
|
+ // modelId: input.modelId,
|
|
|
+ // });
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '探测失败', 'error');
|
|
|
+ // } finally {
|
|
|
+ // probing.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function applySelected() {
|
|
|
+ // const model = selectedModel.value;
|
|
|
+ // if (!model) return;
|
|
|
+ // applying.value = true;
|
|
|
+ // try {
|
|
|
+ // const result = await codexApi.codexApplyProvider(buildApplyInput(model));
|
|
|
+ // status.value = result.status;
|
|
|
+ // probeResult.value = null;
|
|
|
+ // showMessage(`已应用模型「${result.applied.model}」`, 'success');
|
|
|
+ // if (result.droppedConfigKeys.length) {
|
|
|
+ // showMessage(`config 中不支持的键已忽略:${result.droppedConfigKeys.join('、')}`, 'warning');
|
|
|
+ // }
|
|
|
+ // if (result.hint) showMessage(result.hint, 'warning');
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '应用模型失败', 'error');
|
|
|
+ // } finally {
|
|
|
+ // applying.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function clearApplied() {
|
|
|
+ // try {
|
|
|
+ // status.value = await codexApi.codexClearProvider();
|
|
|
+ // showMessage('已清除模型配置', 'success');
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '清除失败', 'error');
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function startRuntime() {
|
|
|
+ // busy.value = true;
|
|
|
+ // try {
|
|
|
+ // status.value = await codexApi.codexStart();
|
|
|
+ // showMessage('Codex 运行时已启动', 'success');
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '启动失败', 'error');
|
|
|
+ // } finally {
|
|
|
+ // busy.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function restartRuntime() {
|
|
|
+ // busy.value = true;
|
|
|
+ // try {
|
|
|
+ // status.value = await codexApi.codexRestart();
|
|
|
+ // showMessage('Codex 运行时已重启', 'success');
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '重启失败', 'error');
|
|
|
+ // } finally {
|
|
|
+ // busy.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function stopRuntime() {
|
|
|
+ // busy.value = true;
|
|
|
+ // try {
|
|
|
+ // status.value = await codexApi.codexStop();
|
|
|
+ // showMessage('Codex 运行时已停止', 'success');
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '停止失败', 'error');
|
|
|
+ // } finally {
|
|
|
+ // busy.value = false;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // async function loadDiagnostics() {
|
|
|
+ // try {
|
|
|
+ // diagnostics.value = await codexApi.codexLogsTail(200);
|
|
|
+ // } catch (e: any) {
|
|
|
+ // showMessage(e?.message || '读取诊断日志失败', 'error');
|
|
|
+ // }
|
|
|
+ // }
|
|
|
|
|
|
// ── MCP ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
@@ -775,7 +777,7 @@
|
|
|
function removeMcp(record: any) {
|
|
|
Modal.confirm({
|
|
|
title: '删除 MCP 服务',
|
|
|
- content: `确认删除「${record.id}」?该配置只影响本机 Codex。`,
|
|
|
+ content: `确认删除「${record.id}」?该配置只影响本机运行环境。`,
|
|
|
okText: '删除',
|
|
|
okType: 'primary',
|
|
|
okButtonProps: { danger: true },
|