Merge branch 'feature/1.4' into 'main'

Feature/1.4

See merge request apipark/APIPark!144
This commit is contained in:
秦圆圆
2025-01-03 16:12:34 +08:00
18 changed files with 1560 additions and 1582 deletions
@@ -45,8 +45,6 @@
"成功": "K43fcaf94",
"上线失败": "Kc71c6a9",
"失败": "K56c686f8",
"正常": "Ke039b9b5",
"无效": "K1da86266",
"申请系统": "K1ff96ff",
"所属团队": "K9bf855d6",
"申请人": "K11b994ed",
@@ -239,6 +237,10 @@
"请输入IP地址或CIDR范围,每条以换行分割": "K49731763",
"待更新": "K3a34d49b",
"待删除": "Kd2850420",
"内容": "K3e7aa0ad",
"调用地址": "K2f5fdf5e",
"消费者 IP": "K1bc5e0a3",
"鉴权名称": "K6f39ea21",
"暂无操作权限,请联系管理员分配。": "K23fda291",
"微信小程序": "K4618cb0a",
"获取文件,需填路径": "Ka854f511",
@@ -324,12 +326,25 @@
"响应 Body": "Kd2be51d1",
"默认工作表": "K2a3f24ac",
"至": "K7e1ab4b0",
"详情": "Kf1b166e7",
"暂不支持带有双斜杠//的url": "K28555332",
"输入的IP或CIDR不符合格式": "K83237c89",
"请正确输入路径,如/usr/*或*/usr/*": "K5ae2c87a",
"必填项": "K71661ee8",
"不是有效邮箱地址": "Kcbee3f8",
"获取 AI providers 失败": "K94b48734",
"AI 供应商": "Kf23a8988",
"预览": "K4d81a657",
"AI 服务": "Kd2c34e2c",
"模型": "Kfede1c7c",
"已用 Token": "K89f135a7",
"拦截": "Kb7df6ac1",
"放行": "K5c1722fe",
"编辑时间": "K1acc30b2",
"查看详情": "K35f990b0",
"AI API 列表": "K91144ebd",
"重置": "K50d471b2",
"查询": "Kee8ae330",
"请输入 APIURL 搜索": "Kf8187c33",
"最近一次更新者": "K617f34f1",
"最近一次更新时间": "K6ebca204",
"保存": "Kabfe9512",
@@ -357,18 +372,18 @@
"变量": "K13ffbe88",
"输入这个接口的描述": "K79c8cfaf",
"重试次数": "K469e475a",
"拦截接口": "Kee4139c2",
"开启拦截后,网关会拦截所有该路径的请求。": "K3e38ea",
"模型配置": "K8a35059b",
"路由": "Kf9dcef3a",
"添加路由": "K6134bbe8",
"输入 URL 查找路由": "Kf85b83a0",
"模型供应商": "Kcf9f90b8",
"模型": "Kfede1c7c",
"参数": "Ke99513a0",
"审核": "Kb595f40",
"通过": "K54e27f57",
"拒绝": "K8582af3f",
"发布结果": "Kd568e15c",
"查看详情": "K35f990b0",
"申请发布": "Kdbc1f6cb",
"回滚": "Kb6860a3f",
"请确认是否回滚?": "Ka3494f4b",
@@ -377,13 +392,28 @@
"终止发布": "Ke1b79b93",
"请确认是否终止发布?": "Ka2449180",
"新建版本": "K2cb02f38",
"从 (0) 获取 API KEY": "Kb3e34847",
"未配置 AI 模型": "Kd752a3a8",
"前往设置": "K8b7ac871",
"AI 模型": "K99935e6f",
"配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务": "K2260837a",
"已设置": "Kf97448b3",
"未设置": "K30d4d8df",
"默认模型": "Kc2ee5223",
"负载优先级": "K608af899",
"负载优先级决定在原供应商异常或停用后,优先使用哪一个供应商。优先级数字越小,优先级越高。": "K65b7a96",
"优先级必须大于 0": "K9eccff16",
"请输入优先级": "Kfcf02780",
"API Key(默认 Key": "K5c6dcf58",
"LLM 状态管理": "K59bf8ed9",
"正常": "Ke039b9b5",
"停用": "Kedd64e4d",
"异常": "K23a3bd72",
"启用": "K52c8a730",
"已配置": "K66a7d24c",
"未配置": "Kaf074220",
"默认": "Kd9a46c29",
"AI 模型管理": "K7ac2be34",
"配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务": "K2260837a",
"同步最新模型": "K18dccc1a",
"默认:": "K2a3aeb8d",
"从 (0) 获取 API KEY": "Kb3e34847",
"待审核": "K35612f29",
"已审核": "K47eaafde",
"发布申请": "K56b4254f",
@@ -437,6 +467,27 @@
"了解 APIPark 如何更好地管理 API 和 AI": "K1afaf20e",
"了解更多功能": "K48f7e21f",
"隐藏该教程": "K698296e2",
"请输入 APIKey": "K8b88ef63",
"API Key": "Kcbd30819",
"请填写 APIKey": "Kcb6e2d3e",
"永不过期": "K9dfa2c97",
"设置过期时间": "Ke13e332a",
"请选择过期时间": "K409aa8ba",
"超额": "Kba69594c",
"过期": "Kb9e7ceda",
"错误": "Kac405b50",
"请选择状态": "K3fde5b49",
"编辑 APIKey": "K434b7e76",
"删除成功": "K28190dbc",
"停用成功": "Kb5fcf5b8",
"启用成功": "K5940d788",
"排序成功": "K8743bccd",
"编辑": "Kad207008",
"调用优先级": "K19590c2c",
"APIKey 资源池": "Kefb03657",
"支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡": "Kc0352e64",
"请输入名称搜索": "Kd25acba1",
"添加 APIKey": "K6d0388a0",
"请输入账号": "Kf076f63c",
"账号": "K80a560a1",
"请输入密码": "K25c895d5",
@@ -460,7 +511,6 @@
"加入部门": "Ke6f00b44",
"确定删除成员?此操作无法恢复,确认操作?": "K501cb1e7",
"成员与部门": "Kf20863b5",
"启用": "K52c8a730",
"禁用": "K718c9310",
"输入用户名、邮箱查找成员": "K5f27a546",
"移出当前部门": "K7c97c5df",
@@ -473,10 +523,13 @@
"成员": "K74aef1ad",
"设置成员和对应的角色,成员只能够看到权限范围内的功能和数据。": "K3f1077c9",
"搜索部门": "Kdce62a6",
"操作成功,即将刷新页面": "K9ada4366",
"数据源类型": "Ka46b9b24",
"数据源地址": "Kbb0cdcd0",
"Organization": "Kd9dfb884",
"鉴权 Token": "K3e770a75",
"请求前缀": "K9b332ab1",
"HTTP 头部": "K3d78d483",
"密钥": "K8ef69ee2",
"上传密钥": "Kba3507d6",
"密钥文件的后缀名一般为 .key 的文件内容": "K93ac0f23",
@@ -490,7 +543,6 @@
"集群": "Ke93d36ed",
"修改配置": "K877985b7",
"设置访问 API 的集群,让 API 在分布式环境中稳定运行,并且能够根据业务需求进行灵活扩展和优化。": "Kdf66a675",
"异常": "K23a3bd72",
"私有网络": "Ke1b1865",
"公共网络": "K4786c57c",
"管理地址": "Kf12b3034",
@@ -501,14 +553,19 @@
"数据源": "K8fa58214",
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
"统计图表": "K1358acf",
"数据日志": "K17dc3a62",
"地址(IP:端口)": "K62dabdf6",
"组织(Organization": "K2db12335",
"添加策略": "K34d0d409",
"输入名称、筛选条件查找": "Kbb4298ac",
"处理日志": "Ke429194e",
"脱敏前": "K8c34c02f",
"脱敏后": "K8e3d388d",
"编辑策略": "Kc82b8374",
"策略类型": "K4b34a5e5",
"匹配条件": "K57f0fee8",
"数据脱敏规则": "K10650c58",
"输入调用地址、消费者IP和消费者条件查找": "K84ffb1dd",
"配置脱敏规则": "K1b34a9ab",
"匹配值": "K26d22405",
"脱敏类型": "K1546e1fe",
@@ -520,7 +577,6 @@
"脱敏规则": "K4cd91d61",
"自定义字符串; 值:": "K8dcad979",
"起始位置:(0)位;长度:(1)位": "K82e3f7b7",
"编辑": "Kad207008",
"已选择(0)项(1)数据": "K49dfc123",
"所有(0)": "K8457ea34",
"属性名称": "K7ca9a795",
@@ -543,13 +599,9 @@
"请求协议": "K6bc47edb",
"请求方式": "K1365fe45",
"转发规则设置": "K90f3c02f",
"拦截": "Kb7df6ac1",
"放行": "K5c1722fe",
"路由详情": "K28435c5c",
"只允许上传PNG、JPG或SVG格式的图片": "Ka9c08390",
"服务名称": "K413b9869",
"服务类型": "K9919285b",
"AI 服务": "Kd2c34e2c",
"REST 服务": "K62840d62",
"默认 AI 供应商": "Kcef64f4d",
"创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API": "K300c89d4",
@@ -604,8 +656,6 @@
"选择API": "Kcc8265e1",
"路径": "Kc380335f",
"请输入请求路径进行搜索": "K8aefc1e4",
"重置": "K50d471b2",
"查询": "Kee8ae330",
"导出": "Ka2c794a2",
"退出全屏": "Kaf70c3b",
"(0)调用详情": "Kd22841a4",
@@ -695,7 +745,6 @@
"修改": "K9cbe1e0",
"访问授权": "Kb6e9328f",
"添加授权": "Kd23d1716",
"永不过期": "K9dfa2c97",
"到期时间": "Kfa920c0",
"订阅的服务": "Kcce1af60",
"审核详情": "Kfefa9b58",
@@ -710,7 +759,6 @@
"创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务": "K5c4e2865",
"订阅的服务数量:已通过 (0) 个,申请中 (1) 个": "K3c7b175f",
"输入名称、ID 查找消费者": "K3a6f905d",
"退出测试": "Kbe3e9335",
"服务市场": "K370a3eb2",
"服务详情": "Kf7ec36d",
"申请服务": "K58ca9485",
@@ -704,64 +704,105 @@
"Kbdec9fa": "Filter Criteria",
"Kbcbb7391": "Processed Count",
"Kad207008": "Edit",
"K630c9e6d": "APIPark",
"Ka3e9f580": "Release Name",
"Kb2480682": "Policy List",
"K118d8d74": "Data Format",
"Kfe7c7d2d": "Keyword",
"K2f57a694": "Regular Expression",
"K8953e0a6": "Mobile Number",
"K6f86a038": "ID Card Number",
"K7954e7c8": "Bank Card Number",
"K320fdb17": "Amount",
"K7867acda": "Date",
"K7d327ae8": "Partial Display",
"Kfbf38e3c": "Partial Masking",
"Kd8c1fbb0": "Truncate",
"K89829921": "Replace",
"K480a7165": "Shuffle",
"Kea0d69df": "Random String",
"Ke7c84d1d": "Custom String",
"K49731763": "Please enter IP address or CIDR range, each separated by a newline.",
"K83237c89": "The entered IP or CIDR does not meet the format.",
"K5ae2c87a": "Please enter the correct path, such as /usr/* or */usr/*.",
"Kc82b8374": "Edit Policy",
"K4b34a5e5": "Policy Type",
"K57f0fee8": "Match Conditions",
"K10650c58": "Data Masking Rules",
"K1b34a9ab": "Configure Masking Rules",
"K26d22405": "Match Value",
"K1546e1fe": "Masking Type",
"K9b9b0629": "Starting Position",
"K52c84fe1": "Length",
"Kde84409c": "Replace Type",
"K338653b4": "Replace Value",
"Kbaeed3b7": "JSON Path",
"K4cd91d61": "Masking Rules",
"K8dcad979": "Custom String; Value:",
"K82e3f7b7": "Starting Position: (0); Length: (1)",
"K49dfc123": "Selected (0) items (1) data",
"K8457ea34": "All (0)",
"K7ca9a795": "Attribute Name",
"Kc4391744": "Attribute Value",
"K678e13fc": "Configure (0)",
"K508d8bf4": "Integration Address",
"K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms",
"K1da86266": "Invalid",
"K3a34d49b": "Pending Update",
"Kd2850420": "Pending Deletion",
"K9ada4366": "Operation successful, the page will refresh shortly",
"K9b332ab1": "Request prefix",
"K3d78d483": "HTTP headers",
"K17dc3a62": "Data logs",
"Ke429194e": "Processing logs",
"K84ffb1dd": "Enter the invocation address, consumer IP, and consumer condition to search",
"Kb147fabc": "Create",
"K40ca4f2": "Update",
"K3e7aa0ad": "Content",
"K2f5fdf5e": "Call Address",
"K1bc5e0a3": "Consumer IP",
"K6f39ea21": "Authentication Name",
"K8c34c02f": "Before Masking",
"K8e3d388d": "After Masking"
"K630c9e6d": "APIPark",
"Ka3e9f580": "Release Name",
"Kb2480682": "Policy List",
"K118d8d74": "Data Format",
"Kfe7c7d2d": "Keyword",
"K2f57a694": "Regular Expression",
"K8953e0a6": "Mobile Number",
"K6f86a038": "ID Card Number",
"K7954e7c8": "Bank Card Number",
"K320fdb17": "Amount",
"K7867acda": "Date",
"K7d327ae8": "Partial Display",
"Kfbf38e3c": "Partial Masking",
"Kd8c1fbb0": "Truncate",
"K89829921": "Replace",
"K480a7165": "Shuffle",
"Kea0d69df": "Random String",
"Ke7c84d1d": "Custom String",
"K49731763": "Please enter IP address or CIDR range, each separated by a newline.",
"K83237c89": "The entered IP or CIDR does not meet the format.",
"K5ae2c87a": "Please enter the correct path, such as /usr/* or */usr/*.",
"Kc82b8374": "Edit Policy",
"K4b34a5e5": "Policy Type",
"K57f0fee8": "Match Conditions",
"K10650c58": "Data Masking Rules",
"K1b34a9ab": "Configure Masking Rules",
"K26d22405": "Match Value",
"K1546e1fe": "Masking Type",
"K9b9b0629": "Starting Position",
"K52c84fe1": "Length",
"Kde84409c": "Replace Type",
"K338653b4": "Replace Value",
"Kbaeed3b7": "JSON Path",
"K4cd91d61": "Masking Rules",
"K8dcad979": "Custom String; Value:",
"K82e3f7b7": "Starting Position: (0); Length: (1)",
"K49dfc123": "Selected (0) items (1) data",
"K8457ea34": "All (0)",
"K7ca9a795": "Attribute Name",
"Kc4391744": "Attribute Value",
"K678e13fc": "Configure (0)",
"K508d8bf4": "Integration Address",
"K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms",
"K1da86266": "Invalid",
"K3a34d49b": "Pending Update",
"Kd2850420": "Pending Deletion",
"K9ada4366": "Operation successful, the page will refresh shortly",
"K9b332ab1": "Request prefix",
"K3d78d483": "HTTP headers",
"K17dc3a62": "Data logs",
"Ke429194e": "Processing logs",
"K84ffb1dd": "Enter the invocation address, consumer IP, and consumer condition to search",
"Kb147fabc": "Create",
"K40ca4f2": "Update",
"K3e7aa0ad": "Content",
"K2f5fdf5e": "Call Address",
"K1bc5e0a3": "Consumer IP",
"K6f39ea21": "Authentication Name",
"K8c34c02f": "Before Masking",
"K8e3d388d": "After Masking",
"K94b48734": "Failed to get AI providers",
"Kf23a8988": "AI Provider",
"K4d81a657": "Preview",
"K91144ebd": "AI API List",
"Kf8187c33": "Please enter API URL to search",
"Kee4139c2": "Intercept Interface",
"K3e38ea": "After enabling interception, the gateway will intercept all requests on this path.",
"Kd752a3a8": "AI Model Not Configured",
"K8b7ac871": "Go to Settings",
"Kf97448b3": "Configured",
"K30d4d8df": "Not Configured",
"Kc2ee5223": "Default Model",
"K608af899": "Load Priority",
"K65b7a96": "Load priority determines which provider to use first when the original provider is abnormal or disabled. The smaller the priority number, the higher the priority.",
"K9eccff16": "Priority must be greater than 0",
"Kfcf02780": "Please enter priority",
"K5c6dcf58": "API Key (Default Key)",
"K59bf8ed9": "LLM Status Management",
"Kedd64e4d": "Disable",
"K2a3aeb8d": "Default:",
"K8b88ef63": "Please enter APIKey",
"Kcbd30819": "API Key",
"Kcb6e2d3e": "Please fill in APIKey",
"Ke13e332a": "Set expiration time",
"K409aa8ba": "Please select expiration time",
"Kba69594c": "Overage",
"Kb9e7ceda": "Expired",
"Kac405b50": "Error",
"K3fde5b49": "Please select status",
"K434b7e76": "Edit APIKey",
"K28190dbc": "Deleted successfully",
"Kb5fcf5b8": "Disabled successfully",
"K5940d788": "Enabled successfully",
"K8743bccd": "Sorted successfully",
"K19590c2c": "Call Priority",
"K89f135a7": "Used Token",
"K1acc30b2": "Edit Time",
"Kefb03657": "APIKey Resource Pool",
"Kc0352e64": "Supports creating multiple APIKeys under a single API model provider for intelligent load balancing",
"Kd25acba1": "Please enter name to search",
"K6d0388a0": "Add APIKey"
}
File diff suppressed because it is too large Load Diff
@@ -2,7 +2,6 @@
"K630c9e6d": "APIPark",
"Ka3e9f580": "发布名称",
"Kb2480682": "策略列表",
"K1da86266": "无效",
"K76036e25": "HTTP 请求头",
"K44607e3f": "全等匹配",
"Kc287500a": "前缀匹配",
@@ -6,11 +6,18 @@
"Kecbb0e45": "System",
"Ka358e23d": "General",
"K449058e9": "API Gateway",
"K99935e6f": "AI Model",
"K1deaa2dd": "User",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kf1b166e7": "Details",
"K7ac2be34": "AI Model Management",
"K18dccc1a": "Sync Latest Model",
"K5cfdd950": "This data cannot be recovered after deletion. Are you sure you want to delete?",
"K28435c5c": "API Details",
"Kb9052305": "Search Username, Email",
"K40a89bd8": "Enter Name, ID to Search Member"
"Kbe3e9335": "Exit Test",
"K40a89bd8": "Enter Name, ID to Search Member",
"K1da86266": "Invalid",
"Kb147fabc": "Create",
"K40ca4f2": "Update"
}
@@ -7,16 +7,19 @@
"Kecbb0e45": "システム",
"Ka358e23d": "一般",
"K449058e9": "API ゲートウェイ",
"K99935e6f": "AI モデル",
"K1deaa2dd": "ユーザー",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kffd7e274": "無審査:すべてのアプリケーションがこのサービスにサブスクライブできます",
"K8a8b13e4": "手動審査:承認されたアプリケーションのみがこのサービスにサブスクライブできます",
"Kf1b166e7": "詳細",
"K7ac2be34": "AI モデル管理",
"K18dccc1a": "最新モデルを同期",
"K9bdd8403": "API を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。",
"Kc8239422": "チームにはユーザー、アプリケーション、サービスが含まれ、異なるチームのアプリケーションとサービスのデータは分離されています。企業内の部門/プロジェクトグループ/チームの管理に使用できます。",
"Ka0a8840a": "他のアプリケーションのサブスクリプション申請をレビューし、承認後に API リクエストが発行できます。",
"K5cfdd950": "このデータを削除すると、復元できません。削除しますか?",
"K28435c5c": "API 詳細",
"Kb9052305": "ユーザー名またはメールを検索",
"K5ece3bac": "チームとメンバーを設定してから、チーム内でサービスとアプリケーションを作成し、API をサブスクライブできます。メンバーは所属チーム内のサービスとアプリケーションのみを表示できます。",
"K1512e983": "アプリケーション呼び出し統計",
@@ -28,11 +31,15 @@
"K546e46f": "Application ID",
"K95764d1d": "Application を削除",
"K667bbbe7": "Application を追加",
"Kbe3e9335": "テスト終了",
"K8723422e": "アプリケーションを接続",
"K93d5a66e": "接続アプリケーション数",
"K216a1ac7": "アプリケーション開発者",
"K27924db": "アプリケーション管理者",
"Kd55c6887": "レビュー",
"K831aa6c0": "申請元-アプリケーション",
"K40a89bd8": "名前または ID を入力してサービスを検索"
"K40a89bd8": "名前または ID を入力してサービスを検索",
"K1da86266": "無効",
"Kb147fabc": "新規作成",
"K40ca4f2": "更新"
}
@@ -7,16 +7,19 @@
"Kecbb0e45": "系统",
"Ka358e23d": "常规",
"K449058e9": "API 网关",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用户",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kffd7e274": "无审核:允许所有消费者订阅该服务",
"K8a8b13e4": "人工审核:仅允许审核通过的消费者订阅该服务",
"Kf1b166e7": "详情",
"K7ac2be34": "AI 模型管理",
"K18dccc1a": "同步最新模型",
"K9bdd8403": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Kc8239422": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Ka0a8840a": "审核其他消费者的订阅申请,审核通过后的才可发起 API 请求。",
"K5cfdd950": "该数据删除后将无法找回,是否删除?",
"K28435c5c": "API 详情",
"Kb9052305": "搜索用户名、邮箱",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K1512e983": "消费者调用统计",
@@ -28,11 +31,14 @@
"K546e46f": "消费者 ID",
"K95764d1d": "删除消费者",
"K667bbbe7": "添加消费者",
"Kbe3e9335": "退出测试",
"K8723422e": "接入消费者",
"K93d5a66e": "接入消费者数量",
"K216a1ac7": "消费者开发者",
"K27924db": "消费者管理员",
"Kd55c6887": "审核",
"K831aa6c0": "申请方-消费者",
"K40a89bd8": "输入名称、ID 查找服务"
"K40a89bd8": "输入名称、ID 查找服务",
"Kb147fabc": "新建",
"K40ca4f2": "更新"
}
@@ -7,16 +7,19 @@
"Kecbb0e45": "系統",
"Ka358e23d": "常規",
"K449058e9": "API 網關",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用戶",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kffd7e274": "無審核:允許所有應用程式訂閱該服務",
"K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務",
"Kf1b166e7": "詳情",
"K7ac2be34": "AI 模型管理",
"K18dccc1a": "同步最新模型",
"K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。",
"Kc8239422": "團隊中包含了人員、應用程式和服務,不同團隊之間的應用程式和服務數據是隔離的,可用於管理企業內部不同的部門/項目組/團隊。",
"Ka0a8840a": "審核其他應用程式的訂閱申請,審核通過後的才可發起 API 請求。",
"K5cfdd950": "該數據刪除後將無法找回,是否刪除?",
"K28435c5c": "API 詳情",
"Kb9052305": "搜索用戶名、電郵",
"K5ece3bac": "設置團隊和成員,然後你可以在團隊內創建服務和應用程式、訂閱API,成員只能看到所屬團隊內的服務和應用程式。",
"K1512e983": "應用程式調用統計",
@@ -28,11 +31,15 @@
"K546e46f": "應用程式 ID",
"K95764d1d": "刪除應用程式",
"K667bbbe7": "添加應用程式",
"Kbe3e9335": "退出測試",
"K8723422e": "接入應用程式",
"K93d5a66e": "接入應用程式數量",
"K216a1ac7": "應用程式開發者",
"K27924db": "應用程式管理員",
"Kd55c6887": "審核",
"K831aa6c0": "申請方-應用程式",
"K40a89bd8": "輸入名稱、ID 查找服務"
"K40a89bd8": "輸入名稱、ID 查找服務",
"K1da86266": "無效",
"Kb147fabc": "新建",
"K40ca4f2": "更新"
}
@@ -716,5 +716,46 @@
"K1bc5e0a3": "消费者 IP",
"K6f39ea21": "鉴权名称",
"K8c34c02f": "脱敏前",
"K8e3d388d": "脱敏后"
}
"K8e3d388d": "脱敏后",
"K94b48734": "获取 AI providers 失败",
"Kf23a8988": "AI 供应商",
"K4d81a657": "预览",
"K91144ebd": "AI API 列表",
"Kf8187c33": "请输入 APIURL 搜索",
"Kee4139c2": "拦截接口",
"K3e38ea": "开启拦截后,网关会拦截所有该路径的请求。",
"Kd752a3a8": "未配置 AI 模型",
"K8b7ac871": "前往设置",
"Kf97448b3": "已设置",
"K30d4d8df": "未设置",
"Kc2ee5223": "默认模型",
"K608af899": "负载优先级",
"K65b7a96": "负载优先级决定在原供应商异常或停用后,优先使用哪一个供应商。优先级数字越小,优先级越高。",
"K9eccff16": "优先级必须大于 0",
"Kfcf02780": "请输入优先级",
"K5c6dcf58": "API Key(默认 Key",
"K59bf8ed9": "LLM 状态管理",
"Kedd64e4d": "停用",
"K2a3aeb8d": "默认:",
"K8b88ef63": "请输入 APIKey",
"Kcbd30819": "API Key",
"Kcb6e2d3e": "请填写 APIKey",
"Ke13e332a": "设置过期时间",
"K409aa8ba": "请选择过期时间",
"Kba69594c": "超额",
"Kb9e7ceda": "过期",
"Kac405b50": "错误",
"K3fde5b49": "请选择状态",
"K434b7e76": "编辑 APIKey",
"K28190dbc": "删除成功",
"Kb5fcf5b8": "停用成功",
"K5940d788": "启用成功",
"K8743bccd": "排序成功",
"K19590c2c": "调用优先级",
"K89f135a7": "已用 Token",
"K1acc30b2": "编辑时间",
"Kefb03657": "APIKey 资源池",
"Kc0352e64": "支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡",
"Kd25acba1": "请输入名称搜索",
"K6d0388a0": "添加 APIKey"
}
File diff suppressed because it is too large Load Diff
@@ -17,11 +17,11 @@
import G6, { EdgeConfig } from '@antv/g6'
import {
SELF_SPACE_CONTENT_COLOR,
OUT_SPACE_CONTENT_COLOR,
OUT_SPACE_THEME,
RELATIVE_PICTURE_NODE_FONTSIZE,
SELF_SPACE_THEME,
OUT_SPACE_THEME
SELF_SPACE_CONTENT_COLOR,
SELF_SPACE_THEME
} from '@core/const/system-running/const'
import { NodeData } from '@core/const/system-running/type'
import { TopologyProjectItem, TopologyServiceItem } from '@core/pages/systemRunning/SystemRunning'
@@ -144,131 +144,6 @@ export const getNodeSpacing = (num: number, nodes?: unknown) => {
return result
}
// const registerEdge = () => {
// G6.registerEdge(
// 'line-running',
// {
// options: {
// style: EDGE_STYLE
// },
// afterDraw: (cfg, group) => {
// const lineDash = [4, 2, 1, 2]
// if (!group) return
// const shape = group.get('children')[0]
// let index = 0
// // Define the animation
// shape.animate(
// () => {
// index = index + 0.4
// if (index > 1000) {
// index = 0
// }
// const res = {
// lineDash,
// lineDashOffset: -index
// }
// return res
// },
// {
// repeat: true,
// duration: 5000
// }
// )
// },
// setState: (name, value, item) => {
// if (!item || !name) return
// const shape = item.get('keyShape')
// const itemStatus = item.getStates()
// if (
// !['edge-success', 'edge-error', 'edge-transparent'].includes(name) &&
// itemStatus.some((state) => ['edge-error', 'edge-success', 'edge-transparent'].includes(state))
// )
// return
// const theme = item?._cfg?.model?.style?.stroke || SELF_SPACE_THEME
// if (name === 'running') {
// if (value) {
// shape.attr({
// lineWidth: 4,
// shadowColor: theme,
// shadowBlur: 2
// })
// } else {
// shape.attr(EDGE_STYLE)
// }
// }
// }
// },
// 'quadratic'
// )
// }
// private initGraph = (container: ElementRef) => {
// const element = container.nativeElement
// this.graph = new G6.Graph({
// container: container.nativeElement,
// plugins: [this.tooltip],
// layout: {
// type: 'force',
// // 稳定系数,初始动画的加载时长(稳定性)=节点数量/稳定系数
// alphaDecay: 0.08,
// // 因为有分组的存在,整体布局需要往左偏移一点
// center: [element.scrollWidth / 2 - 150, element.scrollHeight / 2],
// preventOverlap: true,
// linkDistance: (d: nodeAny) => {
// if (d.source.id === 'node0') {
// return 100
// }
// return 30
// },
// nodeStrength: (d: nodeAny) => {
// if (d.isLeaf) {
// return -50
// }
// return -10
// },
// edgeStrength: (d: nodeAny) => {
// if (d.source.id === 'node1' || d.source.id === 'node2' || d.source.id === 'node3') {
// return 0.7
// }
// return 0.1
// }
// },
// modes: {
// default: ['drag-canvas', 'drag-node', 'zoom-canvas']
// },
// defaultNode: {
// size: [24, 24],
// style: {
// radius: 5,
// stroke: '#69c0ff',
// lineWidth: 1,
// fillOpacity: 1
// },
// labelCfg: {
// style: {
// fontSize: RELATIVE_PICTURE_NODE_FONTSIZE,
// fill: this.textColor
// },
// position: 'bottom',
// offset: 12
// }
// },
// defaultEdge: {
// type: 'line-running',
// label: $t('详情',
// labelCfg: {
// style: {
// fill: '5B8FF9',
// opacity: 0
// }
// }
// }
// })
// }
export class UnionFind {
private parent: Record<string, string>
private rank: Record<string, number>
@@ -1,5 +1,6 @@
import { STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import { ModelDetailData } from '@core/pages/aiSetting/types'
import { Select, Space, message } from 'antd'
import React, { useEffect, useState } from 'react'
@@ -52,10 +53,10 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, st
const selectedProvider: AIProvider = value ? providers.find((p) => p.id === value) : providers[0]
onChange?.(selectedProvider.id, selectedProvider)
} else {
message.error(msg || t('Failed to fetch AI providers'))
message.error(msg || t('获取 AI providers 失败'))
}
} catch (error) {
message.error(t('Failed to fetch AI providers'))
message.error(t('获取 AI providers 失败'))
} finally {
isMounted && setLoading(false)
}
@@ -70,7 +71,7 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, st
return (
<Space className="flex items-center">
<span>{t('AI 供应商')}:</span>
<span>{$t('AI 供应商')}:</span>
<Select
value={value}
onChange={(selectedValue) => {
@@ -1,111 +0,0 @@
import { Codebox } from '@common/components/postcat/api/Codebox'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import { AIProvider } from '@core/components/AIProviderSelect'
import { App, DatePicker, Form, Input, Switch } from 'antd'
import dayjs from 'dayjs'
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import { EditAPIKey } from '../types'
interface ApiKeyContentProps {
provider?: AIProvider
entity: EditAPIKey
}
const ApiKeyContent: React.FC<ApiKeyContentProps> = forwardRef(({ provider, entity }, ref) => {
const [form] = Form.useForm()
const [neverExpire, setNeverExpire] = useState(true)
const { fetchData } = useFetch()
const { message } = App.useApp()
useEffect(() => {
try {
const isNeverExpire = entity.expire_time === 0
setNeverExpire(isNeverExpire)
form.setFieldsValue({
name: entity.name,
expire_time: isNeverExpire ? undefined : dayjs(entity.expire_time),
config: entity.config
})
} catch (e) {
form.setFieldsValue({
name: entity.name,
expire_time: undefined,
config: ''
})
}
}, [])
const handleOk = async () => {
try {
const values = await form.validateFields()
const { expire_time, ...restValues } = values
const expireTime = neverExpire ? 0 : expire_time.valueOf()
const response = await fetchData<BasicResponse<null>>('ai/resource/key', {
method: entity.id ? 'PUT' : 'POST',
eoParams: { provider: provider?.id, id: entity.id },
eoBody: { ...restValues, expire_time: expireTime },
eoTransformKeys: ['config']
})
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
return true
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return false
}
} catch (error) {
console.error('Validation failed:', error)
}
}
useImperativeHandle(ref, () => ({
handleOk
}))
const handleNeverExpireChange = (checked: boolean) => {
setNeverExpire(checked)
if (!checked) {
form.setFieldsValue({
expire_time: dayjs().add(7, 'days')
})
}
}
return (
<Form form={form} layout="vertical">
<Form.Item name="name" label={$t('名称')} rules={[{ required: true, message: $t('请输入 APIKey') }]}>
<Input />
</Form.Item>
<Form.Item label={$t('API Key')} name="config" rules={[{ required: true, message: $t('请填写 APIKey') }]}>
<Codebox
editorTheme="vs-dark"
readOnly={false}
width="100%"
height="150px"
language="json"
enableToolbar={false}
/>
</Form.Item>
<Form.Item label={$t('过期时间')} name="neverExpire" valuePropName="checked">
<div className="flex items-center">
<Switch onChange={handleNeverExpireChange} checked={neverExpire} />
<span className="ml-2">{neverExpire ? $t('永不过期') : $t('设置过期时间')}</span>
</div>
</Form.Item>
{!neverExpire && (
<Form.Item
name="expire_time"
label={$t('过期时间')}
rules={[{ required: true, message: $t('请选择过期时间') }]}
>
<DatePicker style={{ width: '100%' }} showTime />
</Form.Item>
)}
</Form>
)
})
export default ApiKeyContent
@@ -1,40 +0,0 @@
import { $t } from '@common/locales'
import { Select, Space, theme } from 'antd'
import React from 'react'
interface StatusFilterProps {
value: string[]
onChange: (value: string[]) => void
}
const StatusFilter: React.FC<StatusFilterProps> = ({ value, onChange }) => {
const { token } = theme.useToken()
const options = [
{ label: $t('Normal'), value: 'normal', color: token.colorSuccess },
{ label: $t('Exceeded'), value: 'exceeded', color: token.colorError },
{ label: $t('Expired'), value: 'expired', color: token.colorWarning },
{ label: $t('Disabled'), value: 'disabled', color: token.colorTextDisabled },
{ label: $t('Error'), value: 'error', color: token.colorError }
]
return (
<Space>
<span>{$t('Status')}:</span>
<Select
mode="multiple"
value={value}
onChange={onChange}
style={{ width: 300 }}
placeholder={$t('Filter by status')}
allowClear
options={options.map((option) => ({
...option,
label: <span style={{ color: option.color }}>{option.label}</span>
}))}
/>
</Space>
)
}
export default StatusFilter
@@ -12,7 +12,7 @@ import { Alert, App, Button, Typography } from 'antd'
import dayjs from 'dayjs'
import React, { useEffect, useRef, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { APIKey } from './types'
import { APIs } from './types'
const ApiSettings: React.FC = () => {
const pageListRef = useRef<ActionType>(null)
@@ -35,6 +35,9 @@ const ApiSettings: React.FC = () => {
pageListRef.current?.reload()
}, [selectedProvider])
const handlePreview = (record: APIs) => {
navigate(`service/${record.team.id}/aiInside/${record.service.id}/route/${record.id}`)
}
const requestApis = async (params: any) => {
if (!selectedProvider) return
setQueryBtnLoading(true)
@@ -52,7 +55,7 @@ const ApiSettings: React.FC = () => {
eoParams.start = startTime
eoParams.end = endTime
}
const response = await fetchData<BasicResponse<{ data: APIKey[] }>>('ai/apis', {
const response = await fetchData<BasicResponse<{ data: APIs[] }>>('ai/apis', {
method: 'GET',
eoParams
})
@@ -81,28 +84,28 @@ const ApiSettings: React.FC = () => {
}
}
const operation: PageProColumns<APIKey>[] = [
const operation: PageProColumns<APIs>[] = [
{
title: '',
key: 'option',
btnNums: 4,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: APIKey) => [
render: (_: React.ReactNode, entity: APIs) => [
<TableBtnWithPermission
access="system.settings.ai_key_resource.manager"
key="edit"
btnType="edit"
// onClick={() => handleEdit(entity)}
btnTitle={$t('编辑')}
access="team.service.router.view"
key="preview"
btnType="view"
onClick={() => handlePreview(entity)}
btnTitle={$t('预览')}
/>
]
}
]
const columns: PageProColumns<APIKey>[] = [
const columns: PageProColumns<APIs>[] = [
{
title: 'AI 服务',
title: $t('AI 服务'),
dataIndex: 'name',
key: 'name',
width: 180
@@ -112,10 +115,16 @@ const ApiSettings: React.FC = () => {
dataIndex: 'request_path',
key: 'request_path',
width: 200,
ellipsis: true
ellipsis: true,
render: (text: string, record: APIs) => (
<p>
<Typography.Text type="success">{record.method}</Typography.Text>
<span className="ml-1">{text}</span>
</p>
)
},
{
title: '模型',
title: $t('模型'),
dataIndex: ['model', 'name'],
key: 'model',
width: 150,
@@ -125,14 +134,14 @@ const ApiSettings: React.FC = () => {
valueEnum: {}
},
{
title: '已用 Token',
title: $t('已用 Token'),
dataIndex: 'use_token',
key: 'use_token',
width: 120,
sorter: true
},
{
title: '是否放行',
title: $t('是否放行'),
dataIndex: 'disabled',
ellipsis: true,
filters: true,
@@ -144,7 +153,7 @@ const ApiSettings: React.FC = () => {
}
},
{
title: '编辑时间',
title: $t('编辑时间'),
dataIndex: 'update_time',
key: 'update_time',
width: 200,
@@ -165,22 +174,26 @@ const ApiSettings: React.FC = () => {
const renderProviderBanner = () => {
if (!provider) return null
console.log(provider)
if (provider.status === 'disabled' || provider.status === 'abnormal') {
const message =
provider.status === 'disabled'
? $t(`当前供应商异常,以下API均临时调用 ${provider.backupName} 下的 ${provider.backupModel} 模型能力。`)
: $t(`当前供应商异常,以下API均临时调用 ${provider.backupName} 下的 ${provider.backupModel} 模型能力。`)
const type = provider.status === 'disabled' ? 'warning' : 'error'
return (
<Alert
message={message}
type="warning"
type={type}
className="my-4"
showIcon
action={
<Button size="small" type="link" onClick={() => navigate('/aisetting')}>
<Button
size="small"
type="link"
onClick={() => {
navigate('/aisetting')
}}
>
{$t('查看详情')}
</Button>
}
@@ -192,7 +205,7 @@ const ApiSettings: React.FC = () => {
return (
<InsidePage
className="overflow-y-auto gap-4 pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X"
className="overflow-y-auto gap-4 pb-PAGE_INSIDE_B"
pageTitle={$t('AI API 列表')}
description={
<>
@@ -211,7 +224,7 @@ const ApiSettings: React.FC = () => {
showBorder={false}
scrollPage={false}
>
<div className="h-[calc(100%-1rem-36px)]">
<div className="h-[calc(100%-1rem-36px)] pr-PAGE_INSIDE_X">
<PageList
ref={pageListRef}
rowKey="id"
@@ -1,16 +1,18 @@
export interface APIKey extends EditAPIKey {
status: 'normal' | 'exceeded' | 'expired' | 'disabled' | 'error'
use_token: number
update_time: string
can_delete: boolean
priority: number
}
export interface EditAPIKey {
id?: string
name: string
config: string
expire_time: number
}
export interface APIs {
id: string;
name: string;
service: {
id: string;
name: string;
};
team:{
id: string;
name: string;
},
method: string;
request_path: string;
model: string;
disabled: boolean;
update_time: string;
use_token: number;
}
@@ -25,7 +25,7 @@ const ApiKeyContent: React.FC<ApiKeyContentProps> = forwardRef(({ provider, enti
setNeverExpire(isNeverExpire)
form.setFieldsValue({
name: entity.name,
expire_time: isNeverExpire ? undefined : dayjs(entity.expire_time),
expire_time: isNeverExpire ? undefined : dayjs(entity.expire_time * 1000),
config: entity.config
})
} catch (e) {
@@ -41,7 +41,7 @@ const ApiKeyContent: React.FC<ApiKeyContentProps> = forwardRef(({ provider, enti
try {
const values = await form.validateFields()
const { expire_time, ...restValues } = values
const expireTime = neverExpire ? 0 : expire_time.valueOf()
const expireTime = neverExpire ? 0 : Math.trunc(expire_time.valueOf() / 1000)
const response = await fetchData<BasicResponse<null>>('ai/resource/key', {
method: entity.id ? 'PUT' : 'POST',
@@ -11,22 +11,22 @@ const StatusFilter: React.FC<StatusFilterProps> = ({ value, onChange }) => {
const { token } = theme.useToken()
const options = [
{ label: $t('Normal'), value: 'normal', color: token.colorSuccess },
{ label: $t('Exceeded'), value: 'exceeded', color: token.colorError },
{ label: $t('Expired'), value: 'expired', color: token.colorWarning },
{ label: $t('Disabled'), value: 'disabled', color: token.colorTextDisabled },
{ label: $t('Error'), value: 'error', color: token.colorError }
{ label: $t('正常'), value: 'normal', color: token.colorSuccess },
{ label: $t('超额'), value: 'exceeded', color: token.colorError },
{ label: $t('过期'), value: 'expired', color: token.colorWarning },
{ label: $t('停用'), value: 'disabled', color: token.colorTextDisabled },
{ label: $t('错误'), value: 'error', color: token.colorError }
]
return (
<Space>
<span>{$t('Status')}:</span>
<span>{$t('状态')}:</span>
<Select
mode="multiple"
value={value}
onChange={onChange}
style={{ width: 300 }}
placeholder={$t('Filter by status')}
placeholder={$t('请选择状态')}
allowClear
options={options.map((option) => ({
...option,