Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/web/pages/agents/codex-button-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const CODEX_NEW_THREAD_URL = 'codex://threads/new';

function buildCodexNewThreadUrl({ prompt, originUrl }) {
const params = new URLSearchParams();
params.set('prompt', String(prompt || ''));

if (originUrl) {
params.set('originUrl', String(originUrl));
}

return `${CODEX_NEW_THREAD_URL}?${params.toString()}`;
}

function buildAgentDetailCodexPrompt(detail = {}, _originUrl, selectedPrompt) {
const firstPrompt =
selectedPrompt || (Array.isArray(detail.prompts) ? detail.prompts[0] : null);
return firstPrompt ? String(firstPrompt.prompt || '') : '';
}

module.exports = {
buildAgentDetailCodexPrompt,
buildCodexNewThreadUrl,
};
26 changes: 26 additions & 0 deletions app/web/pages/agents/detail/AgentDetailContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
CodeOutlined,
CopyOutlined,
DownloadOutlined,
MessageOutlined,
Expand All @@ -13,6 +14,7 @@ import { Button, Card, Empty, message, Spin, Tabs, Tag, Typography } from 'antd'
import { API } from '@/api';
import { copyToClipboard } from '@/utils/copyUtils';
import { safeOpenUrl } from '@/utils/safeOpenUrl';
import { buildAgentDetailCodexPrompt, buildCodexNewThreadUrl } from '../codex-button-utils';
import type { AgentCapability, AgentDetail, AgentItem, AgentSkillRelation } from '../types';
import './style.scss';

Expand Down Expand Up @@ -146,6 +148,16 @@ const AgentDetailContent: React.FC<AgentDetailContentProps> = ({ name, history }
? `curl -fsSL ${currentOrigin}/agent-market/install.sh | bash -s -- ${detail.name}`
: '';

const openCodexInstall = (selectedPrompt?: { title?: string; prompt?: string }) => {
if (!detail) return;

const originUrl = typeof window !== 'undefined' ? window.location.href : '';
const prompt = buildAgentDetailCodexPrompt(detail, originUrl, selectedPrompt);
const codexUrl = buildCodexNewThreadUrl({ prompt, originUrl });

window.location.href = codexUrl;
};

if (loading) {
return (
<div className="page-agent-detail loading-wrap">
Expand Down Expand Up @@ -364,6 +376,20 @@ const AgentDetailContent: React.FC<AgentDetailContentProps> = ({ name, history }
{item.prompt}
</Paragraph>
</div>
<Button
size="small"
className="agent-question-quick-use"
icon={
<span className="agent-question-quick-use-icon">
<CodeOutlined />
</span>
}
onClick={() =>
openCodexInstall(item)
}
>
快捷使用
</Button>
</div>
</Card>
)
Expand Down
56 changes: 54 additions & 2 deletions app/web/pages/agents/detail/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,15 @@
}
.agent-question-card-body {
display: grid;
grid-template-columns: 32px minmax(0, 1fr);
grid-template-columns: 32px minmax(0, 1fr) auto;
align-items: center;
gap: 12px;
gap: 16px;
}
.agent-question-copy {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
.ant-typography:first-child {
display: block;
margin-bottom: 4px;
Expand All @@ -362,6 +365,48 @@
font-size: 14px;
}
}
.agent-question-quick-use {
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
padding: 0 16px 0 12px;
border-color: #D8DFEA;
border-radius: 10px;
color: #344054;
background: #FFF;
font-weight: 600;
white-space: nowrap;
box-shadow: none;
transition:
transform 0.18s ease,
border-color 0.18s ease,
color 0.18s ease,
background 0.18s ease,
box-shadow 0.18s ease;
&:hover,
&:focus {
border-color: #F6B85A;
color: #B66A00;
background: #FFF9EF;
box-shadow: 0 6px 14px rgba(245, 158, 11, 0.12);
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
box-shadow: 0 3px 8px rgba(245, 158, 11, 0.1);
}
.anticon {
margin-right: 8px;
}
}
.agent-question-quick-use-icon {
display: inline-flex;
align-items: center;
justify-content: center;
color: #F59E0B;
font-size: 14px;
}
.agent-skill-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
Expand Down Expand Up @@ -465,5 +510,12 @@
flex-direction: column;
align-items: flex-start;
}
.agent-question-card-body {
grid-template-columns: 32px minmax(0, 1fr);
}
.agent-question-quick-use {
grid-column: 2;
justify-self: flex-end;
}
}
}
53 changes: 53 additions & 0 deletions test/agent-codex-button-utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const test = require('node:test');
const assert = require('node:assert/strict');

const {
buildAgentDetailCodexPrompt,
buildCodexNewThreadUrl,
} = require('../app/web/pages/agents/codex-button-utils');

test('buildCodexNewThreadUrl builds a codex new thread deep link with encoded prompt and origin', () => {
const url = buildCodexNewThreadUrl({
prompt: '打开 Agent 市场\n使用示例',
originUrl: 'http://10.10.10.168:7001/page/agents?keyword=AI Agent',
});

assert.equal(url.startsWith('codex://threads/new?'), true);
assert.equal(url.includes('prompt='), true);
assert.equal(url.includes('originUrl='), true);
assert.equal(url.includes('AI+Agent'), true);
assert.equal(url.includes('\n'), false);
});

test('buildAgentDetailCodexPrompt returns only the opening question prompt', () => {
const prompt = buildAgentDetailCodexPrompt(
{
displayName: 'Bug 修复 Agent',
name: 'bugfix-agent',
description: '用于修复 Bug',
entrypoint: { slug: 'bugfix-workflow', name: 'Bug 修复工作流' },
dependencies: [
{ slug: 'zentao-api', name: '禅道 API' },
{ slug: 'gitlab-mr-ci-watch', name: 'MR CI 观察' },
],
prompts: [{ title: '修复 Bug', prompt: '$bugfix-workflow 12345' }],
},
'http://10.10.10.168:7001/page/agents/bugfix-agent'
);

assert.equal(prompt, '$bugfix-workflow 12345');
});

test('buildAgentDetailCodexPrompt can use the selected opening question', () => {
const prompt = buildAgentDetailCodexPrompt(
{
displayName: 'Bug 修复 Agent',
name: 'bugfix-agent',
prompts: [{ title: '默认问题', prompt: '$bugfix-workflow 默认' }],
},
'http://10.10.10.168:7001/page/agents/bugfix-agent',
{ title: '自然语言', prompt: '帮我修 bug,禅道 Bug ID 是 156343' }
);

assert.equal(prompt, '帮我修 bug,禅道 Bug ID 是 156343');
});
40 changes: 40 additions & 0 deletions test/agent-detail-layout.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,46 @@ test('Agent 详情右侧展示可复制的自动安装命令', () => {
);
});

test('Agent 开场问题卡片提供调起 Codex 的快捷使用入口', () => {
const content = fs.readFileSync(
path.join(__dirname, '../app/web/pages/agents/detail/AgentDetailContent.tsx'),
'utf8'
);
const styleContent = fs.readFileSync(
path.join(__dirname, '../app/web/pages/agents/detail/style.scss'),
'utf8'
);

assert.equal(
content.includes('buildAgentDetailCodexPrompt') &&
content.includes('buildCodexNewThreadUrl') &&
content.includes('className="agent-question-quick-use"') &&
content.includes('agent-question-quick-use-icon') &&
content.includes('onClick={() => openCodexInstall(item)}') &&
content.includes('快捷使用'),
true,
'开场问题卡片需要提供快捷使用按钮并调起 Codex'
);
assert.equal(
content.includes('className="agent-quick-use"'),
false,
'右侧不应再展示独立快捷使用按钮'
);
assert.equal(
styleContent.includes('.agent-question-quick-use') &&
styleContent.includes('grid-template-columns: 32px minmax(0, 1fr) auto;') &&
styleContent.includes('align-items: center;') &&
styleContent.includes('&:hover,') &&
styleContent.includes('transform: translateY(-1px);') &&
styleContent.includes('border-color: #D8DFEA;') &&
styleContent.includes('.agent-question-quick-use-icon') &&
!styleContent.includes('border: 1px solid #F59E0B;') &&
!styleContent.includes('background: rgba(245, 158, 11, 0.12);'),
true,
'开场问题快捷使用按钮需要左右布局、垂直居中并提供克制的 hover 样式'
);
});

test('Agent 详情右侧提供当前原始 ZIP 下载入口', () => {
const content = fs.readFileSync(
path.join(__dirname, '../app/web/pages/agents/detail/AgentDetailContent.tsx'),
Expand Down
Loading