plugin-ai-api 1.0.20 → 1.0.23

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.
Files changed (91) hide show
  1. package/dist/client/123.e6fe04c856ce6417.js +10 -0
  2. package/dist/client/286.01c0e3c5fff3cccb.js +10 -0
  3. package/dist/client/302.fc3a3491b4ec2dfd.js +10 -0
  4. package/dist/client/562.17a0a299d2e5152c.js +10 -0
  5. package/dist/client/{757.71e30f2a1306562d.js → 757.a01403fb7a1bea01.js} +1 -1
  6. package/dist/client/{902.4238b04ac667c30a.js → 902.92e1daaf1ab16ebf.js} +1 -1
  7. package/dist/client/{97.37cda285d7da3a26.js → 97.72979a11a067a7c9.js} +1 -1
  8. package/dist/client/index.js +1 -1
  9. package/dist/client-v2/123.05f1f649923f93eb.js +10 -0
  10. package/dist/client-v2/302.d27fe4ea9b0b3bf5.js +10 -0
  11. package/dist/client-v2/562.fb2948ee6402de95.js +10 -0
  12. package/dist/client-v2/{757.c377e2f2b054d89d.js → 757.a117ce1cf7119cea.js} +1 -1
  13. package/dist/client-v2/{902.d40d7bda106124c8.js → 902.9054d990ddc223ac.js} +1 -1
  14. package/dist/client-v2/952.94100128b7757f56.js +10 -0
  15. package/dist/client-v2/{97.fc922c37ced86831.js → 97.29c663318eebbd57.js} +1 -1
  16. package/dist/client-v2/index.js +1 -1
  17. package/dist/constants.js +39 -0
  18. package/dist/externalVersion.js +9 -10
  19. package/dist/locale/en-US.json +39 -9
  20. package/dist/locale/vi-VN.json +31 -1
  21. package/dist/locale/zh-CN.json +31 -1
  22. package/dist/server/collections/ai-api-config.js +6 -0
  23. package/dist/server/collections/ai-api-model-metadata.js +83 -0
  24. package/dist/server/collections/ai-api-user-permissions.js +67 -0
  25. package/dist/server/plugin.js +45 -1
  26. package/dist/server/resource/ai-api-config.js +17 -0
  27. package/dist/server/resource/ai-api-user-permissions.js +75 -0
  28. package/dist/server/routes/agent-completions.js +67 -51
  29. package/dist/server/routes/auth.js +11 -1
  30. package/dist/server/routes/chat-completions.js +174 -20
  31. package/dist/server/routes/completions.js +41 -21
  32. package/dist/server/routes/embeddings.js +6 -14
  33. package/dist/server/routes/models.js +102 -20
  34. package/dist/server/routes/router.js +94 -22
  35. package/dist/server/usage.js +2 -0
  36. package/dist/server/utils/app-observability.js +110 -0
  37. package/dist/server/utils/openai-format.js +17 -3
  38. package/dist/server/utils/streaming.js +15 -1
  39. package/dist/server/utils/user-permissions.js +160 -0
  40. package/dist/server/validation.js +18 -0
  41. package/dist/swagger.js +36 -4
  42. package/package.json +2 -2
  43. package/src/client/__tests__/settings-registration.test.tsx +69 -0
  44. package/src/client/components/AiApiRolePermissions.tsx +11 -169
  45. package/src/client/locale.ts +11 -21
  46. package/src/client/plugin.tsx +28 -8
  47. package/src/client-v2/__tests__/settings-registration.test.tsx +87 -0
  48. package/src/client-v2/components/AiApiRolePermissions.tsx +173 -0
  49. package/src/client-v2/locale.ts +21 -1
  50. package/src/client-v2/pages/GeneralPage.tsx +13 -0
  51. package/src/client-v2/pages/ModelMetadataPage.tsx +280 -0
  52. package/src/client-v2/pages/RolePermissionsTab.tsx +14 -0
  53. package/src/client-v2/pages/UserPermissionsPage.tsx +322 -0
  54. package/src/client-v2/plugin.tsx +50 -1
  55. package/src/constants.ts +28 -0
  56. package/src/locale/en-US.json +39 -9
  57. package/src/locale/vi-VN.json +31 -1
  58. package/src/locale/zh-CN.json +31 -1
  59. package/src/server/__tests__/app-observability.test.ts +98 -0
  60. package/src/server/__tests__/models.test.ts +116 -0
  61. package/src/server/__tests__/openai-format.test.ts +52 -1
  62. package/src/server/__tests__/permission-sync.test.ts +109 -0
  63. package/src/server/__tests__/request-body.test.ts +310 -0
  64. package/src/server/__tests__/streaming-observability.test.ts +51 -0
  65. package/src/server/__tests__/usage-route.test.ts +213 -0
  66. package/src/server/__tests__/user-permissions-resource.test.ts +66 -0
  67. package/src/server/__tests__/user-permissions.test.ts +284 -0
  68. package/src/server/collections/ai-api-config.ts +6 -0
  69. package/src/server/collections/ai-api-model-metadata.ts +72 -0
  70. package/src/server/collections/ai-api-user-permissions.ts +46 -0
  71. package/src/server/plugin.ts +65 -4
  72. package/src/server/resource/ai-api-config.ts +23 -0
  73. package/src/server/resource/ai-api-user-permissions.ts +76 -0
  74. package/src/server/routes/agent-completions.ts +84 -62
  75. package/src/server/routes/auth.ts +14 -1
  76. package/src/server/routes/chat-completions.ts +294 -20
  77. package/src/server/routes/completions.ts +54 -20
  78. package/src/server/routes/embeddings.ts +10 -15
  79. package/src/server/routes/models.ts +318 -195
  80. package/src/server/routes/router.ts +136 -26
  81. package/src/server/usage.ts +2 -0
  82. package/src/server/utils/app-observability.ts +105 -0
  83. package/src/server/utils/openai-format.ts +26 -0
  84. package/src/server/utils/streaming.ts +13 -1
  85. package/src/server/utils/user-permissions.ts +218 -0
  86. package/src/server/validation.ts +27 -0
  87. package/src/swagger.ts +47 -4
  88. package/dist/client/302.25edd5d75460acbf.js +0 -10
  89. package/dist/client/778.5c452944cb747975.js +0 -10
  90. package/dist/client-v2/302.9b27a263901d54d8.js +0 -10
  91. package/src/client/AiApiConfigPage.tsx +0 -309
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { AiApiRolePermissions } from '../components/AiApiRolePermissions';
3
+
4
+ /**
5
+ * v2 ACL permission tab wrapper.
6
+ *
7
+ * The v2 `settingsUI.addPermissionsTab` loads a component via `componentLoader` and
8
+ * passes it `PermissionTabProps` ({ activeRole, ... }), whereas AiApiRolePermissions
9
+ * takes { role }. This wrapper bridges the prop shape so the shared component can be
10
+ * reused unchanged across both client runtimes.
11
+ */
12
+ export default function RolePermissionsTab({ activeRole }: { activeRole?: { name?: string } | null }) {
13
+ return <AiApiRolePermissions role={activeRole} />;
14
+ }
@@ -0,0 +1,322 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { Alert, Button, Card, Form, Modal, Popconfirm, Select, Space, Switch, Table, Tag, message } from 'antd';
3
+ import type { ColumnsType } from 'antd/es/table';
4
+ import { useFlowContext } from '@nocobase/flow-engine';
5
+ import { useT } from '../locale';
6
+ import { errorMessage, unwrapData } from './api';
7
+ import type { ApiEnvelope } from './api';
8
+
9
+ interface UserSummary {
10
+ id: string | number;
11
+ nickname?: string;
12
+ username?: string;
13
+ email?: string;
14
+ }
15
+
16
+ interface EnabledLlmService {
17
+ llmService: string;
18
+ llmServiceTitle?: string;
19
+ enabledModels?: { label: string; value: string }[];
20
+ }
21
+
22
+ interface UserPermission {
23
+ id: string | number;
24
+ userId: string | number;
25
+ user?: UserSummary;
26
+ enabled: boolean;
27
+ allowedLlmServices: string[];
28
+ allowAllModels: boolean;
29
+ allowedModels: string[];
30
+ }
31
+
32
+ /** The modal edits everything except the server-assigned id. */
33
+ type UserPermissionFormValues = Omit<UserPermission, 'id' | 'user'>;
34
+
35
+ const PAGE_SIZE = 20;
36
+
37
+ export default function UserPermissionsPage() {
38
+ const ctx = useFlowContext();
39
+ const t = useT();
40
+ const [form] = Form.useForm<UserPermissionFormValues>();
41
+ const selectedServices = Form.useWatch('allowedLlmServices', form);
42
+ const allowAllModels = Form.useWatch('allowAllModels', form);
43
+ const [rows, setRows] = useState<UserPermission[]>([]);
44
+ const [total, setTotal] = useState(0);
45
+ const [page, setPage] = useState(1);
46
+ const [users, setUsers] = useState<UserSummary[]>([]);
47
+ const [userSearch, setUserSearch] = useState('');
48
+ const [services, setServices] = useState<EnabledLlmService[]>([]);
49
+ const [loading, setLoading] = useState(false);
50
+ const [saving, setSaving] = useState(false);
51
+ const [editing, setEditing] = useState<UserPermission>();
52
+ const [open, setOpen] = useState(false);
53
+
54
+ const load = useCallback(async () => {
55
+ setLoading(true);
56
+ try {
57
+ const [permissionsResponse, servicesResponse] = await Promise.all([
58
+ ctx.api.request({
59
+ url: 'aiApiUserPermissions:list',
60
+ method: 'get',
61
+ params: { page, pageSize: PAGE_SIZE, appends: ['user'], sort: '-updatedAt' },
62
+ }),
63
+ ctx.api.request({ url: 'ai:listAllEnabledModels', method: 'get' }),
64
+ ]);
65
+ setRows(unwrapData<UserPermission[]>(permissionsResponse, []));
66
+ setTotal((permissionsResponse as ApiEnvelope<UserPermission[]>)?.data?.meta?.count ?? 0);
67
+ setServices(unwrapData<EnabledLlmService[]>(servicesResponse, []));
68
+ } catch (error) {
69
+ message.error(errorMessage(error));
70
+ } finally {
71
+ setLoading(false);
72
+ }
73
+ }, [ctx.api, page]);
74
+
75
+ useEffect(() => {
76
+ load();
77
+ }, [load]);
78
+
79
+ // Served by the plugin's own action rather than `users:list`, which belongs to the
80
+ // pm.plugin-users snippet and would make this page unusable for a role granted only
81
+ // pm.plugin-ai-api.user-permissions.
82
+ const loadUsers = useCallback(
83
+ async (keyword: string) => {
84
+ try {
85
+ const response = await ctx.api.request({
86
+ url: 'aiApiUserPermissions:listUsers',
87
+ method: 'get',
88
+ params: { keyword, pageSize: 50, excludeGranted: !editing },
89
+ });
90
+ setUsers(unwrapData<UserSummary[]>(response, []));
91
+ } catch (error) {
92
+ message.error(errorMessage(error));
93
+ }
94
+ },
95
+ [ctx.api, editing],
96
+ );
97
+
98
+ useEffect(() => {
99
+ if (!open) return;
100
+ const timer = setTimeout(() => loadUsers(userSearch), 300);
101
+ return () => clearTimeout(timer);
102
+ }, [open, userSearch, loadUsers]);
103
+
104
+ const serviceOptions = useMemo(
105
+ () =>
106
+ services.map((service) => ({ label: service.llmServiceTitle || service.llmService, value: service.llmService })),
107
+ [services],
108
+ );
109
+
110
+ // Model IDs are "serviceName/modelId", matching what the gateway enforces and what
111
+ // GET /v1/models returns, so admins never have to type them by hand.
112
+ const modelOptions = useMemo(() => {
113
+ const picked = new Set(selectedServices || []);
114
+ return services
115
+ .filter((service) => picked.has(service.llmService))
116
+ .flatMap((service) =>
117
+ (service.enabledModels || []).map((model) => ({
118
+ label: `${service.llmServiceTitle || service.llmService} / ${model.label || model.value}`,
119
+ value: `${service.llmService}/${model.value}`,
120
+ })),
121
+ );
122
+ }, [services, selectedServices]);
123
+
124
+ const showCreate = () => {
125
+ setEditing(undefined);
126
+ setUserSearch('');
127
+ form.setFieldsValue({
128
+ enabled: true,
129
+ allowedLlmServices: [],
130
+ allowAllModels: true,
131
+ allowedModels: [],
132
+ });
133
+ setOpen(true);
134
+ };
135
+
136
+ const showEdit = (record: UserPermission) => {
137
+ setEditing(record);
138
+ setUserSearch('');
139
+ // Seed the picker with the granted user so the label renders before any search runs.
140
+ setUsers(record.user ? [record.user] : []);
141
+ form.setFieldsValue({
142
+ userId: record.userId,
143
+ enabled: record.enabled,
144
+ allowAllModels: record.allowAllModels,
145
+ allowedLlmServices: record.allowedLlmServices || [],
146
+ allowedModels: record.allowedModels || [],
147
+ });
148
+ setOpen(true);
149
+ };
150
+
151
+ const save = async () => {
152
+ const values = await form.validateFields();
153
+ setSaving(true);
154
+ try {
155
+ await ctx.api.request({
156
+ url: editing ? `aiApiUserPermissions:update/${editing.id}` : 'aiApiUserPermissions:create',
157
+ method: 'post',
158
+ data: values,
159
+ });
160
+ message.success(t('Saved successfully'));
161
+ setOpen(false);
162
+ await load();
163
+ } catch (error) {
164
+ message.error(errorMessage(error));
165
+ } finally {
166
+ setSaving(false);
167
+ }
168
+ };
169
+
170
+ const remove = async (record: UserPermission) => {
171
+ try {
172
+ await ctx.api.request({ url: `aiApiUserPermissions:destroy/${record.id}`, method: 'post' });
173
+ message.success(t('Deleted successfully'));
174
+ await load();
175
+ } catch (error) {
176
+ message.error(errorMessage(error));
177
+ }
178
+ };
179
+
180
+ const userLabel = (user?: UserSummary) => user?.nickname || user?.username || user?.email || String(user?.id ?? '');
181
+ const serviceLabel = (name: string) => serviceOptions.find((option) => option.value === name)?.label || name;
182
+
183
+ const columns: ColumnsType<UserPermission> = [
184
+ {
185
+ title: t('User'),
186
+ key: 'user',
187
+ width: 180,
188
+ render: (_, record) => userLabel(record.user) || String(record.userId),
189
+ },
190
+ {
191
+ title: t('Allowed LLM services'),
192
+ dataIndex: 'allowedLlmServices',
193
+ key: 'allowedLlmServices',
194
+ render: (values: string[]) =>
195
+ values?.length ? (
196
+ <Space size={[0, 4]} wrap>
197
+ {values.map((value) => (
198
+ <Tag key={value}>{serviceLabel(value)}</Tag>
199
+ ))}
200
+ </Space>
201
+ ) : (
202
+ <Tag color="red">{t('No service allowed')}</Tag>
203
+ ),
204
+ },
205
+ {
206
+ title: t('Allowed models'),
207
+ key: 'allowedModels',
208
+ width: 220,
209
+ render: (_, record) =>
210
+ record.allowAllModels ? (
211
+ <Tag color="blue">{t('All models of allowed services')}</Tag>
212
+ ) : (
213
+ <Space size={[0, 4]} wrap>
214
+ {(record.allowedModels || []).map((value) => (
215
+ <Tag key={value}>{value}</Tag>
216
+ ))}
217
+ </Space>
218
+ ),
219
+ },
220
+ {
221
+ title: t('Status'),
222
+ dataIndex: 'enabled',
223
+ key: 'enabled',
224
+ width: 100,
225
+ render: (enabled: boolean) => (
226
+ <Tag color={enabled ? 'green' : 'default'}>{enabled ? t('Enabled') : t('Disabled')}</Tag>
227
+ ),
228
+ },
229
+ {
230
+ title: t('Actions'),
231
+ key: 'actions',
232
+ width: 150,
233
+ fixed: 'right',
234
+ render: (_, record) => (
235
+ <Space size={0}>
236
+ <Button type="link" onClick={() => showEdit(record)}>
237
+ {t('Edit')}
238
+ </Button>
239
+ <Popconfirm title={t('Delete this permission?')} onConfirm={() => remove(record)}>
240
+ <Button type="link" danger>
241
+ {t('Delete')}
242
+ </Button>
243
+ </Popconfirm>
244
+ </Space>
245
+ ),
246
+ },
247
+ ];
248
+
249
+ return (
250
+ <Card
251
+ title={t('User LLM permissions')}
252
+ extra={
253
+ <Button type="primary" onClick={showCreate}>
254
+ {t('Add permission')}
255
+ </Button>
256
+ }
257
+ >
258
+ <Alert
259
+ type="info"
260
+ showIcon
261
+ style={{ marginBottom: 16 }}
262
+ message={t(
263
+ 'Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.',
264
+ )}
265
+ />
266
+ <Table
267
+ rowKey="id"
268
+ columns={columns}
269
+ dataSource={rows}
270
+ loading={loading}
271
+ scroll={{ x: 1000 }}
272
+ pagination={{
273
+ current: page,
274
+ pageSize: PAGE_SIZE,
275
+ total,
276
+ showSizeChanger: false,
277
+ onChange: setPage,
278
+ }}
279
+ />
280
+ <Modal
281
+ title={editing ? t('Edit permission') : t('Add permission')}
282
+ open={open}
283
+ onCancel={() => setOpen(false)}
284
+ onOk={save}
285
+ confirmLoading={saving}
286
+ destroyOnClose
287
+ >
288
+ <Form form={form} layout="vertical" preserve={false}>
289
+ <Form.Item name="userId" label={t('User')} rules={[{ required: true }]}>
290
+ <Select
291
+ disabled={Boolean(editing)}
292
+ showSearch
293
+ // Matching happens on the server, so keep every returned option visible.
294
+ filterOption={false}
295
+ onSearch={setUserSearch}
296
+ notFoundContent={null}
297
+ options={users.map((user) => ({ label: userLabel(user), value: user.id }))}
298
+ />
299
+ </Form.Item>
300
+ <Form.Item
301
+ name="allowedLlmServices"
302
+ label={t('Allowed LLM services')}
303
+ extra={t('Only services also enabled in the general configuration take effect.')}
304
+ >
305
+ <Select mode="multiple" allowClear showSearch optionFilterProp="label" options={serviceOptions} />
306
+ </Form.Item>
307
+ <Form.Item name="allowAllModels" label={t('Allow all models')} valuePropName="checked">
308
+ <Switch />
309
+ </Form.Item>
310
+ {allowAllModels === false && (
311
+ <Form.Item name="allowedModels" label={t('Allowed models')}>
312
+ <Select mode="multiple" allowClear showSearch optionFilterProp="label" options={modelOptions} />
313
+ </Form.Item>
314
+ )}
315
+ <Form.Item name="enabled" label={t('Enabled')} valuePropName="checked">
316
+ <Switch />
317
+ </Form.Item>
318
+ </Form>
319
+ </Modal>
320
+ </Card>
321
+ );
322
+ }
@@ -1,4 +1,19 @@
1
+ import React from 'react';
1
2
  import { Plugin, Application } from '@nocobase/client-v2';
3
+ import { AI_API_ACL_SNIPPET, AI_API_USER_PERMISSIONS_SNIPPET } from '../constants';
4
+
5
+ interface PermissionTabOptions {
6
+ key: string;
7
+ label: string;
8
+ sort?: number;
9
+ componentLoader: () => Promise<{ default: React.ComponentType<{ activeRole?: { name?: string } | null }> }>;
10
+ }
11
+
12
+ interface AclPluginV2Compat {
13
+ settingsUI?: {
14
+ addPermissionsTab?: (options: PermissionTabOptions) => void;
15
+ };
16
+ }
2
17
 
3
18
  export class PluginAiApiClient extends Plugin<Record<string, never>, Application> {
4
19
  async load() {
@@ -6,13 +21,15 @@ export class PluginAiApiClient extends Plugin<Record<string, never>, Application
6
21
  key: 'ai-api',
7
22
  title: this.t('AI API Gateway'),
8
23
  icon: 'ApiOutlined',
9
- aclSnippet: 'pm.ai-api.configuration',
24
+ aclSnippet: AI_API_ACL_SNIPPET,
10
25
  });
11
26
 
12
27
  this.pluginSettingsManager.addPageTabItem({
13
28
  menuKey: 'ai-api',
14
29
  key: 'index',
15
30
  title: this.t('Configuration'),
31
+ aclSnippet: AI_API_ACL_SNIPPET,
32
+ sort: 1,
16
33
  componentLoader: () => import('./pages/GeneralPage'),
17
34
  });
18
35
 
@@ -20,13 +37,35 @@ export class PluginAiApiClient extends Plugin<Record<string, never>, Application
20
37
  menuKey: 'ai-api',
21
38
  key: 'model-pricing',
22
39
  title: this.t('Model pricing'),
40
+ aclSnippet: AI_API_ACL_SNIPPET,
41
+ sort: 2,
23
42
  componentLoader: () => import('./pages/ModelPricingPage'),
24
43
  });
25
44
 
45
+ this.pluginSettingsManager.addPageTabItem({
46
+ menuKey: 'ai-api',
47
+ key: 'model-metadata',
48
+ title: this.t('Model metadata'),
49
+ aclSnippet: AI_API_ACL_SNIPPET,
50
+ sort: 3,
51
+ componentLoader: () => import('./pages/ModelMetadataPage'),
52
+ });
53
+
54
+ this.pluginSettingsManager.addPageTabItem({
55
+ menuKey: 'ai-api',
56
+ key: 'user-permissions',
57
+ title: this.t('User LLM permissions'),
58
+ aclSnippet: AI_API_USER_PERMISSIONS_SNIPPET,
59
+ sort: 4,
60
+ componentLoader: () => import('./pages/UserPermissionsPage'),
61
+ });
62
+
26
63
  this.pluginSettingsManager.addPageTabItem({
27
64
  menuKey: 'ai-api',
28
65
  key: 'user-quotas',
29
66
  title: this.t('User quotas'),
67
+ aclSnippet: AI_API_ACL_SNIPPET,
68
+ sort: 5,
30
69
  componentLoader: () => import('./pages/UserQuotasPage'),
31
70
  });
32
71
 
@@ -34,8 +73,18 @@ export class PluginAiApiClient extends Plugin<Record<string, never>, Application
34
73
  menuKey: 'ai-api',
35
74
  key: 'usage',
36
75
  title: this.t('Usage'),
76
+ aclSnippet: AI_API_ACL_SNIPPET,
77
+ sort: 6,
37
78
  componentLoader: () => import('./pages/UsagePage'),
38
79
  });
80
+
81
+ const aclPlugin = this.app.pm.get('@nocobase/plugin-acl') as AclPluginV2Compat | undefined;
82
+ aclPlugin?.settingsUI?.addPermissionsTab?.({
83
+ key: 'aiApi',
84
+ label: this.t('AI API'),
85
+ sort: 25,
86
+ componentLoader: () => import('./pages/RolePermissionsTab'),
87
+ });
39
88
  }
40
89
  }
41
90
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ /**
11
+ * The single admin snippet the server registers as `pm.${this.name}.configuration`.
12
+ *
13
+ * `this.name` is the plugin's package name with only `@nocobase/plugin-` or
14
+ * `@nocobase/preset-` stripped (see utils/plugin-package `getPluginNameFromPackageName`),
15
+ * so for this unscoped package it stays `plugin-ai-api` — verified against the running
16
+ * app, which reports `name: "plugin-ai-api"`. Spelling it `pm.ai-api.configuration` on the
17
+ * client names a snippet no role can ever hold.
18
+ *
19
+ * Shared by both client runtimes so a rename cannot silently desynchronise them.
20
+ */
21
+ export const AI_API_ACL_SNIPPET = 'pm.plugin-ai-api.configuration';
22
+
23
+ /**
24
+ * Child snippet for the per-user LLM permission surface, deliberately separate from
25
+ * AI_API_ACL_SNIPPET so granting someone the gateway settings does not also let them
26
+ * hand out model access. Same `plugin-ai-api` prefix constraint as above applies.
27
+ */
28
+ export const AI_API_USER_PERMISSIONS_SNIPPET = 'pm.plugin-ai-api.user-permissions';
@@ -1,11 +1,11 @@
1
- {
2
- "AI API Gateway": "AI API Gateway",
3
- "Configuration": "Configuration",
4
- "Default AI Employee": "Default AI Employee",
5
- "Enabled LLM Services": "Enabled LLM Services",
6
- "Rate Limit": "Rate Limit",
7
- "Save Configuration": "Save Configuration",
8
- "Configuration saved": "Configuration saved",
1
+ {
2
+ "AI API Gateway": "AI API Gateway",
3
+ "Configuration": "Configuration",
4
+ "Default AI Employee": "Default AI Employee",
5
+ "Enabled LLM Services": "Enabled LLM Services",
6
+ "Rate Limit": "Rate Limit",
7
+ "Save Configuration": "Save Configuration",
8
+ "Configuration saved": "Configuration saved",
9
9
  "Failed to save configuration": "Failed to save configuration",
10
10
  "API mode": "API mode",
11
11
  "Direct LLM": "Direct LLM",
@@ -82,5 +82,35 @@
82
82
  "Reset": "Reset",
83
83
  "Requests": "Requests",
84
84
  "Total cost": "Total cost",
85
- "Usage records": "Usage records"
85
+ "Usage records": "Usage records",
86
+ "Model metadata": "Model metadata",
87
+ "Add override": "Add override",
88
+ "Edit override": "Edit override",
89
+ "Delete this override?": "Delete this override?",
90
+ "Context window": "Context window",
91
+ "Max completion tokens": "Max completion tokens",
92
+ "Owned by": "Owned by",
93
+ "Display name": "Display name",
94
+ "Description": "Description",
95
+ "Leave empty to not override": "Leave empty to not override",
96
+ "Total input + output token capacity reported to clients.": "Total input + output token capacity reported to clients.",
97
+ "Maximum output tokens reported to clients.": "Maximum output tokens reported to clients.",
98
+ "AI API": "AI API",
99
+ "Allow this role to use the AI API": "Allow this role to use the AI API",
100
+ "Allow all AI Employees": "Allow all AI Employees",
101
+ "Select which AI Employees this role may use:": "Select which AI Employees this role may use:",
102
+ "Select allowed AI Employees": "Select allowed AI Employees",
103
+ "Max request body size (MB)": "Max request body size (MB)",
104
+ "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.": "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.",
105
+ "User LLM permissions": "User LLM permissions",
106
+ "Add permission": "Add permission",
107
+ "Edit permission": "Edit permission",
108
+ "Delete this permission?": "Delete this permission?",
109
+ "Allowed LLM services": "Allowed LLM services",
110
+ "Allow all models": "Allow all models",
111
+ "Allowed models": "Allowed models",
112
+ "No service allowed": "No service allowed",
113
+ "All models of allowed services": "All models of allowed services",
114
+ "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.": "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.",
115
+ "Only services also enabled in the general configuration take effect.": "Only services also enabled in the general configuration take effect."
86
116
  }
@@ -82,5 +82,35 @@
82
82
  "Reset": "Đặt lại",
83
83
  "Requests": "Số request",
84
84
  "Total cost": "Tổng chi phí",
85
- "Usage records": "Chi tiết usage"
85
+ "Usage records": "Chi tiết usage",
86
+ "Model metadata": "Metadata model",
87
+ "Add override": "Thêm override",
88
+ "Edit override": "Sửa override",
89
+ "Delete this override?": "Xóa override này?",
90
+ "Context window": "Context window",
91
+ "Max completion tokens": "Max completion tokens",
92
+ "Owned by": "Nhà cung cấp",
93
+ "Display name": "Tên hiển thị",
94
+ "Description": "Mô tả",
95
+ "Leave empty to not override": "Để trống nếu không override",
96
+ "Total input + output token capacity reported to clients.": "Tổng dung lượng token (input + output) trả về cho client.",
97
+ "Maximum output tokens reported to clients.": "Số token output tối đa trả về cho client.",
98
+ "AI API": "AI API",
99
+ "Allow this role to use the AI API": "Cho phép vai trò này sử dụng AI API",
100
+ "Allow all AI Employees": "Cho phép tất cả AI Employee",
101
+ "Select which AI Employees this role may use:": "Chọn AI Employee mà vai trò này được dùng:",
102
+ "Select allowed AI Employees": "Chọn AI Employee được phép",
103
+ "Max request body size (MB)": "Giới hạn kích thước request body (MB)",
104
+ "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.": "Tăng giá trị này để nhận ảnh base64 gửi trực tiếp. Base64 làm tăng khoảng 33% so với kích thước tệp gốc.",
105
+ "User LLM permissions": "Phân quyền LLM theo người dùng",
106
+ "Add permission": "Thêm phân quyền",
107
+ "Edit permission": "Sửa phân quyền",
108
+ "Delete this permission?": "Xoá phân quyền này?",
109
+ "Allowed LLM services": "Dịch vụ LLM được phép",
110
+ "Allow all models": "Cho phép tất cả model",
111
+ "Allowed models": "Model được phép",
112
+ "No service allowed": "Không được phép dịch vụ nào",
113
+ "All models of allowed services": "Tất cả model của các dịch vụ được phép",
114
+ "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.": "Người dùng có trong danh sách này chỉ được dùng các dịch vụ được chọn bên dưới. Người dùng không có bản ghi sẽ dùng theo cấu hình chung.",
115
+ "Only services also enabled in the general configuration take effect.": "Chỉ những dịch vụ đồng thời được bật trong cấu hình chung mới có hiệu lực."
86
116
  }
@@ -82,5 +82,35 @@
82
82
  "Reset": "重置",
83
83
  "Requests": "请求数",
84
84
  "Total cost": "总费用",
85
- "Usage records": "用量记录"
85
+ "Usage records": "用量记录",
86
+ "Model metadata": "模型元数据",
87
+ "Add override": "添加覆盖",
88
+ "Edit override": "编辑覆盖",
89
+ "Delete this override?": "确认删除此覆盖?",
90
+ "Context window": "上下文窗口",
91
+ "Max completion tokens": "最大输出 token 数",
92
+ "Owned by": "所属方",
93
+ "Display name": "显示名称",
94
+ "Description": "描述",
95
+ "Leave empty to not override": "留空则不覆盖",
96
+ "Total input + output token capacity reported to clients.": "返回给客户端的输入+输出 token 总容量。",
97
+ "Maximum output tokens reported to clients.": "返回给客户端的最大输出 token 数。",
98
+ "AI API": "AI API",
99
+ "Allow this role to use the AI API": "允许此角色使用 AI API",
100
+ "Allow all AI Employees": "允许所有 AI 员工",
101
+ "Select which AI Employees this role may use:": "选择此角色可使用的 AI 员工:",
102
+ "Select allowed AI Employees": "选择允许的 AI 员工",
103
+ "Max request body size (MB)": "请求体大小上限(MB)",
104
+ "Raise this to accept inline base64 images. Base64 adds about 33% to the original file size.": "调高此值以接收内联 base64 图片。base64 编码会使体积增加约 33%。",
105
+ "User LLM permissions": "用户 LLM 权限",
106
+ "Add permission": "添加权限",
107
+ "Edit permission": "编辑权限",
108
+ "Delete this permission?": "确定删除此权限?",
109
+ "Allowed LLM services": "允许的 LLM 服务",
110
+ "Allow all models": "允许所有模型",
111
+ "Allowed models": "允许的模型",
112
+ "No service allowed": "未允许任何服务",
113
+ "All models of allowed services": "允许服务下的所有模型",
114
+ "Users listed here are limited to the services selected below. Users without a record fall back to the general configuration.": "此处列出的用户仅能使用下方所选的服务;没有记录的用户按通用配置处理。",
115
+ "Only services also enabled in the general configuration take effect.": "仅当服务同时在通用配置中启用时才会生效。"
86
116
  }