feat(models): persist context metadata

This commit is contained in:
huanghuoguoguo
2026-06-08 00:39:30 +08:00
parent 573e1fe36e
commit b82db2b7f8
23 changed files with 498 additions and 22 deletions

View File

@@ -64,6 +64,17 @@ function convertExtraArgsToObject(
return obj;
}
function parseContextLength(
value: number | null | undefined,
invalidMessage: string,
): number | null {
if (value === undefined || value === null) return null;
if (!Number.isInteger(value) || value <= 0) {
throw new Error(invalidMessage);
}
return value;
}
export default function ModelsDialog({
open,
onOpenChange,
@@ -254,6 +265,7 @@ export default function ModelsDialog({
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) {
if (!name.trim()) {
toast.error(t('models.modelNameRequired'));
@@ -268,6 +280,10 @@ export default function ModelsDialog({
name,
provider_uuid: providerUuid,
abilities,
context_length: parseContextLength(
contextLength,
t('models.contextLengthInvalid'),
),
extra_args: extraArgsObj,
} as never);
} else if (modelType === 'embedding') {
@@ -325,6 +341,7 @@ export default function ModelsDialog({
name: item.model.name,
provider_uuid: providerUuid,
abilities: item.abilities,
context_length: item.model.context_length ?? null,
extra_args: {},
} as never);
} else if (effectiveType === 'embedding') {
@@ -361,6 +378,7 @@ export default function ModelsDialog({
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) {
if (!name.trim()) {
toast.error(t('models.modelNameRequired'));
@@ -375,6 +393,10 @@ export default function ModelsDialog({
name,
provider_uuid: providerUuid,
abilities,
context_length: parseContextLength(
contextLength,
t('models.contextLengthInvalid'),
),
extra_args: extraArgsObj,
} as never);
} else if (modelType === 'embedding') {
@@ -509,8 +531,15 @@ export default function ModelsDialog({
onSpaceLogin={handleSpaceLogin}
onOpenAddModel={() => setAddModelPopoverOpen(provider.uuid)}
onCloseAddModel={() => setAddModelPopoverOpen(null)}
onAddModel={(modelType, name, abilities, extraArgs) =>
handleAddModel(provider.uuid, modelType, name, abilities, extraArgs)
onAddModel={(modelType, name, abilities, extraArgs, contextLength) =>
handleAddModel(
provider.uuid,
modelType,
name,
abilities,
extraArgs,
contextLength,
)
}
onScanModels={(modelType) => handleScanModels(provider.uuid, modelType)}
onAddScannedModels={(modelType, models) =>
@@ -518,7 +547,14 @@ export default function ModelsDialog({
}
onOpenEditModel={(modelId) => setEditModelPopoverOpen(modelId)}
onCloseEditModel={() => setEditModelPopoverOpen(null)}
onUpdateModel={(modelId, modelType, name, abilities, extraArgs) =>
onUpdateModel={(
modelId,
modelType,
name,
abilities,
extraArgs,
contextLength,
) =>
handleUpdateModel(
provider.uuid,
modelId,
@@ -526,6 +562,7 @@ export default function ModelsDialog({
name,
abilities,
extraArgs,
contextLength,
)
}
onOpenDeleteConfirm={(modelId) => setDeleteConfirmOpen(modelId)}

View File

@@ -41,6 +41,7 @@ interface AddModelPopoverProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onScanModels: (modelType?: ModelType) => Promise<ScanModelsResult>;
onAddScannedModels: (
@@ -81,6 +82,7 @@ export default function AddModelPopover({
const [mode, setMode] = useState<'manual' | 'scan'>('manual');
const [name, setName] = useState('');
const [abilities, setAbilities] = useState<string[]>([]);
const [contextLength, setContextLength] = useState('');
const [extraArgs, setExtraArgs] = useState<ExtraArg[]>([]);
const [scanLoading, setScanLoading] = useState(false);
const [scannedModels, setScannedModels] = useState<ScannedProviderModel[]>(
@@ -98,6 +100,7 @@ export default function AddModelPopover({
setMode(initialMode);
setName('');
setAbilities([]);
setContextLength('');
setExtraArgs([]);
setScanLoading(false);
setScannedModels([]);
@@ -119,7 +122,11 @@ export default function AddModelPopover({
}, [tab, mode]);
const handleAdd = async () => {
await onAddModel(tab, name, abilities, extraArgs);
const parsedContextLength =
tab === 'llm' && contextLength.trim()
? Number(contextLength.trim())
: null;
await onAddModel(tab, name, abilities, extraArgs, parsedContextLength);
};
const handleTest = async () => {
@@ -318,6 +325,24 @@ export default function AddModelPopover({
</div>
)}
{tab === 'llm' && (
<div className="space-y-2">
<Label htmlFor="add-context-length">
{t('models.contextLength')}
</Label>
<Input
id="add-context-length"
type="number"
min={1}
step={1}
inputMode="numeric"
placeholder={t('models.contextLengthPlaceholder')}
value={contextLength}
onChange={(e) => setContextLength(e.target.value)}
/>
</div>
)}
<ExtraArgsEditor
args={extraArgs}
onChange={setExtraArgs}

View File

@@ -31,6 +31,7 @@ interface ModelItemProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onTestModel: (
name: string,
@@ -92,6 +93,11 @@ export default function ModelItem({
const [editAbilities, setEditAbilities] = useState<string[]>(
modelType === 'llm' ? (model as LLMModel).abilities || [] : [],
);
const [editContextLength, setEditContextLength] = useState(
modelType === 'llm' && (model as LLMModel).context_length
? String((model as LLMModel).context_length)
: '',
);
const [editExtraArgs, setEditExtraArgs] = useState<ExtraArg[]>(
convertExtraArgsToArray(model.extra_args),
);
@@ -106,13 +112,27 @@ export default function ModelItem({
setEditAbilities(
modelType === 'llm' ? (model as LLMModel).abilities || [] : [],
);
setEditContextLength(
modelType === 'llm' && (model as LLMModel).context_length
? String((model as LLMModel).context_length)
: '',
);
setEditExtraArgs(convertExtraArgsToArray(model.extra_args));
onResetTestResult();
}
}, [isEditOpen]);
const handleSave = async () => {
await onUpdateModel(editName, editAbilities, editExtraArgs);
const parsedContextLength =
modelType === 'llm' && editContextLength.trim()
? Number(editContextLength.trim())
: null;
await onUpdateModel(
editName,
editAbilities,
editExtraArgs,
parsedContextLength,
);
};
const handleTest = async () => {
@@ -287,6 +307,25 @@ export default function ModelItem({
</div>
)}
{modelType === 'llm' && (
<div className="space-y-2">
<Label htmlFor={`edit-context-length-${model.uuid}`}>
{t('models.contextLength')}
</Label>
<Input
id={`edit-context-length-${model.uuid}`}
type="number"
min={1}
step={1}
inputMode="numeric"
placeholder={t('models.contextLengthPlaceholder')}
value={editContextLength}
disabled={isLangBotModels}
onChange={(e) => setEditContextLength(e.target.value)}
/>
</div>
)}
<ExtraArgsEditor
args={editExtraArgs}
onChange={setEditExtraArgs}

View File

@@ -60,6 +60,7 @@ interface ProviderCardProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onScanModels: (modelType?: ModelType) => Promise<ScanModelsResult>;
onAddScannedModels: (
@@ -74,6 +75,7 @@ interface ProviderCardProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onOpenDeleteConfirm: (modelId: string) => void;
onCloseDeleteConfirm: () => void;
@@ -405,13 +407,19 @@ export default function ProviderCard({
onOpenDeleteConfirm={onOpenDeleteConfirm}
onCloseDeleteConfirm={onCloseDeleteConfirm}
onDeleteModel={() => onDeleteModel(model.uuid, 'llm')}
onUpdateModel={(name, abilities, extraArgs) =>
onUpdateModel={(
name,
abilities,
extraArgs,
contextLength,
) =>
onUpdateModel(
model.uuid,
'llm',
name,
abilities,
extraArgs,
contextLength,
)
}
onTestModel={(name, abilities, extraArgs) =>

View File

@@ -53,6 +53,7 @@ export interface ModelItemProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onTest: (
name: string,
@@ -89,6 +90,7 @@ export interface ProviderCardProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onScanModels: (modelType?: ModelType) => Promise<ScanModelsResult>;
onAddScannedModels: (
@@ -103,6 +105,7 @@ export interface ProviderCardProps {
name: string,
abilities: string[],
extraArgs: ExtraArg[],
contextLength?: number | null,
) => Promise<void>;
onOpenDeleteConfirm: (modelId: string) => void;
onCloseDeleteConfirm: () => void;

View File

@@ -96,6 +96,7 @@ export interface LLMModel {
provider_uuid: string;
provider?: ModelProvider;
abilities?: string[];
context_length?: number | null;
extra_args?: object;
}

View File

@@ -201,6 +201,9 @@ const enUS = {
selectModelAbilities: 'Select model abilities',
visionAbility: 'Vision Ability',
functionCallAbility: 'Function Call',
contextLength: 'Context Window',
contextLengthPlaceholder: 'Unknown',
contextLengthInvalid: 'Context window must be a positive integer',
extraParameters: 'Extra Parameters',
addParameter: 'Add Parameter',
keyName: 'Key Name',

View File

@@ -206,6 +206,9 @@ const esES = {
selectModelAbilities: 'Seleccionar capacidades del modelo',
visionAbility: 'Capacidad de visión',
functionCallAbility: 'Llamada a funciones',
contextLength: 'Ventana de contexto',
contextLengthPlaceholder: 'Desconocido',
contextLengthInvalid: 'La ventana de contexto debe ser un entero positivo',
extraParameters: 'Parámetros adicionales',
addParameter: 'Añadir parámetro',
keyName: 'Nombre de la clave',

View File

@@ -204,6 +204,10 @@ const jaJP = {
selectModelAbilities: 'モデル機能を選択',
visionAbility: '視覚機能',
functionCallAbility: '関数呼び出し',
contextLength: 'コンテキストウィンドウ',
contextLengthPlaceholder: '不明',
contextLengthInvalid:
'コンテキストウィンドウは正の整数である必要があります',
extraParameters: '追加パラメータ',
addParameter: 'パラメータを追加',
keyName: 'キー名',

View File

@@ -203,6 +203,10 @@ const ruRU = {
selectModelAbilities: 'Выберите возможности модели',
visionAbility: 'Распознавание изображений',
functionCallAbility: 'Вызов функций',
contextLength: 'Контекстное окно',
contextLengthPlaceholder: 'Неизвестно',
contextLengthInvalid:
'Контекстное окно должно быть положительным целым числом',
extraParameters: 'Дополнительные параметры',
addParameter: 'Добавить параметр',
keyName: 'Имя ключа',

View File

@@ -199,6 +199,9 @@ const thTH = {
selectModelAbilities: 'เลือกความสามารถของโมเดล',
visionAbility: 'ความสามารถด้านภาพ',
functionCallAbility: 'การเรียกฟังก์ชัน',
contextLength: 'หน้าต่างบริบท',
contextLengthPlaceholder: 'ไม่ทราบ',
contextLengthInvalid: 'หน้าต่างบริบทต้องเป็นจำนวนเต็มบวก',
extraParameters: 'พารามิเตอร์เพิ่มเติม',
addParameter: 'เพิ่มพารามิเตอร์',
keyName: 'ชื่อคีย์',

View File

@@ -203,6 +203,9 @@ const viVN = {
selectModelAbilities: 'Chọn khả năng mô hình',
visionAbility: 'Khả năng thị giác',
functionCallAbility: 'Gọi hàm',
contextLength: 'Cửa sổ ngữ cảnh',
contextLengthPlaceholder: 'Không rõ',
contextLengthInvalid: 'Cửa sổ ngữ cảnh phải là số nguyên dương',
extraParameters: 'Tham số bổ sung',
addParameter: 'Thêm tham số',
keyName: 'Tên khóa',

View File

@@ -193,6 +193,9 @@ const zhHans = {
selectModelAbilities: '选择模型能力',
visionAbility: '视觉能力',
functionCallAbility: '函数调用',
contextLength: '上下文窗口',
contextLengthPlaceholder: '未知',
contextLengthInvalid: '上下文窗口必须是正整数',
extraParameters: '额外参数',
addParameter: '添加参数',
keyName: '键名',

View File

@@ -193,6 +193,9 @@ const zhHant = {
selectModelAbilities: '選擇模型能力',
visionAbility: '視覺能力',
functionCallAbility: '函數呼叫',
contextLength: '上下文視窗',
contextLengthPlaceholder: '未知',
contextLengthInvalid: '上下文視窗必須是正整數',
extraParameters: '額外參數',
addParameter: '新增參數',
keyName: '鍵名',