feat(ui): i18n for model filters, group alias settings, and hub columns

This commit is contained in:
吹雪 2026-06-10 13:24:49 +09:00
parent 625feb0ab7
commit 4018f9a836
5 changed files with 134 additions and 50 deletions

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Card, Text } from "@tremor/react";
interface ModelGroupInfo {
@ -32,6 +33,7 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
showFiltersCard = true,
className = "",
}) => {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState<string>("");
const [selectedProvider, setSelectedProvider] = useState<string>("");
const [selectedMode, setSelectedMode] = useState<string>("");
@ -132,24 +134,24 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
const filtersContent = (
<div className="flex flex-wrap gap-4 items-center">
<div>
<Text className="text-sm font-medium mb-2">Search Models:</Text>
<Text className="text-sm font-medium mb-2">{t("modelFilters.searchModels")}</Text>
<input
type="text"
placeholder="Search model names..."
placeholder={t("modelFilters.searchPlaceholder")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="border rounded px-3 py-2 w-64 h-10 text-sm"
/>
</div>
<div>
<Text className="text-sm font-medium mb-2">Provider:</Text>
<Text className="text-sm font-medium mb-2">{t("modelFilters.provider")}</Text>
<select
value={selectedProvider}
onChange={(e) => setSelectedProvider(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10"
>
<option value="" className="text-sm text-gray-600">
All Providers
{t("modelFilters.allProviders")}
</option>
{modelHubData &&
getUniqueProviders(modelHubData).map((provider) => (
@ -160,14 +162,14 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
</select>
</div>
<div>
<Text className="text-sm font-medium mb-2">Mode:</Text>
<Text className="text-sm font-medium mb-2">{t("modelFilters.mode")}</Text>
<select
value={selectedMode}
onChange={(e) => setSelectedMode(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10"
>
<option value="" className="text-sm text-gray-600">
All Modes
{t("modelFilters.allModes")}
</option>
{modelHubData &&
getUniqueModes(modelHubData).map((mode) => (
@ -178,14 +180,14 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
</select>
</div>
<div>
<Text className="text-sm font-medium mb-2">Features:</Text>
<Text className="text-sm font-medium mb-2">{t("modelFilters.features")}</Text>
<select
value={selectedFeature}
onChange={(e) => setSelectedFeature(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10"
>
<option value="" className="text-sm text-gray-600">
All Features
{t("modelFilters.allFeatures")}
</option>
{modelHubData &&
getUniqueFeatures(modelHubData).map((feature) => (
@ -203,7 +205,7 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
onClick={resetFilters}
className="text-blue-600 hover:text-blue-800 text-sm underline h-10 flex items-center"
>
Clear Filters
{t("common.clear")} {t("modelFilters.filtersLabel")}
</button>
</div>
)}

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { PlusCircleIcon, PencilIcon, TrashIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
import { setCallbacksCall } from "./networking";
import { Card, Title, Text, Table, TableHead, TableHeaderCell, TableBody, TableRow, TableCell } from "@tremor/react";
@ -23,6 +24,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
initialModelGroupAlias = {},
onAliasUpdate,
}) => {
const { t } = useTranslation();
const [aliases, setAliases] = useState<AliasItem[]>([]);
const [newAlias, setNewAlias] = useState({ aliasName: "", targetModelGroup: "" });
const [editingAlias, setEditingAlias] = useState<AliasItem | null>(null);
@ -67,20 +69,20 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
return true;
} catch (error) {
console.error("Failed to save model group alias settings:", error);
NotificationsManager.fromBackend("Failed to save model group alias settings");
NotificationsManager.fromBackend(t("modelGroupAliasSettings.saveFailed"));
return false;
}
};
const handleAddAlias = async () => {
if (!newAlias.aliasName || !newAlias.targetModelGroup) {
NotificationsManager.fromBackend("Please provide both alias name and target model group");
NotificationsManager.fromBackend(t("modelGroupAliasSettings.bothRequired"));
return;
}
// Check for duplicate alias names
if (aliases.some((alias) => alias.aliasName === newAlias.aliasName)) {
NotificationsManager.fromBackend("An alias with this name already exists");
NotificationsManager.fromBackend(t("modelGroupAliasSettings.duplicateAlias"));
return;
}
@ -95,7 +97,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
if (await saveAliasesToBackend(updatedAliases)) {
setAliases(updatedAliases);
setNewAlias({ aliasName: "", targetModelGroup: "" });
NotificationsManager.success("Alias added successfully");
NotificationsManager.success(t("modelGroupAliasSettings.addSuccess"));
}
};
@ -107,13 +109,13 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
if (!editingAlias) return;
if (!editingAlias.aliasName || !editingAlias.targetModelGroup) {
NotificationsManager.fromBackend("Please provide both alias name and target model group");
NotificationsManager.fromBackend(t("modelGroupAliasSettings.bothRequired"));
return;
}
// Check for duplicate alias names (excluding current alias)
if (aliases.some((alias) => alias.id !== editingAlias.id && alias.aliasName === editingAlias.aliasName)) {
NotificationsManager.fromBackend("An alias with this name already exists");
NotificationsManager.fromBackend(t("modelGroupAliasSettings.duplicateAlias"));
return;
}
@ -122,7 +124,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
if (await saveAliasesToBackend(updatedAliases)) {
setAliases(updatedAliases);
setEditingAlias(null);
NotificationsManager.success("Alias updated successfully");
NotificationsManager.success(t("modelGroupAliasSettings.updateSuccess"));
}
};
@ -135,7 +137,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
if (await saveAliasesToBackend(updatedAliases)) {
setAliases(updatedAliases);
NotificationsManager.success("Alias deleted successfully");
NotificationsManager.success(t("modelGroupAliasSettings.deleteSuccess"));
}
};
@ -152,11 +154,8 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
<Card className="mb-6">
<div className="flex items-center justify-between cursor-pointer" onClick={() => setIsExpanded(!isExpanded)}>
<div className="flex flex-col">
<Title className="mb-0">Model Group Alias Settings</Title>
<p className="text-sm text-gray-500">
Create aliases for your model groups to simplify API calls. For example, you can create an alias
&apos;gpt-4o&apos; that points to &apos;gpt-4o-mini-openai&apos; model group.
</p>
<Title className="mb-0">{t("modelGroupAliasSettings.title")}</Title>
<p className="text-sm text-gray-500">{t("modelGroupAliasSettings.description")}</p>
</div>
<div className="flex items-center">
{isExpanded ? (
@ -170,10 +169,10 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
{isExpanded && (
<div className="mt-4">
<div className="mb-6">
<Text className="text-sm font-medium text-gray-700 mb-2">Add New Alias</Text>
<Text className="text-sm font-medium text-gray-700 mb-2">{t("modelGroupAliasSettings.addNewAlias")}</Text>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-xs text-gray-500 mb-1">Alias Name</label>
<label className="block text-xs text-gray-500 mb-1">{t("modelGroupAliasSettings.aliasName")}</label>
<input
type="text"
value={newAlias.aliasName}
@ -188,7 +187,9 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
/>
</div>
<div>
<label className="block text-xs text-gray-500 mb-1">Target Model Group</label>
<label className="block text-xs text-gray-500 mb-1">
{t("modelGroupAliasSettings.targetModelGroup")}
</label>
<input
type="text"
value={newAlias.targetModelGroup}
@ -209,21 +210,23 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
className={`flex items-center px-4 py-2 rounded-md text-sm ${!newAlias.aliasName || !newAlias.targetModelGroup ? "bg-gray-300 text-gray-500 cursor-not-allowed" : "bg-green-600 text-white hover:bg-green-700"}`}
>
<PlusCircleIcon className="w-4 h-4 mr-1" />
Add Alias
{t("modelGroupAliasSettings.addAlias")}
</button>
</div>
</div>
</div>
<Text className="text-sm font-medium text-gray-700 mb-2">Manage Existing Aliases</Text>
<Text className="text-sm font-medium text-gray-700 mb-2">{t("modelGroupAliasSettings.manageExisting")}</Text>
<div className="rounded-lg custom-border relative mb-6">
<div className="overflow-x-auto">
<Table className="[&_td]:py-0.5 [&_th]:py-1">
<TableHead>
<TableRow>
<TableHeaderCell className="py-1 h-8">Alias Name</TableHeaderCell>
<TableHeaderCell className="py-1 h-8">Target Model Group</TableHeaderCell>
<TableHeaderCell className="py-1 h-8">Actions</TableHeaderCell>
<TableHeaderCell className="py-1 h-8">{t("modelGroupAliasSettings.aliasName")}</TableHeaderCell>
<TableHeaderCell className="py-1 h-8">
{t("modelGroupAliasSettings.targetModelGroup")}
</TableHeaderCell>
<TableHeaderCell className="py-1 h-8">{t("common.actions")}</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
@ -263,13 +266,13 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
onClick={handleUpdateAlias}
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100"
>
Save
{t("common.save")}
</button>
<button
onClick={handleCancelEdit}
className="text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100"
>
Cancel
{t("common.cancel")}
</button>
</div>
</TableCell>
@ -301,7 +304,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
{aliases.length === 0 && (
<TableRow>
<TableCell colSpan={3} className="py-0.5 text-sm text-gray-500 text-center">
No aliases added yet. Add a new alias above.
{t("modelGroupAliasSettings.noAliases")}
</TableCell>
</TableRow>
)}
@ -312,10 +315,8 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
{/* Configuration Example */}
<Card>
<Title className="mb-4">Configuration Example</Title>
<Text className="text-gray-600 mb-4">
Here&apos;s how your current aliases would look in the config.yaml:
</Text>
<Title className="mb-4">{t("modelGroupAliasSettings.configExample")}</Title>
<Text className="text-gray-600 mb-4">{t("modelGroupAliasSettings.configExampleDesc")}</Text>
<div className="bg-gray-100 rounded-lg p-4 font-mono text-sm">
<div className="text-gray-700">
router_settings:

View file

@ -2,6 +2,7 @@ import { ColumnDef } from "@tanstack/react-table";
import { Button, Badge, Text } from "@tremor/react";
import { Tooltip, Tag } from "antd";
import { CopyOutlined, InfoCircleOutlined } from "@ant-design/icons";
import i18n from "@/lib/i18n";
interface ModelHubData {
model_group: string;
@ -55,7 +56,7 @@ export const modelHubColumns = (
): ColumnDef<ModelHubData>[] => {
const allColumns: ColumnDef<ModelHubData>[] = [
{
header: "Public Model Name",
header: i18n.t("modelHubTableColumns.publicModelName"),
accessorKey: "model_group",
enableSorting: true,
sortingFn: "alphanumeric",
@ -66,7 +67,7 @@ export const modelHubColumns = (
<div className="space-y-1">
<div className="flex items-center space-x-2">
<Text className="font-medium text-sm">{model.model_group}</Text>
<Tooltip title="Copy model name">
<Tooltip title={i18n.t("modelHubTableColumns.copyModelName")}>
<CopyOutlined
onClick={() => copyToClipboard(model.model_group)}
className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs"
@ -82,7 +83,7 @@ export const modelHubColumns = (
},
},
{
header: "Provider",
header: i18n.t("modelHubTableColumns.provider"),
accessorKey: "providers",
enableSorting: true,
sortingFn: (rowA, rowB) => {
@ -109,7 +110,7 @@ export const modelHubColumns = (
},
},
{
header: "Mode",
header: i18n.t("modelHubTableColumns.mode"),
accessorKey: "mode",
enableSorting: true,
sortingFn: "alphanumeric",
@ -129,7 +130,7 @@ export const modelHubColumns = (
},
},
{
header: "Tokens",
header: i18n.t("modelHubTableColumns.tokens"),
accessorKey: "max_input_tokens",
enableSorting: true,
sortingFn: (rowA, rowB) => {
@ -154,7 +155,7 @@ export const modelHubColumns = (
},
},
{
header: "Cost/1M",
header: i18n.t("modelHubTableColumns.costPerMillion"),
accessorKey: "input_cost_per_token",
enableSorting: true,
sortingFn: (rowA, rowB) => {
@ -176,7 +177,7 @@ export const modelHubColumns = (
},
},
{
header: "Features",
header: i18n.t("modelHubTableColumns.features"),
accessorKey: "capabilities",
enableSorting: false,
cell: ({ row }) => {
@ -200,7 +201,7 @@ export const modelHubColumns = (
},
},
{
header: "Public",
header: i18n.t("modelHubTableColumns.public"),
accessorKey: "is_public_model_group",
enableSorting: true,
sortingFn: (rowA, rowB) => {
@ -213,11 +214,11 @@ export const modelHubColumns = (
return model.is_public_model_group === true ? (
<Badge color="green" size="xs">
Yes
{i18n.t("common.yes")}
</Badge>
) : (
<Badge color="gray" size="xs">
No
{i18n.t("common.no")}
</Badge>
);
},
@ -226,7 +227,7 @@ export const modelHubColumns = (
},
},
{
header: "Details",
header: i18n.t("common.details"),
id: "details",
enableSorting: false,
cell: ({ row }) => {
@ -234,8 +235,8 @@ export const modelHubColumns = (
return (
<Button size="xs" variant="secondary" onClick={() => showModal(model)} icon={InfoCircleOutlined}>
<span className="hidden lg:inline">Details</span>
<span className="lg:hidden">Info</span>
<span className="hidden lg:inline">{i18n.t("common.details")}</span>
<span className="lg:hidden">{i18n.t("modelHubTableColumns.infoShort")}</span>
</Button>
);
},

View file

@ -1259,6 +1259,46 @@
"allTools": "All tools",
"infoShort": "Info"
},
"modelFilters": {
"searchModels": "Search Models:",
"searchPlaceholder": "Search model names...",
"provider": "Provider:",
"allProviders": "All Providers",
"mode": "Mode:",
"allModes": "All Modes",
"features": "Features:",
"allFeatures": "All Features",
"filtersLabel": "Filters"
},
"modelGroupAliasSettings": {
"title": "Model Group Alias Settings",
"description": "Create aliases for your model groups to simplify API calls. For example, you can create an alias 'gpt-4o' that points to 'gpt-4o-mini-openai' model group.",
"addNewAlias": "Add New Alias",
"aliasName": "Alias Name",
"targetModelGroup": "Target Model Group",
"addAlias": "Add Alias",
"manageExisting": "Manage Existing Aliases",
"noAliases": "No aliases added yet. Add a new alias above.",
"configExample": "Configuration Example",
"configExampleDesc": "Here's how your current aliases would look in the config.yaml:",
"bothRequired": "Please provide both alias name and target model group",
"duplicateAlias": "An alias with this name already exists",
"saveFailed": "Failed to save model group alias settings",
"addSuccess": "Alias added successfully",
"updateSuccess": "Alias updated successfully",
"deleteSuccess": "Alias deleted successfully"
},
"modelHubTableColumns": {
"publicModelName": "Public Model Name",
"copyModelName": "Copy model name",
"provider": "Provider",
"mode": "Mode",
"tokens": "Tokens",
"costPerMillion": "Cost/1M",
"features": "Features",
"public": "Public",
"infoShort": "Info"
},
"common": {
"save": "Save",
"cancel": "Cancel",

View file

@ -1259,6 +1259,46 @@
"allTools": "所有工具",
"infoShort": "详情"
},
"modelFilters": {
"searchModels": "搜索模型:",
"searchPlaceholder": "搜索模型名称...",
"provider": "提供商:",
"allProviders": "所有提供商",
"mode": "模式:",
"allModes": "所有模式",
"features": "功能:",
"allFeatures": "所有功能",
"filtersLabel": "筛选"
},
"modelGroupAliasSettings": {
"title": "模型组别名设置",
"description": "为您的模型组创建别名以简化 API 调用。例如,您可以创建别名 'gpt-4o' 指向 'gpt-4o-mini-openai' 模型组。",
"addNewAlias": "添加新别名",
"aliasName": "别名名称",
"targetModelGroup": "目标模型组",
"addAlias": "添加别名",
"manageExisting": "管理现有别名",
"noAliases": "暂无别名。请在上方添加新别名。",
"configExample": "配置示例",
"configExampleDesc": "以下是您当前别名在 config.yaml 中的样子:",
"bothRequired": "请同时提供别名名称和目标模型组",
"duplicateAlias": "同名别名已存在",
"saveFailed": "保存模型组别名设置失败",
"addSuccess": "别名添加成功",
"updateSuccess": "别名更新成功",
"deleteSuccess": "别名删除成功"
},
"modelHubTableColumns": {
"publicModelName": "公开模型名称",
"copyModelName": "复制模型名称",
"provider": "提供商",
"mode": "模式",
"tokens": "Token",
"costPerMillion": "费用/百万",
"features": "功能",
"public": "公开",
"infoShort": "详情"
},
"common": {
"save": "保存",
"cancel": "取消",