plugin-ai-api 1.0.15 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/302.25edd5d75460acbf.js +10 -0
- package/dist/client/757.71e30f2a1306562d.js +10 -0
- package/dist/client/902.4238b04ac667c30a.js +10 -0
- package/dist/client/97.37cda285d7da3a26.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/302.9b27a263901d54d8.js +10 -0
- package/dist/client-v2/757.c377e2f2b054d89d.js +10 -0
- package/dist/client-v2/902.d40d7bda106124c8.js +10 -0
- package/dist/client-v2/97.fc922c37ced86831.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +9 -9
- package/dist/locale/en-US.json +78 -2
- package/dist/locale/vi-VN.json +86 -0
- package/dist/locale/zh-CN.json +86 -10
- package/dist/server/billing.js +331 -0
- package/dist/server/collections/ai-api-config.js +12 -0
- package/dist/server/collections/ai-api-model-prices.js +55 -0
- package/dist/server/collections/ai-api-usage-records.js +9 -0
- package/dist/server/collections/ai-api-user-quota-buckets.js +54 -0
- package/dist/server/collections/ai-api-user-quota-policies.js +62 -0
- package/dist/server/plugin.js +23 -2
- package/dist/server/resource/ai-api-config.js +8 -0
- package/dist/server/resource/ai-api-usage-monitor.js +86 -0
- package/dist/server/routes/chat-completions.js +12 -2
- package/dist/server/routes/completions.js +12 -2
- package/dist/server/routes/router.js +14 -1
- package/dist/server/usage.js +17 -2
- package/dist/server/validation.js +102 -0
- package/package.json +1 -1
- package/src/client/plugin.tsx +73 -48
- package/src/client-v2/locale.ts +1 -0
- package/src/client-v2/pages/GeneralPage.tsx +170 -0
- package/src/client-v2/pages/ModelPricingPage.tsx +285 -0
- package/src/client-v2/pages/UsagePage.tsx +248 -0
- package/src/client-v2/pages/UserQuotasPage.tsx +258 -0
- package/src/client-v2/pages/api.ts +16 -0
- package/src/client-v2/plugin.tsx +21 -3
- package/src/locale/en-US.json +78 -2
- package/src/locale/vi-VN.json +86 -0
- package/src/locale/zh-CN.json +86 -10
- package/src/server/__tests__/billing-quota.test.ts +134 -0
- package/src/server/__tests__/billing.test.ts +33 -0
- package/src/server/__tests__/usage-monitor.test.ts +63 -0
- package/src/server/__tests__/usage-route.test.ts +4 -0
- package/src/server/billing.ts +387 -0
- package/src/server/collections/ai-api-config.ts +63 -51
- package/src/server/collections/ai-api-model-prices.ts +25 -0
- package/src/server/collections/ai-api-usage-records.ts +9 -0
- package/src/server/collections/ai-api-user-quota-buckets.ts +24 -0
- package/src/server/collections/ai-api-user-quota-policies.ts +32 -0
- package/src/server/plugin.ts +24 -2
- package/src/server/resource/ai-api-config.ts +82 -74
- package/src/server/resource/ai-api-usage-monitor.ts +74 -0
- package/src/server/routes/chat-completions.ts +13 -2
- package/src/server/routes/completions.ts +13 -2
- package/src/server/routes/router.ts +16 -1
- package/src/server/usage.ts +17 -1
- package/src/server/validation.ts +62 -0
- package/dist/client/950.83390c5f1d5a97fb.js +0 -10
- package/dist/client-v2/950.42b30b5cc9e32b8f.js +0 -10
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
Form,
|
|
6
|
+
Input,
|
|
7
|
+
InputNumber,
|
|
8
|
+
Modal,
|
|
9
|
+
Popconfirm,
|
|
10
|
+
Select,
|
|
11
|
+
Space,
|
|
12
|
+
Switch,
|
|
13
|
+
Table,
|
|
14
|
+
Tag,
|
|
15
|
+
message,
|
|
16
|
+
} from 'antd';
|
|
17
|
+
import type { ColumnsType } from 'antd/es/table';
|
|
18
|
+
import { useFlowContext } from '@nocobase/flow-engine';
|
|
19
|
+
import { useT } from '../locale';
|
|
20
|
+
import { errorMessage, unwrapData } from './api';
|
|
21
|
+
|
|
22
|
+
interface UserSummary {
|
|
23
|
+
id: string | number;
|
|
24
|
+
nickname?: string;
|
|
25
|
+
username?: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface QuotaPolicy {
|
|
30
|
+
id: string | number;
|
|
31
|
+
userId: string | number;
|
|
32
|
+
user?: UserSummary;
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
periodType: 'daily' | 'monthly';
|
|
35
|
+
timezone: string;
|
|
36
|
+
requestLimit?: string;
|
|
37
|
+
totalTokenLimit?: string;
|
|
38
|
+
costLimit?: string;
|
|
39
|
+
currency: string;
|
|
40
|
+
rejectUnpricedModel: boolean;
|
|
41
|
+
missingUsageBehavior: 'allow' | 'use_reserved';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default function UserQuotasPage() {
|
|
45
|
+
const ctx = useFlowContext();
|
|
46
|
+
const t = useT();
|
|
47
|
+
const [form] = Form.useForm<QuotaPolicy>();
|
|
48
|
+
const [rows, setRows] = useState<QuotaPolicy[]>([]);
|
|
49
|
+
const [users, setUsers] = useState<UserSummary[]>([]);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
const [saving, setSaving] = useState(false);
|
|
52
|
+
const [editing, setEditing] = useState<QuotaPolicy>();
|
|
53
|
+
const [open, setOpen] = useState(false);
|
|
54
|
+
|
|
55
|
+
const load = useCallback(async () => {
|
|
56
|
+
setLoading(true);
|
|
57
|
+
try {
|
|
58
|
+
const [policiesResponse, usersResponse] = await Promise.all([
|
|
59
|
+
ctx.api.request({
|
|
60
|
+
url: 'aiApiUserQuotaPolicies:list',
|
|
61
|
+
method: 'get',
|
|
62
|
+
params: { pageSize: 200, appends: ['user'], sort: '-updatedAt' },
|
|
63
|
+
}),
|
|
64
|
+
ctx.api.request({ url: 'users:list', method: 'get', params: { pageSize: 200 } }),
|
|
65
|
+
]);
|
|
66
|
+
setRows(unwrapData<QuotaPolicy[]>(policiesResponse, []));
|
|
67
|
+
setUsers(unwrapData<UserSummary[]>(usersResponse, []));
|
|
68
|
+
} catch (error) {
|
|
69
|
+
message.error(errorMessage(error));
|
|
70
|
+
} finally {
|
|
71
|
+
setLoading(false);
|
|
72
|
+
}
|
|
73
|
+
}, [ctx.api]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
load();
|
|
77
|
+
}, [load]);
|
|
78
|
+
|
|
79
|
+
const showCreate = () => {
|
|
80
|
+
setEditing(undefined);
|
|
81
|
+
form.setFieldsValue({
|
|
82
|
+
enabled: true,
|
|
83
|
+
periodType: 'monthly',
|
|
84
|
+
timezone: 'UTC',
|
|
85
|
+
currency: 'USD',
|
|
86
|
+
rejectUnpricedModel: true,
|
|
87
|
+
missingUsageBehavior: 'use_reserved',
|
|
88
|
+
} as QuotaPolicy);
|
|
89
|
+
setOpen(true);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const showEdit = (record: QuotaPolicy) => {
|
|
93
|
+
setEditing(record);
|
|
94
|
+
form.setFieldsValue(record);
|
|
95
|
+
setOpen(true);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const save = async () => {
|
|
99
|
+
const values = await form.validateFields();
|
|
100
|
+
setSaving(true);
|
|
101
|
+
try {
|
|
102
|
+
await ctx.api.request({
|
|
103
|
+
url: editing ? `aiApiUserQuotaPolicies:update/${editing.id}` : 'aiApiUserQuotaPolicies:create',
|
|
104
|
+
method: 'post',
|
|
105
|
+
data: values,
|
|
106
|
+
});
|
|
107
|
+
message.success(t('Saved successfully'));
|
|
108
|
+
setOpen(false);
|
|
109
|
+
await load();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
message.error(errorMessage(error));
|
|
112
|
+
} finally {
|
|
113
|
+
setSaving(false);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const remove = async (record: QuotaPolicy) => {
|
|
118
|
+
try {
|
|
119
|
+
await ctx.api.request({
|
|
120
|
+
url: `aiApiUserQuotaPolicies:destroy/${record.id}`,
|
|
121
|
+
method: 'post',
|
|
122
|
+
});
|
|
123
|
+
message.success(t('Deleted successfully'));
|
|
124
|
+
await load();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
message.error(errorMessage(error));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const userLabel = (user?: UserSummary) => user?.nickname || user?.username || user?.email || String(user?.id ?? '');
|
|
131
|
+
const columns: ColumnsType<QuotaPolicy> = [
|
|
132
|
+
{
|
|
133
|
+
title: t('User'),
|
|
134
|
+
key: 'user',
|
|
135
|
+
width: 180,
|
|
136
|
+
render: (_, record) => userLabel(record.user) || String(record.userId),
|
|
137
|
+
},
|
|
138
|
+
{ title: t('Period'), dataIndex: 'periodType', key: 'periodType', width: 100 },
|
|
139
|
+
{
|
|
140
|
+
title: t('Request limit'),
|
|
141
|
+
dataIndex: 'requestLimit',
|
|
142
|
+
key: 'requestLimit',
|
|
143
|
+
width: 130,
|
|
144
|
+
render: (value) => value ?? t('Unlimited'),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
title: t('Token limit'),
|
|
148
|
+
dataIndex: 'totalTokenLimit',
|
|
149
|
+
key: 'totalTokenLimit',
|
|
150
|
+
width: 130,
|
|
151
|
+
render: (value) => value ?? t('Unlimited'),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
title: t('Cost limit'),
|
|
155
|
+
dataIndex: 'costLimit',
|
|
156
|
+
key: 'costLimit',
|
|
157
|
+
width: 120,
|
|
158
|
+
render: (value, record) => (value == null ? t('Unlimited') : `${value} ${record.currency}`),
|
|
159
|
+
},
|
|
160
|
+
{ title: t('Timezone'), dataIndex: 'timezone', key: 'timezone', width: 140 },
|
|
161
|
+
{
|
|
162
|
+
title: t('Status'),
|
|
163
|
+
dataIndex: 'enabled',
|
|
164
|
+
key: 'enabled',
|
|
165
|
+
width: 100,
|
|
166
|
+
render: (enabled: boolean) => (
|
|
167
|
+
<Tag color={enabled ? 'green' : 'default'}>{enabled ? t('Enabled') : t('Disabled')}</Tag>
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: t('Actions'),
|
|
172
|
+
key: 'actions',
|
|
173
|
+
width: 150,
|
|
174
|
+
fixed: 'right',
|
|
175
|
+
render: (_, record) => (
|
|
176
|
+
<Space size={0}>
|
|
177
|
+
<Button type="link" onClick={() => showEdit(record)}>
|
|
178
|
+
{t('Edit')}
|
|
179
|
+
</Button>
|
|
180
|
+
<Popconfirm title={t('Delete this quota?')} onConfirm={() => remove(record)}>
|
|
181
|
+
<Button type="link" danger>
|
|
182
|
+
{t('Delete')}
|
|
183
|
+
</Button>
|
|
184
|
+
</Popconfirm>
|
|
185
|
+
</Space>
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Card
|
|
192
|
+
title={t('User quotas')}
|
|
193
|
+
extra={
|
|
194
|
+
<Button type="primary" onClick={showCreate}>
|
|
195
|
+
{t('Add quota')}
|
|
196
|
+
</Button>
|
|
197
|
+
}
|
|
198
|
+
>
|
|
199
|
+
<Table rowKey="id" columns={columns} dataSource={rows} loading={loading} scroll={{ x: 1100 }} />
|
|
200
|
+
<Modal
|
|
201
|
+
title={editing ? t('Edit quota') : t('Add quota')}
|
|
202
|
+
open={open}
|
|
203
|
+
onCancel={() => setOpen(false)}
|
|
204
|
+
onOk={save}
|
|
205
|
+
confirmLoading={saving}
|
|
206
|
+
destroyOnClose
|
|
207
|
+
>
|
|
208
|
+
<Form form={form} layout="vertical" preserve={false}>
|
|
209
|
+
<Form.Item name="userId" label={t('User')} rules={[{ required: true }]}>
|
|
210
|
+
<Select
|
|
211
|
+
disabled={Boolean(editing)}
|
|
212
|
+
showSearch
|
|
213
|
+
optionFilterProp="label"
|
|
214
|
+
options={users.map((user) => ({ label: userLabel(user), value: user.id }))}
|
|
215
|
+
/>
|
|
216
|
+
</Form.Item>
|
|
217
|
+
<Form.Item name="periodType" label={t('Period')} rules={[{ required: true }]}>
|
|
218
|
+
<Select
|
|
219
|
+
options={[
|
|
220
|
+
{ label: t('Daily'), value: 'daily' },
|
|
221
|
+
{ label: t('Monthly'), value: 'monthly' },
|
|
222
|
+
]}
|
|
223
|
+
/>
|
|
224
|
+
</Form.Item>
|
|
225
|
+
<Form.Item name="timezone" label={t('Timezone')} rules={[{ required: true }]}>
|
|
226
|
+
<Input placeholder="UTC" />
|
|
227
|
+
</Form.Item>
|
|
228
|
+
<Form.Item name="requestLimit" label={t('Request limit')}>
|
|
229
|
+
<InputNumber min={0} stringMode style={{ width: '100%' }} />
|
|
230
|
+
</Form.Item>
|
|
231
|
+
<Form.Item name="totalTokenLimit" label={t('Token limit')}>
|
|
232
|
+
<InputNumber min={0} stringMode style={{ width: '100%' }} />
|
|
233
|
+
</Form.Item>
|
|
234
|
+
<Form.Item name="costLimit" label={t('Cost limit')}>
|
|
235
|
+
<InputNumber min={0} stringMode style={{ width: '100%' }} />
|
|
236
|
+
</Form.Item>
|
|
237
|
+
<Form.Item name="currency" label={t('Currency')} rules={[{ required: true }]}>
|
|
238
|
+
<Input />
|
|
239
|
+
</Form.Item>
|
|
240
|
+
<Form.Item name="rejectUnpricedModel" label={t('Reject unpriced models')} valuePropName="checked">
|
|
241
|
+
<Switch />
|
|
242
|
+
</Form.Item>
|
|
243
|
+
<Form.Item name="missingUsageBehavior" label={t('Missing usage behavior')} rules={[{ required: true }]}>
|
|
244
|
+
<Select
|
|
245
|
+
options={[
|
|
246
|
+
{ label: t('Use reserved estimate'), value: 'use_reserved' },
|
|
247
|
+
{ label: t('Allow without token charge'), value: 'allow' },
|
|
248
|
+
]}
|
|
249
|
+
/>
|
|
250
|
+
</Form.Item>
|
|
251
|
+
<Form.Item name="enabled" label={t('Enabled')} valuePropName="checked">
|
|
252
|
+
<Switch />
|
|
253
|
+
</Form.Item>
|
|
254
|
+
</Form>
|
|
255
|
+
</Modal>
|
|
256
|
+
</Card>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ApiEnvelope<T> {
|
|
2
|
+
data?: {
|
|
3
|
+
data?: T;
|
|
4
|
+
meta?: { count?: number };
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function unwrapData<T>(response: unknown, fallback: T): T {
|
|
9
|
+
if (!response || typeof response !== 'object') return fallback;
|
|
10
|
+
const outer = response as ApiEnvelope<T>;
|
|
11
|
+
return outer.data?.data ?? fallback;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function errorMessage(error: unknown): string {
|
|
15
|
+
return error instanceof Error ? error.message : String(error);
|
|
16
|
+
}
|
package/src/client-v2/plugin.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Plugin, Application } from '@nocobase/client-v2';
|
|
2
|
-
import React from 'react';
|
|
3
2
|
|
|
4
3
|
export class PluginAiApiClient extends Plugin<Record<string, never>, Application> {
|
|
5
4
|
async load() {
|
|
@@ -14,10 +13,29 @@ export class PluginAiApiClient extends Plugin<Record<string, never>, Application
|
|
|
14
13
|
menuKey: 'ai-api',
|
|
15
14
|
key: 'index',
|
|
16
15
|
title: this.t('Configuration'),
|
|
17
|
-
|
|
18
|
-
componentLoader: () => import('../client/AiApiConfigPage'),
|
|
16
|
+
componentLoader: () => import('./pages/GeneralPage'),
|
|
19
17
|
});
|
|
20
18
|
|
|
19
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
20
|
+
menuKey: 'ai-api',
|
|
21
|
+
key: 'model-pricing',
|
|
22
|
+
title: this.t('Model pricing'),
|
|
23
|
+
componentLoader: () => import('./pages/ModelPricingPage'),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
27
|
+
menuKey: 'ai-api',
|
|
28
|
+
key: 'user-quotas',
|
|
29
|
+
title: this.t('User quotas'),
|
|
30
|
+
componentLoader: () => import('./pages/UserQuotasPage'),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
34
|
+
menuKey: 'ai-api',
|
|
35
|
+
key: 'usage',
|
|
36
|
+
title: this.t('Usage'),
|
|
37
|
+
componentLoader: () => import('./pages/UsagePage'),
|
|
38
|
+
});
|
|
21
39
|
}
|
|
22
40
|
}
|
|
23
41
|
|
package/src/locale/en-US.json
CHANGED
|
@@ -6,5 +6,81 @@
|
|
|
6
6
|
"Rate Limit": "Rate Limit",
|
|
7
7
|
"Save Configuration": "Save Configuration",
|
|
8
8
|
"Configuration saved": "Configuration saved",
|
|
9
|
-
"Failed to save configuration": "Failed to save configuration"
|
|
10
|
-
|
|
9
|
+
"Failed to save configuration": "Failed to save configuration",
|
|
10
|
+
"API mode": "API mode",
|
|
11
|
+
"Direct LLM": "Direct LLM",
|
|
12
|
+
"AI Employee agent": "AI Employee agent",
|
|
13
|
+
"Default LLM service": "Default LLM service",
|
|
14
|
+
"Enable user quotas": "Enable user quotas",
|
|
15
|
+
"Default reserved output tokens": "Default reserved output tokens",
|
|
16
|
+
"Refresh": "Refresh",
|
|
17
|
+
"Model pricing": "Model pricing",
|
|
18
|
+
"User quotas": "User quotas",
|
|
19
|
+
"Usage": "Usage",
|
|
20
|
+
"LLM service": "LLM service",
|
|
21
|
+
"Model": "Model",
|
|
22
|
+
"Input price / 1M": "Input price / 1M",
|
|
23
|
+
"Output price / 1M": "Output price / 1M",
|
|
24
|
+
"Fixed request cost": "Fixed request cost",
|
|
25
|
+
"Currency": "Currency",
|
|
26
|
+
"Status": "Status",
|
|
27
|
+
"Enabled": "Enabled",
|
|
28
|
+
"Disabled": "Disabled",
|
|
29
|
+
"Actions": "Actions",
|
|
30
|
+
"Edit": "Edit",
|
|
31
|
+
"Delete": "Delete",
|
|
32
|
+
"Delete this price?": "Delete this price?",
|
|
33
|
+
"Add price": "Add price",
|
|
34
|
+
"Edit price": "Edit price",
|
|
35
|
+
"Effective from": "Effective from",
|
|
36
|
+
"Effective to": "Effective to",
|
|
37
|
+
"Notes": "Notes",
|
|
38
|
+
"Saved successfully": "Saved successfully",
|
|
39
|
+
"Deleted successfully": "Deleted successfully",
|
|
40
|
+
"User": "User",
|
|
41
|
+
"Period": "Period",
|
|
42
|
+
"Request limit": "Request limit",
|
|
43
|
+
"Token limit": "Token limit",
|
|
44
|
+
"Cost limit": "Cost limit",
|
|
45
|
+
"Timezone": "Timezone",
|
|
46
|
+
"Unlimited": "Unlimited",
|
|
47
|
+
"Add quota": "Add quota",
|
|
48
|
+
"Edit quota": "Edit quota",
|
|
49
|
+
"Delete this quota?": "Delete this quota?",
|
|
50
|
+
"Daily": "Daily",
|
|
51
|
+
"Monthly": "Monthly",
|
|
52
|
+
"Reject unpriced models": "Reject unpriced models",
|
|
53
|
+
"Missing usage behavior": "Missing usage behavior",
|
|
54
|
+
"Use reserved estimate": "Use reserved estimate",
|
|
55
|
+
"Allow without token charge": "Allow without token charge",
|
|
56
|
+
"Started at": "Started at",
|
|
57
|
+
"Requested model": "Requested model",
|
|
58
|
+
"Resolved service": "Resolved service",
|
|
59
|
+
"Resolved model": "Resolved model",
|
|
60
|
+
"Input tokens": "Input tokens",
|
|
61
|
+
"Output tokens": "Output tokens",
|
|
62
|
+
"Total tokens": "Total tokens",
|
|
63
|
+
"Cost": "Cost",
|
|
64
|
+
"Cost status": "Cost status",
|
|
65
|
+
"Request ID": "Request ID",
|
|
66
|
+
"Failed to load models": "Failed to load models",
|
|
67
|
+
"Select a model": "Select a model",
|
|
68
|
+
"Select an AI Employee": "Select an AI Employee",
|
|
69
|
+
"Usage guide": "Usage guide",
|
|
70
|
+
"OpenAI-compatible endpoint": "OpenAI-compatible endpoint",
|
|
71
|
+
"Base URL": "Base URL",
|
|
72
|
+
"Use a NocoBase API key as the Bearer token.": "Use a NocoBase API key as the Bearer token.",
|
|
73
|
+
"List available models": "List available models",
|
|
74
|
+
"Send a chat completion": "Send a chat completion",
|
|
75
|
+
"Usage filters": "Usage filters",
|
|
76
|
+
"Time range": "Time range",
|
|
77
|
+
"User ID": "User ID",
|
|
78
|
+
"Succeeded": "Succeeded",
|
|
79
|
+
"Failed": "Failed",
|
|
80
|
+
"Started": "Started",
|
|
81
|
+
"Apply filters": "Apply filters",
|
|
82
|
+
"Reset": "Reset",
|
|
83
|
+
"Requests": "Requests",
|
|
84
|
+
"Total cost": "Total cost",
|
|
85
|
+
"Usage records": "Usage records"
|
|
86
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"AI API Gateway": "Cổng AI API",
|
|
3
|
+
"Configuration": "Cấu hình",
|
|
4
|
+
"Default AI Employee": "AI Employee mặc định",
|
|
5
|
+
"Enabled LLM Services": "Dịch vụ LLM được bật",
|
|
6
|
+
"Rate Limit": "Giới hạn tốc độ",
|
|
7
|
+
"Save Configuration": "Lưu cấu hình",
|
|
8
|
+
"Configuration saved": "Đã lưu cấu hình",
|
|
9
|
+
"Failed to save configuration": "Không thể lưu cấu hình",
|
|
10
|
+
"API mode": "Chế độ API",
|
|
11
|
+
"Direct LLM": "LLM trực tiếp",
|
|
12
|
+
"AI Employee agent": "AI Employee agent",
|
|
13
|
+
"Default LLM service": "Dịch vụ LLM mặc định",
|
|
14
|
+
"Enable user quotas": "Bật quota theo người dùng",
|
|
15
|
+
"Default reserved output tokens": "Số output token giữ chỗ mặc định",
|
|
16
|
+
"Refresh": "Làm mới",
|
|
17
|
+
"Model pricing": "Bảng giá model",
|
|
18
|
+
"User quotas": "Quota người dùng",
|
|
19
|
+
"Usage": "Lịch sử sử dụng",
|
|
20
|
+
"LLM service": "Dịch vụ LLM",
|
|
21
|
+
"Model": "Model",
|
|
22
|
+
"Input price / 1M": "Giá input / 1 triệu token",
|
|
23
|
+
"Output price / 1M": "Giá output / 1 triệu token",
|
|
24
|
+
"Fixed request cost": "Chi phí cố định mỗi request",
|
|
25
|
+
"Currency": "Tiền tệ",
|
|
26
|
+
"Status": "Trạng thái",
|
|
27
|
+
"Enabled": "Đang bật",
|
|
28
|
+
"Disabled": "Đã tắt",
|
|
29
|
+
"Actions": "Thao tác",
|
|
30
|
+
"Edit": "Sửa",
|
|
31
|
+
"Delete": "Xóa",
|
|
32
|
+
"Delete this price?": "Xóa cấu hình giá này?",
|
|
33
|
+
"Add price": "Thêm giá",
|
|
34
|
+
"Edit price": "Sửa giá",
|
|
35
|
+
"Effective from": "Hiệu lực từ",
|
|
36
|
+
"Effective to": "Hiệu lực đến",
|
|
37
|
+
"Notes": "Ghi chú",
|
|
38
|
+
"Saved successfully": "Đã lưu thành công",
|
|
39
|
+
"Deleted successfully": "Đã xóa thành công",
|
|
40
|
+
"User": "Người dùng",
|
|
41
|
+
"Period": "Chu kỳ",
|
|
42
|
+
"Request limit": "Giới hạn request",
|
|
43
|
+
"Token limit": "Giới hạn token",
|
|
44
|
+
"Cost limit": "Giới hạn chi phí",
|
|
45
|
+
"Timezone": "Múi giờ",
|
|
46
|
+
"Unlimited": "Không giới hạn",
|
|
47
|
+
"Add quota": "Thêm quota",
|
|
48
|
+
"Edit quota": "Sửa quota",
|
|
49
|
+
"Delete this quota?": "Xóa quota này?",
|
|
50
|
+
"Daily": "Hàng ngày",
|
|
51
|
+
"Monthly": "Hàng tháng",
|
|
52
|
+
"Reject unpriced models": "Từ chối model chưa có giá",
|
|
53
|
+
"Missing usage behavior": "Xử lý khi thiếu token usage",
|
|
54
|
+
"Use reserved estimate": "Dùng số liệu giữ chỗ để ước tính",
|
|
55
|
+
"Allow without token charge": "Cho phép và không tính token",
|
|
56
|
+
"Started at": "Bắt đầu lúc",
|
|
57
|
+
"Requested model": "Model được yêu cầu",
|
|
58
|
+
"Resolved service": "Service đã resolve",
|
|
59
|
+
"Resolved model": "Model đã resolve",
|
|
60
|
+
"Input tokens": "Input token",
|
|
61
|
+
"Output tokens": "Output token",
|
|
62
|
+
"Total tokens": "Tổng token",
|
|
63
|
+
"Cost": "Chi phí",
|
|
64
|
+
"Cost status": "Trạng thái chi phí",
|
|
65
|
+
"Request ID": "Request ID",
|
|
66
|
+
"Failed to load models": "Không thể tải danh sách model",
|
|
67
|
+
"Select a model": "Chọn model",
|
|
68
|
+
"Select an AI Employee": "Chọn AI Employee",
|
|
69
|
+
"Usage guide": "Hướng dẫn sử dụng",
|
|
70
|
+
"OpenAI-compatible endpoint": "Endpoint tương thích OpenAI",
|
|
71
|
+
"Base URL": "Base URL",
|
|
72
|
+
"Use a NocoBase API key as the Bearer token.": "Sử dụng NocoBase API key làm Bearer token.",
|
|
73
|
+
"List available models": "Liệt kê model khả dụng",
|
|
74
|
+
"Send a chat completion": "Gửi yêu cầu chat completion",
|
|
75
|
+
"Usage filters": "Bộ lọc usage",
|
|
76
|
+
"Time range": "Khoảng thời gian",
|
|
77
|
+
"User ID": "User ID",
|
|
78
|
+
"Succeeded": "Thành công",
|
|
79
|
+
"Failed": "Thất bại",
|
|
80
|
+
"Started": "Đã bắt đầu",
|
|
81
|
+
"Apply filters": "Áp dụng bộ lọc",
|
|
82
|
+
"Reset": "Đặt lại",
|
|
83
|
+
"Requests": "Số request",
|
|
84
|
+
"Total cost": "Tổng chi phí",
|
|
85
|
+
"Usage records": "Chi tiết usage"
|
|
86
|
+
}
|
package/src/locale/zh-CN.json
CHANGED
|
@@ -1,10 +1,86 @@
|
|
|
1
|
-
{
|
|
2
|
-
"AI API Gateway": "AI API 网关",
|
|
3
|
-
"Configuration": "配置",
|
|
4
|
-
"Default AI Employee": "默认 AI 员工",
|
|
5
|
-
"Enabled LLM Services": "已启用 LLM 服务",
|
|
6
|
-
"Rate Limit": "速率限制",
|
|
7
|
-
"Save Configuration": "保存配置",
|
|
8
|
-
"Configuration saved": "配置已保存",
|
|
9
|
-
"Failed to save configuration": "保存配置失败"
|
|
10
|
-
|
|
1
|
+
{
|
|
2
|
+
"AI API Gateway": "AI API 网关",
|
|
3
|
+
"Configuration": "配置",
|
|
4
|
+
"Default AI Employee": "默认 AI 员工",
|
|
5
|
+
"Enabled LLM Services": "已启用 LLM 服务",
|
|
6
|
+
"Rate Limit": "速率限制",
|
|
7
|
+
"Save Configuration": "保存配置",
|
|
8
|
+
"Configuration saved": "配置已保存",
|
|
9
|
+
"Failed to save configuration": "保存配置失败",
|
|
10
|
+
"API mode": "API 模式",
|
|
11
|
+
"Direct LLM": "直接 LLM",
|
|
12
|
+
"AI Employee agent": "AI 员工代理",
|
|
13
|
+
"Default LLM service": "默认 LLM 服务",
|
|
14
|
+
"Enable user quotas": "启用用户配额",
|
|
15
|
+
"Default reserved output tokens": "默认预留输出令牌数",
|
|
16
|
+
"Refresh": "刷新",
|
|
17
|
+
"Model pricing": "模型定价",
|
|
18
|
+
"User quotas": "用户配额",
|
|
19
|
+
"Usage": "用量",
|
|
20
|
+
"LLM service": "LLM 服务",
|
|
21
|
+
"Model": "模型",
|
|
22
|
+
"Input price / 1M": "输入价格 / 百万令牌",
|
|
23
|
+
"Output price / 1M": "输出价格 / 百万令牌",
|
|
24
|
+
"Fixed request cost": "每次请求固定费用",
|
|
25
|
+
"Currency": "货币",
|
|
26
|
+
"Status": "状态",
|
|
27
|
+
"Enabled": "已启用",
|
|
28
|
+
"Disabled": "已禁用",
|
|
29
|
+
"Actions": "操作",
|
|
30
|
+
"Edit": "编辑",
|
|
31
|
+
"Delete": "删除",
|
|
32
|
+
"Delete this price?": "删除此价格?",
|
|
33
|
+
"Add price": "添加价格",
|
|
34
|
+
"Edit price": "编辑价格",
|
|
35
|
+
"Effective from": "生效时间",
|
|
36
|
+
"Effective to": "失效时间",
|
|
37
|
+
"Notes": "备注",
|
|
38
|
+
"Saved successfully": "保存成功",
|
|
39
|
+
"Deleted successfully": "删除成功",
|
|
40
|
+
"User": "用户",
|
|
41
|
+
"Period": "周期",
|
|
42
|
+
"Request limit": "请求限制",
|
|
43
|
+
"Token limit": "令牌限制",
|
|
44
|
+
"Cost limit": "费用限制",
|
|
45
|
+
"Timezone": "时区",
|
|
46
|
+
"Unlimited": "无限制",
|
|
47
|
+
"Add quota": "添加配额",
|
|
48
|
+
"Edit quota": "编辑配额",
|
|
49
|
+
"Delete this quota?": "删除此配额?",
|
|
50
|
+
"Daily": "每日",
|
|
51
|
+
"Monthly": "每月",
|
|
52
|
+
"Reject unpriced models": "拒绝未定价模型",
|
|
53
|
+
"Missing usage behavior": "缺少用量时的行为",
|
|
54
|
+
"Use reserved estimate": "使用预留估算",
|
|
55
|
+
"Allow without token charge": "允许且不计令牌",
|
|
56
|
+
"Started at": "开始时间",
|
|
57
|
+
"Requested model": "请求模型",
|
|
58
|
+
"Resolved service": "解析后的服务",
|
|
59
|
+
"Resolved model": "解析后的模型",
|
|
60
|
+
"Input tokens": "输入令牌",
|
|
61
|
+
"Output tokens": "输出令牌",
|
|
62
|
+
"Total tokens": "总令牌",
|
|
63
|
+
"Cost": "费用",
|
|
64
|
+
"Cost status": "费用状态",
|
|
65
|
+
"Request ID": "请求 ID",
|
|
66
|
+
"Failed to load models": "加载模型失败",
|
|
67
|
+
"Select a model": "选择模型",
|
|
68
|
+
"Select an AI Employee": "选择 AI 员工",
|
|
69
|
+
"Usage guide": "使用指南",
|
|
70
|
+
"OpenAI-compatible endpoint": "OpenAI 兼容端点",
|
|
71
|
+
"Base URL": "基础 URL",
|
|
72
|
+
"Use a NocoBase API key as the Bearer token.": "使用 NocoBase API 密钥作为 Bearer Token。",
|
|
73
|
+
"List available models": "列出可用模型",
|
|
74
|
+
"Send a chat completion": "发送聊天补全请求",
|
|
75
|
+
"Usage filters": "用量筛选",
|
|
76
|
+
"Time range": "时间范围",
|
|
77
|
+
"User ID": "用户 ID",
|
|
78
|
+
"Succeeded": "成功",
|
|
79
|
+
"Failed": "失败",
|
|
80
|
+
"Started": "已开始",
|
|
81
|
+
"Apply filters": "应用筛选",
|
|
82
|
+
"Reset": "重置",
|
|
83
|
+
"Requests": "请求数",
|
|
84
|
+
"Total cost": "总费用",
|
|
85
|
+
"Usage records": "用量记录"
|
|
86
|
+
}
|