🔧 chore: sync cloud changes (#10307)

This commit is contained in:
YuTengjing
2025-11-19 19:05:38 +08:00
committed by GitHub
parent 7ec5594e1c
commit 474af231b5
12 changed files with 88 additions and 104 deletions

2
.gitignore vendored
View File

@@ -103,8 +103,8 @@ vertex-ai-key.json
.local/
.claude/
.mcp.json
CLAUDE.local.md
.agent/
# MCP tools
.serena/**

View File

@@ -283,6 +283,7 @@
"business": "Business Cooperation",
"support": "Email Support"
},
"new": "NEW",
"oauth": "SSO Login",
"officialSite": "Official Website",
"ok": "OK",

View File

@@ -283,6 +283,7 @@
"business": "商务合作",
"support": "邮件支持"
},
"new": "新",
"oauth": "SSO 登录",
"officialSite": "官方网站",
"ok": "确定",

View File

@@ -423,6 +423,7 @@ export interface EnabledAiModel {
id: string;
parameters?: ModelParamsSchema;
providerId: string;
releasedAt?: string;
settings?: AiModelSettings;
sort?: number;
type: AiModelType;

View File

@@ -39,3 +39,7 @@ export const lastMonth = () => monthsAgo(1).endOf('month');
export function getYYYYmmddHHMMss(date: Date) {
return dayjs(date).format('YYYYMMDD_HHmmss');
}
export const isNewReleaseDate = (date: string, days = 14) => {
return dayjs().diff(dayjs(date), 'day') < days;
};

View File

@@ -21,12 +21,11 @@ import { createErrorResponse } from '@/utils/errorResponse';
import { checkAuthMethod } from './utils';
type CreateRuntime = (jwtPayload: ClientSecretPayload) => ModelRuntime;
type RequestOptions = { createRuntime?: CreateRuntime; params: Promise<{ provider: string }> };
type RequestOptions = { createRuntime?: CreateRuntime; params: Promise<{ provider?: string }> };
export type RequestHandler = (
req: Request,
options: RequestOptions & {
createRuntime?: CreateRuntime;
jwtPayload: ClientSecretPayload;
},
) => Promise<Response>;

View File

@@ -14,7 +14,7 @@ import { getTracePayload } from '@/utils/trace';
export const maxDuration = 300;
export const POST = checkAuth(async (req: Request, { params, jwtPayload, createRuntime }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
// ============ 1. init chat model ============ //

View File

@@ -6,7 +6,7 @@ import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
import { createErrorResponse } from '@/utils/errorResponse';
export const POST = checkAuth(async (req, { params, jwtPayload }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
const agentRuntime = await initModelRuntimeWithUserPayload(provider, jwtPayload);

View File

@@ -10,7 +10,7 @@ import { createErrorResponse } from '@/utils/errorResponse';
const noNeedAPIKey = (provider: string) => [ModelProvider.OpenRouter].includes(provider as any);
export const GET = checkAuth(async (req, { params, jwtPayload }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
const hasDefaultApiKey = jwtPayload.apiKey || 'dont-need-api-key-for-model-list';

View File

@@ -45,7 +45,7 @@ export const preferredRegion = [
// );
export const POST = checkAuth(async (req: Request, { params, jwtPayload }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
// ============ 1. init chat model ============ //

View File

@@ -4,7 +4,7 @@ import { App, Switch } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { LucidePencil, TrashIcon } from 'lucide-react';
import { AiModelSourceEnum, AiProviderModelListItem } from 'model-bank';
import { memo, use, useState } from 'react';
import React, { memo, use, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
@@ -17,6 +17,7 @@ import {
getTextInputUnitRate,
getTextOutputUnitRate,
} from '@/utils/pricing';
import { isNewReleaseDate } from '@/utils/time';
import ModelConfigModal from './ModelConfigModal';
import { ProviderSettingsContext } from './ProviderSettingsContext';
@@ -162,6 +163,70 @@ const ModelItem = memo<ModelItemProps>(
const isMobile = useIsMobile();
const NewTag =
releasedAt && isNewReleaseDate(releasedAt) ? (
<Tag color="blue" style={{ marginLeft: 8 }}>
{t('new', { ns: 'common' })}
</Tag>
) : null;
const ModelIdTag = (
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
{id}
</Tag>
);
const EnableSwitch = (
<Switch
checked={checked}
loading={isModelLoading}
onChange={async (e) => {
setChecked(e);
await toggleModelEnabled({ enabled: e, id, source, type });
}}
size={'small'}
/>
);
const Actions =
modelEditable &&
((style?: React.CSSProperties) => (
<Flexbox className={styles.config} horizontal style={style}>
<ActionIcon
icon={LucidePencil}
onClick={(e) => {
e.stopPropagation();
setShowConfig(true);
}}
size={'small'}
title={t('providerModels.item.config')}
/>
{source !== AiModelSourceEnum.Builtin && (
<ActionIcon
icon={TrashIcon}
onClick={() => {
modal.confirm({
centered: true,
okButtonProps: {
danger: true,
type: 'primary',
},
onOk: async () => {
await removeAiModel(id, activeAiProvider!);
message.success(t('providerModels.item.delete.success'));
},
title: t('providerModels.item.delete.confirm', {
displayName: displayName || id,
}),
});
}}
size={'small'}
title={t('providerModels.item.delete.title')}
/>
)}
</Flexbox>
));
const dom = isMobile ? (
<Flexbox
align={'center'}
@@ -195,58 +260,14 @@ const ModelItem = memo<ModelItemProps>(
</Flexbox>
</Flexbox>
<div>
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
{id}
</Tag>
{ModelIdTag}
{NewTag}
</div>
</Flexbox>
</Flexbox>
<Flexbox align={'center'} gap={4} horizontal>
{modelEditable && (
<Flexbox className={styles.config} horizontal style={{ opacity: 1 }}>
<ActionIcon
icon={LucidePencil}
onClick={(e) => {
e.stopPropagation();
setShowConfig(true);
}}
size={'small'}
title={t('providerModels.item.config')}
/>
{source !== AiModelSourceEnum.Builtin && (
<ActionIcon
icon={TrashIcon}
onClick={() => {
modal.confirm({
centered: true,
okButtonProps: {
danger: true,
type: 'primary',
},
onOk: async () => {
await removeAiModel(id, activeAiProvider!);
message.success(t('providerModels.item.delete.success'));
},
title: t('providerModels.item.delete.confirm', {
displayName: displayName || id,
}),
});
}}
size={'small'}
title={t('providerModels.item.delete.title')}
/>
)}
</Flexbox>
)}
<Switch
checked={checked}
loading={isModelLoading}
onChange={async (e) => {
setChecked(e);
await toggleModelEnabled({ enabled: e, id, source, type });
}}
size={'small'}
/>
{Actions && Actions({ opacity: 1 })}
{EnableSwitch}
</Flexbox>
</Flexbox>
) : (
@@ -264,45 +285,9 @@ const ModelItem = memo<ModelItemProps>(
<Flexbox flex={1} gap={2} style={{ minWidth: 0 }}>
<Flexbox align={'center'} gap={8} horizontal>
{displayName || id}
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
{id}
</Tag>
{modelEditable && (
<Flexbox className={styles.config} horizontal>
<ActionIcon
icon={LucidePencil}
onClick={(e) => {
e.stopPropagation();
setShowConfig(true);
}}
size={'small'}
title={t('providerModels.item.config')}
/>
{source !== AiModelSourceEnum.Builtin && (
<ActionIcon
icon={TrashIcon}
onClick={() => {
modal.confirm({
centered: true,
okButtonProps: {
danger: true,
type: 'primary',
},
onOk: async () => {
await removeAiModel(id, activeAiProvider!);
message.success(t('providerModels.item.delete.success'));
},
title: t('providerModels.item.delete.confirm', {
displayName: displayName || id,
}),
});
}}
size={'small'}
title={t('providerModels.item.delete.title')}
/>
)}
</Flexbox>
)}
{ModelIdTag}
{NewTag}
{Actions && Actions()}
</Flexbox>
<Flexbox align={'baseline'} gap={8} horizontal>
{content.length > 0 && (
@@ -329,15 +314,7 @@ const ModelItem = memo<ModelItemProps>(
{/* <ActionIcon icon={Recycle} style={{ color: theme.colorWarning }} />*/}
{/* </Tooltip>*/}
{/*)}*/}
<Switch
checked={checked}
loading={isModelLoading}
onChange={async (e) => {
setChecked(e);
await toggleModelEnabled({ enabled: e, id, source, type });
}}
size={'small'}
/>
{EnableSwitch}
</Flexbox>
</Flexbox>
);

View File

@@ -286,6 +286,7 @@ export default {
business: '商务合作',
support: '邮件支持',
},
new: '新',
oauth: 'SSO 登录',
officialSite: '官方网站',
ok: '确定',