mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-03-27 10:05:50 +00:00
- Add database migration and repository for project review prompts - Add API endpoint for setting project-level prompts - Integrate project prompts into Agent and Codex review flows - Redesign repository management UI with dialog-based prompt editor - Replace flat buttons with Switch for webhook toggle and dedicated prompt button - Add Dialog and DropdownMenu UI components from Radix UI - Add comprehensive tests for wiring and interactions
404 lines
10 KiB
TypeScript
404 lines
10 KiB
TypeScript
import type { Page, Route } from '@playwright/test';
|
|
|
|
const repositories = {
|
|
data: [
|
|
{
|
|
name: 'demo-repo-1',
|
|
webhook_status: 'active',
|
|
hook_id: 101,
|
|
project_review_prompt: '重点检查 API 错误处理与鉴权边界。',
|
|
},
|
|
{
|
|
name: 'demo-repo-2',
|
|
webhook_status: 'inactive',
|
|
hook_id: null,
|
|
project_review_prompt: null,
|
|
},
|
|
],
|
|
totalCount: 2,
|
|
page: 1,
|
|
limit: 30,
|
|
};
|
|
|
|
const configResponse = {
|
|
groups: [
|
|
{
|
|
key: 'gitea',
|
|
label: 'Gitea 连接',
|
|
description: '配置 Gitea 服务地址和访问凭据。',
|
|
icon: 'git-branch',
|
|
fields: [
|
|
{
|
|
envKey: 'GITEA_BASE_URL',
|
|
label: 'Gitea 地址',
|
|
description: 'Gitea API 根地址',
|
|
type: 'url',
|
|
sensitive: false,
|
|
value: 'https://gitea.example.com',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: 'notification',
|
|
label: '通知服务',
|
|
description: '配置飞书与企业微信通知。',
|
|
icon: 'bell',
|
|
fields: [
|
|
{
|
|
envKey: 'FEISHU_ENABLED',
|
|
label: '启用飞书通知',
|
|
description: '是否启用飞书通知',
|
|
type: 'boolean',
|
|
sensitive: false,
|
|
value: true,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'FEISHU_WEBHOOK_URL',
|
|
label: '飞书 Webhook URL',
|
|
description: '用于发送飞书通知',
|
|
type: 'url',
|
|
sensitive: false,
|
|
value: 'https://open.feishu.cn/mock/webhook',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'WECOM_ENABLED',
|
|
label: '启用企业微信通知',
|
|
description: '是否启用企业微信通知',
|
|
type: 'boolean',
|
|
sensitive: false,
|
|
value: false,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: 'security',
|
|
label: '安全设置',
|
|
description: '控制签名校验与访问策略。',
|
|
icon: 'shield',
|
|
fields: [
|
|
{
|
|
envKey: 'ENABLE_SIGNATURE_VERIFY',
|
|
label: '启用签名校验',
|
|
description: '是否开启 webhook 签名验证',
|
|
type: 'boolean',
|
|
sensitive: false,
|
|
value: true,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: 'review',
|
|
label: '审查设置',
|
|
description: '控制审查引擎与执行策略。',
|
|
icon: 'search',
|
|
fields: [
|
|
{
|
|
envKey: 'REVIEW_ENGINE',
|
|
label: '审查引擎',
|
|
description: '当前使用的审查引擎',
|
|
type: 'enum',
|
|
enumValues: ['agent', 'codex'],
|
|
sensitive: false,
|
|
value: 'agent',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'GLOBAL_PROMPT',
|
|
label: '全局提示词',
|
|
description: '审查上下文提示词模板',
|
|
type: 'text',
|
|
sensitive: false,
|
|
value: 'Review with focus on correctness and maintainability.',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'REVIEW_WORKDIR',
|
|
label: '审查工作目录',
|
|
description: '任务执行工作目录',
|
|
type: 'string',
|
|
sensitive: false,
|
|
value: '/tmp/review-workdir',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'REVIEW_MAX_PARALLEL_RUNS',
|
|
label: '最大并发任务',
|
|
description: '控制并发执行数量',
|
|
type: 'number',
|
|
sensitive: false,
|
|
value: 4,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'REVIEW_MAX_FILES_PER_RUN',
|
|
label: '单次最大文件数',
|
|
description: '每轮审查最大文件数',
|
|
type: 'number',
|
|
sensitive: false,
|
|
value: 40,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'REVIEW_MAX_FILE_CONTENT_CHARS',
|
|
label: '单文件最大字符',
|
|
description: '单文件读取上限',
|
|
type: 'number',
|
|
sensitive: false,
|
|
value: 20000,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'LLM_MAX_CONCURRENT_CALLS',
|
|
label: 'LLM 并发调用数',
|
|
description: '限制并发调用',
|
|
type: 'number',
|
|
sensitive: false,
|
|
value: 4,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'CODEX_MODEL',
|
|
label: 'Codex 模型',
|
|
description: 'Codex 模式默认模型',
|
|
type: 'string',
|
|
sensitive: false,
|
|
value: 'gpt-4o-mini',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
{
|
|
envKey: 'CODEX_API_URL',
|
|
label: 'Codex API 地址',
|
|
description: 'Codex 服务地址',
|
|
type: 'url',
|
|
sensitive: false,
|
|
value: 'https://api.openai.com/v1',
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: 'memory',
|
|
label: '记忆设置',
|
|
description: '控制上下文记忆与保留策略。',
|
|
icon: 'database',
|
|
fields: [
|
|
{
|
|
envKey: 'MEMORY_ENABLED',
|
|
label: '启用记忆',
|
|
description: '是否启用长期记忆',
|
|
type: 'boolean',
|
|
sensitive: false,
|
|
value: true,
|
|
hasValue: true,
|
|
source: 'db',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const providers = [
|
|
{
|
|
id: 'provider-openai',
|
|
name: 'OpenAI',
|
|
type: 'openai_responses',
|
|
baseUrl: null,
|
|
defaultModel: 'gpt-4o-mini',
|
|
isEnabled: true,
|
|
hasKey: true,
|
|
extraConfig: {},
|
|
createdAt: '2026-03-01T00:00:00.000Z',
|
|
updatedAt: '2026-03-02T00:00:00.000Z',
|
|
},
|
|
{
|
|
id: 'provider-deepseek',
|
|
name: 'DeepSeek',
|
|
type: 'openai_compatible',
|
|
baseUrl: 'https://api.deepseek.com/v1',
|
|
defaultModel: 'deepseek-chat',
|
|
isEnabled: true,
|
|
hasKey: true,
|
|
extraConfig: {},
|
|
createdAt: '2026-03-01T00:00:00.000Z',
|
|
updatedAt: '2026-03-02T00:00:00.000Z',
|
|
},
|
|
];
|
|
|
|
const roles = [
|
|
{
|
|
role: 'planner',
|
|
providerId: 'provider-openai',
|
|
providerName: 'OpenAI',
|
|
providerType: 'openai_responses',
|
|
model: 'gpt-4o-mini',
|
|
},
|
|
{
|
|
role: 'specialist',
|
|
providerId: 'provider-deepseek',
|
|
providerName: 'DeepSeek',
|
|
providerType: 'openai_compatible',
|
|
model: 'deepseek-chat',
|
|
},
|
|
];
|
|
|
|
const modelSuggestions = {
|
|
openai_compatible: ['deepseek-chat', 'gpt-4o-mini'],
|
|
openai_responses: ['gpt-4o', 'gpt-4o-mini', 'o3-mini'],
|
|
anthropic: ['claude-sonnet-4-20250514'],
|
|
gemini: ['gemini-2.5-pro'],
|
|
};
|
|
|
|
const notificationTestHistory = [
|
|
{
|
|
id: 'test-1',
|
|
provider: 'feishu',
|
|
status: 'success',
|
|
message: 'feishu 测试通知已发送',
|
|
timestamp: '2026-03-24T09:00:00.000Z',
|
|
},
|
|
{
|
|
id: 'test-2',
|
|
provider: 'wecom',
|
|
status: 'error',
|
|
message: 'wecom 未启用或未配置',
|
|
timestamp: '2026-03-24T08:50:00.000Z',
|
|
},
|
|
];
|
|
|
|
const json = async (route: Route, body: unknown, status = 200) => {
|
|
await route.fulfill({
|
|
status,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(body),
|
|
});
|
|
};
|
|
|
|
export async function installVisualApiMocks(page: Page) {
|
|
await page.route('**/admin/api/**', async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const path = url.pathname;
|
|
const method = route.request().method();
|
|
|
|
if (method === 'POST' && path.endsWith('/admin/api/login')) {
|
|
return json(route, { token: 'visual-token' });
|
|
}
|
|
|
|
if (method === 'GET' && path.endsWith('/admin/api/repositories')) {
|
|
return json(route, repositories);
|
|
}
|
|
|
|
if (method === 'POST' && /\/admin\/api\/repositories\/[^/]+(?:\/[^/]+)?\/webhook$/.test(path)) {
|
|
return json(route, { hook_id: 101, webhook_status: 'active' });
|
|
}
|
|
|
|
if (
|
|
method === 'DELETE' &&
|
|
/\/admin\/api\/repositories\/[^/]+(?:\/[^/]+)?\/webhook\/\d+$/.test(path)
|
|
) {
|
|
return route.fulfill({ status: 204, body: '' });
|
|
}
|
|
|
|
if (
|
|
method === 'PUT' &&
|
|
/\/admin\/api\/repositories\/[^/]+(?:\/[^/]+)?\/project-prompt$/.test(path)
|
|
) {
|
|
return json(route, {
|
|
success: true,
|
|
project_review_prompt: 'updated prompt',
|
|
});
|
|
}
|
|
|
|
if (method === 'GET' && path.endsWith('/admin/api/config')) {
|
|
return json(route, configResponse);
|
|
}
|
|
|
|
if (method === 'PUT' && path.endsWith('/admin/api/config')) {
|
|
return route.fulfill({ status: 204, body: '' });
|
|
}
|
|
|
|
if (method === 'POST' && path.endsWith('/admin/api/config/reset')) {
|
|
return route.fulfill({ status: 204, body: '' });
|
|
}
|
|
|
|
if (method === 'POST' && path.endsWith('/admin/api/config/notification/test')) {
|
|
return json(route, { success: true, message: 'test sent' });
|
|
}
|
|
|
|
if (method === 'GET' && path.endsWith('/admin/api/config/notification/test/history')) {
|
|
return json(route, { data: notificationTestHistory });
|
|
}
|
|
|
|
if (method === 'GET' && path.endsWith('/admin/api/llm/model-suggestions')) {
|
|
return json(route, modelSuggestions);
|
|
}
|
|
|
|
if (method === 'GET' && path.endsWith('/admin/api/llm/providers')) {
|
|
return json(route, providers);
|
|
}
|
|
|
|
if (method === 'POST' && path.endsWith('/admin/api/llm/providers')) {
|
|
return json(route, providers[0]);
|
|
}
|
|
|
|
if (method === 'PUT' && /\/admin\/api\/llm\/providers\/[^/]+$/.test(path)) {
|
|
return json(route, providers[0]);
|
|
}
|
|
|
|
if (method === 'DELETE' && /\/admin\/api\/llm\/providers\/[^/]+$/.test(path)) {
|
|
return route.fulfill({ status: 204, body: '' });
|
|
}
|
|
|
|
if (method === 'PUT' && /\/admin\/api\/llm\/providers\/[^/]+\/key$/.test(path)) {
|
|
return route.fulfill({ status: 204, body: '' });
|
|
}
|
|
|
|
if (method === 'DELETE' && /\/admin\/api\/llm\/providers\/[^/]+\/key$/.test(path)) {
|
|
return route.fulfill({ status: 204, body: '' });
|
|
}
|
|
|
|
if (method === 'GET' && path.endsWith('/admin/api/llm/roles')) {
|
|
return json(route, roles);
|
|
}
|
|
|
|
if (method === 'PUT' && /\/admin\/api\/llm\/roles\/[^/]+$/.test(path)) {
|
|
return json(route, roles[0]);
|
|
}
|
|
|
|
if (method === 'POST' && /\/admin\/api\/llm\/providers\/[^/]+\/test$/.test(path)) {
|
|
return json(route, {
|
|
success: true,
|
|
latencyMs: 154,
|
|
model: 'gpt-4o-mini',
|
|
message: 'Test connection success',
|
|
});
|
|
}
|
|
|
|
return json(
|
|
route,
|
|
{
|
|
error: `Unhandled visual mock request: ${method} ${path}`,
|
|
},
|
|
501
|
|
);
|
|
});
|
|
}
|