feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -5,58 +5,159 @@ import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const conciergePagePath = path.join(adminRoot, 'src/pages/ai/concierge/index.tsx');
|
||||
const conciergeLocalePath = path.join(adminRoot, 'src/pages/ai/concierge/locale/index.ts');
|
||||
const omniTypesPath = path.join(adminRoot, 'src/types/omni.ts');
|
||||
const deepseekPagePath = path.join(adminRoot, 'src/pages/ai/deepseek/index.tsx');
|
||||
const deepseekLocalePath = path.join(adminRoot, 'src/pages/ai/deepseek/locale/index.ts');
|
||||
|
||||
test('1. DeepSeek 大模型后台界面必须接入标准 API 与连通性测试端点', () => {
|
||||
assert.ok(fs.existsSync(conciergePagePath), 'AI 页面文件必须存在');
|
||||
const content = fs.readFileSync(conciergePagePath, 'utf-8');
|
||||
/** 账单式误导词(标题/指标/描述里禁止正向使用;disclaimer 可否定提及) */
|
||||
const BILLING_MISLEAD_RE =
|
||||
/账本|账单|已出账|对账|划扣|本月账单|invoice|billed|billing|pay-as-you-go|ledger|reconcil/i;
|
||||
|
||||
/** 前端不得填入的示例消耗数字 */
|
||||
const SAMPLE_USAGE_RE =
|
||||
/totalTokens:\s*[1-9]\d*|promptTokens:\s*[1-9]\d*|completionTokens:\s*[1-9]\d*|callCount:\s*[1-9]\d*/;
|
||||
|
||||
function readPage(): string {
|
||||
assert.ok(fs.existsSync(deepseekPagePath), 'DeepSeek 配置页必须存在');
|
||||
return fs.readFileSync(deepseekPagePath, 'utf-8');
|
||||
}
|
||||
|
||||
function readLocale(): string {
|
||||
assert.ok(fs.existsSync(deepseekLocalePath), 'DeepSeek 语言包必须存在');
|
||||
return fs.readFileSync(deepseekLocalePath, 'utf-8');
|
||||
}
|
||||
|
||||
/** 去掉 usage.notice 词条后再扫误导词,避免「不是账单」说明被误伤 */
|
||||
function localeWithoutUsageNotice(locale: string): string {
|
||||
return locale.replace(
|
||||
/'ai\.deepseek\.usage\.notice'\s*:\s*(?:'(?:\\'|[^'])*'|"(?:\\"|[^"])*"|`(?:\\`|[^`])*`)\s*,?/g,
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
test('1. DeepSeek 页必须请求 usage,且无用量时回落 0 / 空,禁止示例消耗', () => {
|
||||
const content = readPage();
|
||||
|
||||
assert.ok(
|
||||
content.includes('/api/admin/ai/deepseek/usage'),
|
||||
'必须请求 POST /api/admin/ai/deepseek/usage'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('/api/admin/ai/deepseek/config'),
|
||||
'必须包含 /api/admin/ai/deepseek/config 配置端点'
|
||||
'必须请求配置端点'
|
||||
);
|
||||
assert.ok(
|
||||
content.includes('/api/admin/ai/deepseek/test-connection'),
|
||||
'必须包含 /api/admin/ai/deepseek/test-connection 连通性测试端点'
|
||||
content.includes("from '@arco-design/web-react'"),
|
||||
'必须从 @arco-design/web-react 引入组件'
|
||||
);
|
||||
assert.ok(content.includes('deepseek-chat'), '必须支持 deepseek-chat (V3)');
|
||||
assert.ok(content.includes('deepseek-reasoner'), '必须支持 deepseek-reasoner (R1)');
|
||||
assert.ok(content.includes('handleTestDeepSeek'), '必须提供连通性测试处理函数');
|
||||
assert.ok(content.includes('handleSaveDeepSeek'), '必须提供保存配置处理函数');
|
||||
});
|
||||
assert.ok(content.includes('Statistic'), '必须用 Statistic 展示用量');
|
||||
assert.ok(content.includes('Alert'), '必须用 Alert 标明进程内性质');
|
||||
|
||||
test('2. DeepSeek 大模型跨端类型契约必须在 admin/src/types/omni.ts 中严密定义', () => {
|
||||
assert.ok(fs.existsSync(omniTypesPath), 'omni.ts 类型契约必须存在');
|
||||
const content = fs.readFileSync(omniTypesPath, 'utf-8');
|
||||
|
||||
assert.ok(content.includes('DeepSeekModel'), '必须导出 DeepSeekModel 类型');
|
||||
assert.ok(content.includes('DeepSeekConfig'), '必须导出 DeepSeekConfig 接口');
|
||||
assert.ok(content.includes('DeepSeekChatMessage'), '必须导出 DeepSeekChatMessage 接口');
|
||||
assert.ok(
|
||||
content.includes('DeepSeekTestConnectionResult'),
|
||||
'必须导出 DeepSeekTestConnectionResult 接口'
|
||||
/totalTokens:\s*0/.test(content) &&
|
||||
/promptTokens:\s*0/.test(content) &&
|
||||
/completionTokens:\s*0/.test(content) &&
|
||||
/callCount:\s*0/.test(content),
|
||||
'初始用量必须为 0,不得填示例消耗'
|
||||
);
|
||||
assert.ok(!SAMPLE_USAGE_RE.test(content), '禁止硬编码非零示例 Token 消耗');
|
||||
assert.ok(
|
||||
content.includes('usageRes.data?.data') || content.includes('usageRes.data.data'),
|
||||
'仅当接口返回 data 时才写入用量'
|
||||
);
|
||||
assert.ok(!/\bany\b/.test(content), '禁止使用 any');
|
||||
assert.ok(
|
||||
!content.includes('Mock.js') && !content.includes("from 'mockjs'"),
|
||||
'禁止用 Mock.js 假用量'
|
||||
);
|
||||
});
|
||||
|
||||
test('3. DeepSeek 多语言国际化必须覆盖中英阿且无 emoji 无 any', () => {
|
||||
assert.ok(fs.existsSync(conciergeLocalePath), '语言包必须存在');
|
||||
const localeContent = fs.readFileSync(conciergeLocalePath, 'utf-8');
|
||||
test('2. Token 用量文案必须标明进程内统计,禁止账单式误导', () => {
|
||||
const page = readPage();
|
||||
const locale = readLocale();
|
||||
|
||||
assert.ok(localeContent.includes("'aiDeepSeek.card.title'"), '必须包含卡片标题词条');
|
||||
assert.ok(localeContent.includes("'aiDeepSeek.form.apiKey'"), '必须包含 API Key 表单项');
|
||||
assert.ok(localeContent.includes("'aiDeepSeek.form.model'"), '必须包含模型选择项');
|
||||
assert.ok(localeContent.includes("'aiDeepSeek.action.test'"), '必须包含测试连通性按钮词条');
|
||||
assert.ok(localeContent.includes("'aiDeepSeek.test.success'"), '必须包含测试成功提示');
|
||||
assert.ok(
|
||||
locale.includes("'ai.deepseek.usage.cardTitle'") ||
|
||||
locale.includes('"ai.deepseek.usage.cardTitle"'),
|
||||
'必须有用量卡片标题词条'
|
||||
);
|
||||
assert.ok(
|
||||
locale.includes("'ai.deepseek.usage.notice'") ||
|
||||
locale.includes('"ai.deepseek.usage.notice"'),
|
||||
'必须有进程内用量说明词条'
|
||||
);
|
||||
assert.ok(
|
||||
page.includes("t['ai.deepseek.usage.cardTitle']") ||
|
||||
page.includes('t["ai.deepseek.usage.cardTitle"]'),
|
||||
'卡片标题必须走 locale,禁止硬编码「账本」'
|
||||
);
|
||||
assert.ok(
|
||||
page.includes("t['ai.deepseek.usage.notice']") ||
|
||||
page.includes('t["ai.deepseek.usage.notice"]'),
|
||||
'进程内说明必须走 locale'
|
||||
);
|
||||
|
||||
// 三语检查
|
||||
assert.ok(localeContent.includes("'en-US'"), '必须支持 en-US');
|
||||
assert.ok(localeContent.includes("'zh-CN'"), '必须支持 zh-CN');
|
||||
assert.ok(localeContent.includes("'ar-SA'"), '必须支持 ar-SA');
|
||||
assert.ok(
|
||||
/进程内|本次进程/.test(locale),
|
||||
'中文词条必须标明「进程内 / 本次进程」'
|
||||
);
|
||||
assert.ok(
|
||||
/process|in-process|current process|this process/i.test(locale),
|
||||
'英文词条必须标明 process / in-process'
|
||||
);
|
||||
|
||||
// disclaimer 必须明确否定账单语义
|
||||
assert.ok(
|
||||
/不是已出账|不是.*账单|非.*账单/.test(locale) ||
|
||||
/not billed|not .*invoice|not .*reconcil/i.test(locale),
|
||||
'usage.notice 必须明确否定已出账/账单/可对账语义'
|
||||
);
|
||||
|
||||
const localeBody = localeWithoutUsageNotice(locale);
|
||||
assert.ok(
|
||||
!BILLING_MISLEAD_RE.test(localeBody),
|
||||
'locale 标题/指标/描述禁止账单式误导词(notice 除外)'
|
||||
);
|
||||
assert.ok(!BILLING_MISLEAD_RE.test(page), '页面禁止账单式硬编码文案');
|
||||
|
||||
for (const key of [
|
||||
'ai.deepseek.stats.totalTokens',
|
||||
'ai.deepseek.stats.quotaProgress',
|
||||
'ai.deepseek.stats.unlimited',
|
||||
'ai.deepseek.desc',
|
||||
]) {
|
||||
assert.ok(locale.includes(`'${key}'`) || locale.includes(`"${key}"`), `必须包含 ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('3. DeepSeek locale 覆盖中英,无 emoji,用量词条可被页面引用', () => {
|
||||
const locale = readLocale();
|
||||
const page = readPage();
|
||||
|
||||
assert.ok(locale.includes("'en-US'"), '必须支持 en-US');
|
||||
assert.ok(locale.includes("'zh-CN'"), '必须支持 zh-CN');
|
||||
|
||||
// 禁止 emoji
|
||||
const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
|
||||
assert.ok(!emojiRegex.test(localeContent), '语言包严禁使用 emoji');
|
||||
assert.ok(!/\bany\b/.test(localeContent), '语言包严禁使用 any');
|
||||
assert.ok(!emojiRegex.test(locale), '语言包严禁使用 emoji');
|
||||
assert.ok(!/\bany\b/.test(locale), '语言包严禁使用 any');
|
||||
|
||||
assert.ok(
|
||||
page.includes("t['ai.deepseek.stats.totalTokens']") ||
|
||||
page.includes('t["ai.deepseek.stats.totalTokens"]'),
|
||||
'总量标题必须走 locale'
|
||||
);
|
||||
assert.ok(
|
||||
page.includes("t['ai.deepseek.stats.promptTokens']") ||
|
||||
page.includes('t["ai.deepseek.stats.promptTokens"]'),
|
||||
'Prompt 标题必须走 locale'
|
||||
);
|
||||
assert.ok(
|
||||
page.includes("t['ai.deepseek.stats.completionTokens']") ||
|
||||
page.includes('t["ai.deepseek.stats.completionTokens"]'),
|
||||
'Completion 标题必须走 locale'
|
||||
);
|
||||
assert.ok(
|
||||
page.includes("t['ai.deepseek.stats.callCount']") ||
|
||||
page.includes('t["ai.deepseek.stats.callCount"]'),
|
||||
'调用次数标题必须走 locale'
|
||||
);
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户