feat(ai): 完成 DeepSeek 大模型官方 API 接入、双模调用、高可用平滑降级与管理后台连通性测试

这个提交包含在:
wikiboss
2026-09-21 00:18:19 +08:00
父节点 eec259a3a7
当前提交 56392d8f9e
共有 33 个文件被更改,包括 1362 次插入 和 110 次删除

查看文件

@@ -12,6 +12,8 @@ import {
Message,
Spin,
Grid,
Tag,
Divider,
} from '@arco-design/web-react';
import axios from 'axios';
import useLocale from '@/utils/useLocale';
@@ -20,6 +22,8 @@ import type {
AiConciergeConfig,
ConciergePersonaType,
ConciergeToneStyle,
DeepSeekConfig,
DeepSeekTestConnectionResult,
} from '@/types/omni';
import locale from './locale';
@@ -28,6 +32,8 @@ const { Row, Col } = Grid;
const GET_CONFIG_API = '/api/admin/ai/concierge/getByCardId';
const SAVE_CONFIG_API = '/api/admin/ai/concierge/saveByCardId';
const CARD_PAGE_API = '/api/admin/card/info/page';
const DEEPSEEK_CONFIG_API = '/api/admin/ai/deepseek/config';
const DEEPSEEK_TEST_API = '/api/admin/ai/deepseek/test-connection';
interface CoolResponse<T> {
code: number;
@@ -98,6 +104,100 @@ export default function AiConciergePage(): JSX.Element {
const [selectedCardId, setSelectedCardId] = useState<number>(DEFAULT_CARD_ID);
const [cardOptions, setCardOptions] = useState<CardOptionItem[]>([]);
const [deepSeekForm] = Form.useForm<DeepSeekConfig>();
const [deepSeekLoading, setDeepSeekLoading] = useState<boolean>(false);
const [deepSeekSaving, setDeepSeekSaving] = useState<boolean>(false);
const [deepSeekTesting, setDeepSeekTesting] = useState<boolean>(false);
const [deepSeekConfig, setDeepSeekConfig] = useState<DeepSeekConfig | null>(null);
const [testResult, setTestResult] = useState<DeepSeekTestConnectionResult | null>(null);
const loadDeepSeekConfig = async (): Promise<void> => {
setDeepSeekLoading(true);
try {
const res = await axios.post<CoolResponse<DeepSeekConfig>>(
DEEPSEEK_CONFIG_API,
{}
);
if (res.data && res.data.code === 1000 && res.data.data) {
const d = res.data.data;
setDeepSeekConfig(d);
deepSeekForm.setFieldsValue({
apiKey: d.apiKey || '',
baseUrl: d.baseUrl || 'https://api.deepseek.com',
model: d.model || 'deepseek-chat',
temperature: d.temperature ?? 0.7,
maxTokens: d.maxTokens ?? 2048,
enabled: d.enabled !== false,
});
}
} catch {
// 保持静默优雅降级
} finally {
setDeepSeekLoading(false);
}
};
const handleTestDeepSeek = async (): Promise<void> => {
setDeepSeekTesting(true);
setTestResult(null);
try {
const values = deepSeekForm.getFieldsValue();
const res = await axios.post<CoolResponse<DeepSeekTestConnectionResult>>(
DEEPSEEK_TEST_API,
{ apiKey: values.apiKey }
);
if (res.data && res.data.code === 1000 && res.data.data) {
setTestResult(res.data.data);
if (res.data.data.success) {
Message.success(
`${t['aiDeepSeek.test.success']} (${res.data.data.latencyMs ?? 0}ms)`
);
} else {
Message.error(res.data.data.message || t['aiDeepSeek.test.fail']);
}
} else {
Message.error(res.data?.message || t['aiDeepSeek.test.fail']);
}
} catch {
Message.error(t['aiDeepSeek.test.fail']);
} finally {
setDeepSeekTesting(false);
}
};
const handleSaveDeepSeek = async (values: DeepSeekConfig): Promise<void> => {
setDeepSeekSaving(true);
try {
const payload: Partial<DeepSeekConfig> = {
baseUrl: values.baseUrl,
model: values.model,
temperature: Number(values.temperature),
maxTokens: Number(values.maxTokens),
enabled: Boolean(values.enabled),
};
if (values.apiKey && !values.apiKey.includes('****')) {
payload.apiKey = values.apiKey;
}
const res = await axios.post<CoolResponse<DeepSeekConfig>>(
DEEPSEEK_CONFIG_API,
payload
);
if (res.data && res.data.code === 1000 && res.data.data) {
Message.success(t['aiDeepSeek.save.success']);
setDeepSeekConfig(res.data.data);
if (res.data.data.apiKey) {
deepSeekForm.setFieldValue('apiKey', res.data.data.apiKey);
}
} else {
Message.error(res.data?.message || t['aiDeepSeek.save.fail']);
}
} catch {
Message.error(t['aiDeepSeek.save.fail']);
} finally {
setDeepSeekSaving(false);
}
};
const fetchCardOptions = async (): Promise<void> => {
try {
const res = await axios.post<CoolResponse<CardPageResult>>(
@@ -171,6 +271,7 @@ export default function AiConciergePage(): JSX.Element {
useEffect(() => {
fetchCardOptions();
loadConciergeConfig(selectedCardId);
loadDeepSeekConfig();
}, []);
const handleCardChange = (cardId: number): void => {
@@ -401,6 +502,178 @@ export default function AiConciergePage(): JSX.Element {
</Form>
</Spin>
</Card>
<Card
title={
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
<span>{t['aiDeepSeek.card.title']}</span>
<Tag color={deepSeekConfig?.isConfigured ? 'green' : 'gray'}>
{deepSeekConfig?.isConfigured
? t['aiDeepSeek.status.configured']
: t['aiDeepSeek.status.unconfigured']}
</Tag>
</div>
}
bordered={false}
style={{
marginTop: spacing[24],
borderRadius: radius.card,
border: `1px solid ${lightColors.border}`,
backgroundColor: lightColors.bg,
}}
>
<div
style={{
color: lightColors.text2,
marginBottom: spacing[24],
fontSize: '14px',
}}
>
{t['aiDeepSeek.card.desc']}
</div>
<Spin loading={deepSeekLoading} style={{ width: '100%' }}>
<Form
form={deepSeekForm}
layout="vertical"
onSubmit={handleSaveDeepSeek}
initialValues={{
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-chat',
temperature: 0.7,
maxTokens: 2048,
enabled: true,
}}
>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label={t['aiDeepSeek.form.apiKey']}
field="apiKey"
extra={
deepSeekConfig?.apiKey
? `已保存密钥: ${deepSeekConfig.apiKey}`
: undefined
}
>
<Input.Password
placeholder={t['aiDeepSeek.form.apiKey.placeholder']}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label={t['aiDeepSeek.form.status']}
field="enabled"
triggerPropName="checked"
extra={t['aiDeepSeek.form.status.desc']}
>
<Switch />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label={t['aiDeepSeek.form.baseUrl']}
field="baseUrl"
rules={[{ required: true }]}
>
<Input placeholder="https://api.deepseek.com" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label={t['aiDeepSeek.form.model']}
field="model"
rules={[{ required: true }]}
>
<Select>
<Select.Option value="deepseek-chat">
{t['aiDeepSeek.model.chat']}
</Select.Option>
<Select.Option value="deepseek-reasoner">
{t['aiDeepSeek.model.reasoner']}
</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label={t['aiDeepSeek.form.temperature']}
field="temperature"
extra={t['aiDeepSeek.form.temperature.desc']}
>
<Slider
min={0}
max={1.5}
step={0.1}
marks={{ 0: '0', 0.7: '0.7', 1.0: '1.0', 1.5: '1.5' }}
style={{ width: '100%' }}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="Max Tokens"
field="maxTokens"
>
<Select>
<Select.Option value={1024}>1024</Select.Option>
<Select.Option value={2048}>2048</Select.Option>
<Select.Option value={4096}>4096</Select.Option>
<Select.Option value={8192}>8192</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
{testResult && (
<div style={{ marginBottom: spacing[16] }}>
<Tag
color={testResult.success ? 'green' : 'red'}
style={{ padding: `${spacing[4]} ${spacing[12]}` }}
>
{testResult.message || (testResult.success ? '连通成功' : '连通失败')}
{testResult.latencyMs !== undefined && ` | ${testResult.latencyMs}ms`}
{testResult.model && ` | ${testResult.model}`}
</Tag>
</div>
)}
<div style={{ marginTop: spacing[24] }}>
<Space>
<Button
htmlType="submit"
loading={deepSeekSaving}
style={{ borderRadius: radius.button }}
>
{t['aiDeepSeek.action.save']}
</Button>
<Button
type="outline"
onClick={handleTestDeepSeek}
loading={deepSeekTesting}
style={{ borderRadius: radius.button }}
>
{t['aiDeepSeek.action.test']}
</Button>
</Space>
</div>
</Form>
</Spin>
</Card>
</div>
);
}

查看文件

@@ -43,6 +43,31 @@ const i18n = {
'aiConcierge.request.success': 'Concierge settings saved successfully',
'aiConcierge.request.fail': 'Failed to load concierge configuration',
'aiConcierge.request.saveFail': 'Failed to save configuration',
'aiDeepSeek.card.title': 'DeepSeek Official LLM Configuration',
'aiDeepSeek.card.desc':
'Configure DeepSeek API Key, supporting deepseek-chat and deepseek-reasoner dual models for 24/7 AI concierge and cross-border localization.',
'aiDeepSeek.form.apiKey': 'API Key',
'aiDeepSeek.form.apiKey.placeholder':
'Enter official API Key starting with sk- (leave blank to keep current)',
'aiDeepSeek.form.baseUrl': 'API Base URL',
'aiDeepSeek.form.model': 'Model Version',
'aiDeepSeek.model.chat': 'DeepSeek-V3 Chat (deepseek-chat)',
'aiDeepSeek.model.reasoner': 'DeepSeek-R1 Reasoner (deepseek-reasoner)',
'aiDeepSeek.form.temperature': 'Temperature',
'aiDeepSeek.form.temperature.desc':
'Lower values are deterministic; higher values are creative.',
'aiDeepSeek.form.status': 'Enable DeepSeek LLM',
'aiDeepSeek.form.status.desc':
'When enabled, requests prioritize DeepSeek official API with smooth fallback.',
'aiDeepSeek.action.test': 'Test Connection',
'aiDeepSeek.action.testing': 'Testing connection...',
'aiDeepSeek.action.save': 'Save Model Config',
'aiDeepSeek.status.configured': 'Configured',
'aiDeepSeek.status.unconfigured': 'Not Configured',
'aiDeepSeek.test.success': 'DeepSeek API connection successful',
'aiDeepSeek.test.fail': 'Connection test failed',
'aiDeepSeek.save.success': 'DeepSeek configuration updated',
'aiDeepSeek.save.fail': 'Failed to save DeepSeek configuration',
},
'zh-CN': {
'menu.ai.concierge': 'AI 顾问配置',
@@ -88,6 +113,31 @@ const i18n = {
'aiConcierge.request.success': 'AI 顾问配置已保存',
'aiConcierge.request.fail': 'AI 顾问配置加载失败',
'aiConcierge.request.saveFail': '保存配置失败',
'aiDeepSeek.card.title': 'DeepSeek 官方大模型底座配置',
'aiDeepSeek.card.desc':
'配置 DeepSeek API Key,支持 deepseek-chat 与 deepseek-reasoner 双模型接入,赋能 24/7 AI 顾问对话与出海多语言润色。',
'aiDeepSeek.form.apiKey': 'API Key 密钥',
'aiDeepSeek.form.apiKey.placeholder':
'输入 sk- 开头的官方密钥 (留空则保留当前已配置密钥)',
'aiDeepSeek.form.baseUrl': 'API 接口基地址 (Base URL)',
'aiDeepSeek.form.model': '接入模型版本',
'aiDeepSeek.model.chat': 'DeepSeek-V3 对话模型 (deepseek-chat)',
'aiDeepSeek.model.reasoner': 'DeepSeek-R1 深度推理模型 (deepseek-reasoner)',
'aiDeepSeek.form.temperature': '采样温度 (Temperature)',
'aiDeepSeek.form.temperature.desc':
'较低值输出严谨稳定,较高值回复生动多样。',
'aiDeepSeek.form.status': '启用 DeepSeek 大模型',
'aiDeepSeek.form.status.desc':
'开启后系统优先直连 DeepSeek 官方 API,网络异常或未配置时自动平滑降级。',
'aiDeepSeek.action.test': '测试连通性',
'aiDeepSeek.action.testing': '连通性测试中...',
'aiDeepSeek.action.save': '保存大模型配置',
'aiDeepSeek.status.configured': '已配置',
'aiDeepSeek.status.unconfigured': '未配置',
'aiDeepSeek.test.success': 'DeepSeek API 连通测试成功',
'aiDeepSeek.test.fail': '连通性测试失败',
'aiDeepSeek.save.success': 'DeepSeek 大模型配置已保存',
'aiDeepSeek.save.fail': '保存大模型配置失败',
},
'ar-SA': {
'menu.ai.concierge': 'إعدادات المساعد الذكي',
@@ -133,6 +183,31 @@ const i18n = {
'aiConcierge.request.success': 'تم حفظ الإعدادات بنجاح',
'aiConcierge.request.fail': 'تعذر تحميل الإعدادات',
'aiConcierge.request.saveFail': 'فشل حفظ الإعدادات',
'aiDeepSeek.card.title': 'إعدادات نموذج DeepSeek الرسمي',
'aiDeepSeek.card.desc':
'تكوين مفتاح واجهة DeepSeek البرمجية لدعم محادثات المساعد الذكي وترجمة المحتوى.',
'aiDeepSeek.form.apiKey': 'مفتاح API',
'aiDeepSeek.form.apiKey.placeholder':
'أدخل المفتاح الرسمي الذي يبدأ بـ sk-',
'aiDeepSeek.form.baseUrl': 'عنوان الواجهة الأساسي',
'aiDeepSeek.form.model': 'إصدار النموذج',
'aiDeepSeek.model.chat': 'نموذج محادثة DeepSeek-V3',
'aiDeepSeek.model.reasoner': 'نموذج استدلال DeepSeek-R1',
'aiDeepSeek.form.temperature': 'درجة الحرارة',
'aiDeepSeek.form.temperature.desc':
'القيم المنخفضة أكثر دقة والصلبة أكثر إبداعا.',
'aiDeepSeek.form.status': 'تفعيل نموذج DeepSeek',
'aiDeepSeek.form.status.desc':
'عند التفعيل، تعطى الأولوية لواجهة DeepSeek مع خفض سلس للأداء عند الانقطاع.',
'aiDeepSeek.action.test': 'اختبار الاتصال',
'aiDeepSeek.action.testing': 'جاري اختبار الاتصال...',
'aiDeepSeek.action.save': 'حفظ إعدادات النموذج',
'aiDeepSeek.status.configured': 'تم التكوين',
'aiDeepSeek.status.unconfigured': 'غير مكوّن',
'aiDeepSeek.test.success': 'نجح اختبار الاتصال بـ DeepSeek',
'aiDeepSeek.test.fail': 'فشل اختبار الاتصال',
'aiDeepSeek.save.success': 'تم حفظ إعدادات DeepSeek بنجاح',
'aiDeepSeek.save.fail': 'فشل حفظ إعدادات DeepSeek',
},
};

查看文件

@@ -476,6 +476,42 @@ export interface AiConciergeConfig {
isEnabled: boolean;
}
// ==========================================
// 5.1 DeepSeek 大模型集成契约 (DeepSeek AI Suite)
// ==========================================
export type DeepSeekModel = 'deepseek-chat' | 'deepseek-reasoner';
export interface DeepSeekConfig {
apiKey?: string;
baseUrl?: string;
model?: DeepSeekModel;
temperature?: number;
maxTokens?: number;
timeoutMs?: number;
enabled?: boolean;
}
export interface DeepSeekChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
export interface DeepSeekChatCompletionRequest {
messages: DeepSeekChatMessage[];
model?: DeepSeekModel;
temperature?: number;
maxTokens?: number;
stream?: boolean;
}
export interface DeepSeekTestConnectionResult {
success: boolean;
latencyMs?: number;
model?: string;
message?: string;
}
export type LocalizeTone = 'professional' | 'direct' | 'storytelling';
export interface LocalizeRequest {
@@ -725,4 +761,117 @@ export interface TeamRankBroadcastPayload {
meta?: TeamRankBroadcastMeta;
}
// ==========================================
// 12. AI 顾问对话与 DeepSeek 大模型契约 (AI Concierge & DeepSeek Suite)
// ==========================================
export type AiChatRole = 'user' | 'assistant' | 'system';
export interface AiChatMessage {
role: AiChatRole;
content: string;
}
export interface AiChatRequest {
cardId: string | number;
locale?: string;
messages: AiChatMessage[];
}
export interface AiChatReply {
reply: string;
locale: string;
shouldCollectLead: boolean;
}
export interface AiKnowledgeDoc {
id?: string | number;
cardId: string | number;
title: string;
content: string;
locale?: string;
}
export type ConciergePersonaType =
| 'trade_expert'
| 'business_rep'
| 'tech_advisor'
| 'custom';
export type ConciergeToneStyle =
| 'professional'
| 'direct'
| 'enthusiastic'
| 'friendly';
export interface AiConciergeConfig {
id?: string | number;
cardId: string | number;
personaType: ConciergePersonaType;
welcomeMsg: string;
triggerLeadThreshold: number;
promptTemplate: string;
toneStyle: ConciergeToneStyle;
isEnabled: boolean;
}
export interface KnowledgeChunk {
id?: string | number;
index: number;
content: string;
length: number;
score?: number;
}
export type DeepSeekModel = 'deepseek-chat' | 'deepseek-reasoner';
export interface DeepSeekConfig {
apiKey?: string;
baseUrl?: string;
model?: DeepSeekModel;
temperature?: number;
maxTokens?: number;
timeoutMs?: number;
enabled?: boolean;
isConfigured?: boolean;
}
export interface DeepSeekChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
export interface DeepSeekChatCompletionRequest {
messages: DeepSeekChatMessage[];
model?: DeepSeekModel;
temperature?: number;
maxTokens?: number;
stream?: boolean;
}
export interface DeepSeekChatCompletionResponse {
id: string;
model: string;
choices: Array<{
index: number;
message: {
role: 'assistant';
content: string;
};
finish_reason: string;
}>;
usage?: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}
export interface DeepSeekTestConnectionResult {
success: boolean;
latencyMs?: number;
model?: string;
message?: string;
}