@allbluecn/admin-app 0.2.0 → 0.2.3

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 (87) hide show
  1. package/package.json +10 -10
  2. package/src/components/admin/admin-user-menu.tsx +12 -6
  3. package/src/components/admin/developer-log.tsx +42 -44
  4. package/src/components/ai-provider/model-list-editor.tsx +5 -4
  5. package/src/components/ai-provider/provider-form.tsx +63 -64
  6. package/src/components/ai-provider/provider-table.tsx +35 -36
  7. package/src/components/auth/admin-login-form.tsx +23 -24
  8. package/src/components/auth/oauth-section.tsx +3 -5
  9. package/src/components/auth/password-input.tsx +2 -3
  10. package/src/components/common/placeholder-page.tsx +2 -4
  11. package/src/components/component/component-form-dialog.tsx +48 -49
  12. package/src/components/component/component-import-dialog.tsx +5 -5
  13. package/src/components/component/component-table.tsx +13 -14
  14. package/src/components/data-table/data-table-pagination.tsx +4 -6
  15. package/src/components/data-table/table-detail-layout.tsx +2 -3
  16. package/src/components/dependency/changelog-dialog.tsx +3 -4
  17. package/src/components/dependency/columns.tsx +32 -24
  18. package/src/components/dependency/dependency-table.tsx +37 -29
  19. package/src/components/dependency/edit-dialog.tsx +24 -25
  20. package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
  21. package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
  22. package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
  23. package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
  24. package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
  25. package/src/components/market/columns.tsx +20 -21
  26. package/src/components/market/market-app-form.tsx +45 -38
  27. package/src/components/market/market-app-list.tsx +16 -17
  28. package/src/components/monitoring/error-list.tsx +18 -19
  29. package/src/components/monitoring/health-cards.tsx +11 -13
  30. package/src/components/monitoring/metrics-stats.tsx +11 -12
  31. package/src/components/monitoring/request-log-table.tsx +8 -9
  32. package/src/components/ui/pagination.tsx +0 -2
  33. package/src/components/ui/sidebar.tsx +1 -1
  34. package/src/components/user/columns.tsx +9 -9
  35. package/src/components/user/settings-activity-log.tsx +39 -29
  36. package/src/components/user/settings-profile.tsx +36 -36
  37. package/src/components/user/user-create-dialog.tsx +4 -4
  38. package/src/components/user/user-filters.tsx +16 -18
  39. package/src/lib/i18n/config.ts +0 -32
  40. package/src/providers/index.tsx +6 -16
  41. package/src/routeTree.gen.ts +1199 -0
  42. package/src/routes/__root.tsx +9 -23
  43. package/src/routes/_nav/apps/route.lazy.tsx +2 -3
  44. package/src/routes/_nav/audit/route.lazy.tsx +10 -11
  45. package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
  46. package/src/routes/_nav/billing/route.lazy.tsx +3 -4
  47. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
  48. package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
  49. package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
  50. package/src/routes/_nav/components/route.lazy.tsx +6 -6
  51. package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
  52. package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
  53. package/src/routes/_nav/docs/route.lazy.tsx +2 -3
  54. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
  55. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
  56. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
  57. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
  58. package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
  59. package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
  60. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
  61. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
  62. package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
  63. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
  64. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
  65. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
  66. package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
  67. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
  68. package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
  69. package/src/routes/_nav/settings/route.lazy.tsx +2 -3
  70. package/src/routes/_nav/sso/route.lazy.tsx +20 -18
  71. package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
  72. package/src/routes/_nav/users/route.lazy.tsx +4 -5
  73. package/src/routes/setup/route.lazy.tsx +15 -16
  74. package/src/start.ts +2 -2
  75. package/src/lib/i18n/client.ts +0 -32
  76. package/src/lib/i18n/locales/en/admin-ai.json +0 -128
  77. package/src/lib/i18n/locales/en/admin-billing.json +0 -56
  78. package/src/lib/i18n/locales/en/admin-users.json +0 -88
  79. package/src/lib/i18n/locales/en/common.json +0 -877
  80. package/src/lib/i18n/locales/en/enterprise.json +0 -208
  81. package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
  82. package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
  83. package/src/lib/i18n/locales/zh/admin-users.json +0 -88
  84. package/src/lib/i18n/locales/zh/common.json +0 -877
  85. package/src/lib/i18n/locales/zh/enterprise.json +0 -208
  86. package/src/lib/i18n/server.ts +0 -18
  87. package/src/lib/i18n/types.ts +0 -5
@@ -17,7 +17,7 @@
17
17
 
18
18
  // apps/admin/src/components/ai-provider/provider-form.tsx
19
19
  import { useState } from "react"
20
- import { useTranslation } from 'react-i18next'
20
+ import { m } from '@/paraglide/messages.js'
21
21
  import { useMutation } from "@tanstack/react-query"
22
22
  import { Link, useNavigate, useRouter } from "@tanstack/react-router"
23
23
  import { ArrowLeft, Loader2, Save } from "lucide-react"
@@ -104,7 +104,6 @@ export function ProviderForm({
104
104
  initial?: Partial<ProviderFormValues>
105
105
  providerDbId?: string
106
106
  }) {
107
- const { t } = useTranslation('admin-ai')
108
107
  const navigate = useNavigate()
109
108
  const router = useRouter()
110
109
  const [values, setValues] = useState<ProviderFormValues>({
@@ -126,17 +125,17 @@ export function ProviderForm({
126
125
  })
127
126
  },
128
127
  onSuccess: async () => {
129
- toast.success(mode === "create" ? t('form.toast.created') : t('form.toast.updated'))
128
+ toast.success(mode === "create" ? m['admin-ai_form_toast_created']() : m['admin-ai_form_toast_updated']())
130
129
  await router.invalidate()
131
130
  navigate({ to: "/ai-providers" })
132
131
  },
133
132
  onError: (err) =>
134
- toast.error(t('form.toast.saveFailed', { error: err instanceof Error ? err.message : t('status.error') })),
133
+ toast.error(m['admin-ai_form_toast_saveFailed']({ error: err instanceof Error ? err.message : m.common_status_error() })),
135
134
  })
136
135
 
137
136
  function handleSubmit() {
138
- if (!values.providerId.trim()) return toast.error(t('form.validation.providerIdRequired'))
139
- if (!values.displayName.trim()) return toast.error(t('form.validation.displayNameRequired'))
137
+ if (!values.providerId.trim()) return toast.error(m['admin-ai_form_validation_providerIdRequired']())
138
+ if (!values.displayName.trim()) return toast.error(m['admin-ai_form_validation_displayNameRequired']())
140
139
  saveMutation.mutate()
141
140
  }
142
141
 
@@ -149,25 +148,25 @@ export function ProviderForm({
149
148
  </Link>
150
149
  </Button>
151
150
  <h2 className="text-lg font-semibold">
152
- {mode === "create" ? t('form.addProvider') : t('form.editProvider')}
151
+ {mode === "create" ? m['admin-ai_form_addProvider']() : m['admin-ai_form_editProvider']()}
153
152
  </h2>
154
153
  </div>
155
154
 
156
155
  <FieldSet>
157
- <FieldLegend>{t('form.basicInfo')}</FieldLegend>
156
+ <FieldLegend>{m['admin-ai_form_basicInfo']()}</FieldLegend>
158
157
  <FieldGroup>
159
158
  <Field orientation="horizontal">
160
159
  <FieldContent>
161
- <FieldLabel>{t('form.providerId')}</FieldLabel>
160
+ <FieldLabel>{m['admin-ai_form_providerId']()}</FieldLabel>
162
161
  <FieldDescription>
163
- {t('form.providerIdDesc')}
164
- {mode === "edit" && " " + t('form.providerIdEditNote')}
162
+ {m['admin-ai_form_providerIdDesc']()}
163
+ {mode === "edit" && " " + m['admin-ai_form_providerIdEditNote']()}
165
164
  </FieldDescription>
166
165
  </FieldContent>
167
166
  <Input
168
167
  value={values.providerId}
169
- onChange={(e) => patch({ providerId: e.target.value })}
170
- placeholder={t('form.providerIdPlaceholder')}
168
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ providerId: e.target.value })}
169
+ placeholder={m['admin-ai_form_providerIdPlaceholder']()}
171
170
  disabled={mode === "edit"}
172
171
  className="w-60 font-mono"
173
172
  />
@@ -175,8 +174,8 @@ export function ProviderForm({
175
174
 
176
175
  <Field orientation="horizontal">
177
176
  <FieldContent>
178
- <FieldLabel>{t('form.preview')}</FieldLabel>
179
- <FieldDescription>{t('form.previewDesc')}</FieldDescription>
177
+ <FieldLabel>{m['admin-ai_form_preview']()}</FieldLabel>
178
+ <FieldDescription>{m['admin-ai_form_previewDesc']()}</FieldDescription>
180
179
  </FieldContent>
181
180
  <ProviderIcon
182
181
  provider={{
@@ -189,36 +188,36 @@ export function ProviderForm({
189
188
 
190
189
  <Field orientation="horizontal">
191
190
  <FieldContent>
192
- <FieldLabel>{t('form.displayName')}</FieldLabel>
193
- <FieldDescription>{t('form.displayNameDesc')}</FieldDescription>
191
+ <FieldLabel>{m['admin-ai_form_displayName']()}</FieldLabel>
192
+ <FieldDescription>{m['admin-ai_form_displayNameDesc']()}</FieldDescription>
194
193
  </FieldContent>
195
194
  <Input
196
195
  value={values.displayName}
197
- onChange={(e) => patch({ displayName: e.target.value })}
196
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ displayName: e.target.value })}
198
197
  className="w-60"
199
198
  />
200
199
  </Field>
201
200
 
202
201
  <Field orientation="horizontal">
203
202
  <FieldContent>
204
- <FieldLabel>{t('form.brandName')}</FieldLabel>
205
- <FieldDescription>{t('form.brandNameDesc')}</FieldDescription>
203
+ <FieldLabel>{m['admin-ai_form_brandName']()}</FieldLabel>
204
+ <FieldDescription>{m['admin-ai_form_brandNameDesc']()}</FieldDescription>
206
205
  </FieldContent>
207
206
  <Input
208
207
  value={values.brandName}
209
- onChange={(e) => patch({ brandName: e.target.value })}
208
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ brandName: e.target.value })}
210
209
  className="w-60"
211
210
  />
212
211
  </Field>
213
212
 
214
213
  <Field>
215
214
  <FieldContent>
216
- <FieldLabel>{t('form.description')}</FieldLabel>
217
- <FieldDescription>{t('form.descriptionDesc')}</FieldDescription>
215
+ <FieldLabel>{m['admin-ai_form_description']()}</FieldLabel>
216
+ <FieldDescription>{m['admin-ai_form_descriptionDesc']()}</FieldDescription>
218
217
  </FieldContent>
219
218
  <Textarea
220
219
  value={values.description}
221
- onChange={(e) => patch({ description: e.target.value })}
220
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => patch({ description: e.target.value })}
222
221
  rows={2}
223
222
  />
224
223
  </Field>
@@ -227,11 +226,11 @@ export function ProviderForm({
227
226
 
228
227
  <Field orientation="horizontal">
229
228
  <FieldContent>
230
- <FieldLabel>{t('form.category')}</FieldLabel>
229
+ <FieldLabel>{m['admin-ai_form_category']()}</FieldLabel>
231
230
  </FieldContent>
232
231
  <Select
233
232
  value={values.category}
234
- onValueChange={(v) => patch({ category: v as ProviderCategory })}
233
+ onValueChange={(v: string) => patch({ category: v as ProviderCategory })}
235
234
  >
236
235
  <SelectTrigger className="w-40">
237
236
  <SelectValue />
@@ -248,12 +247,12 @@ export function ProviderForm({
248
247
 
249
248
  <Field orientation="horizontal">
250
249
  <FieldContent>
251
- <FieldLabel>{t('form.adapterKind')}</FieldLabel>
252
- <FieldDescription>{t('form.adapterKindDesc')}</FieldDescription>
250
+ <FieldLabel>{m['admin-ai_form_adapterKind']()}</FieldLabel>
251
+ <FieldDescription>{m['admin-ai_form_adapterKindDesc']()}</FieldDescription>
253
252
  </FieldContent>
254
253
  <Select
255
254
  value={values.adapterKind}
256
- onValueChange={(v) => patch({ adapterKind: v as AdapterKind })}
255
+ onValueChange={(v: string) => patch({ adapterKind: v as AdapterKind })}
257
256
  >
258
257
  <SelectTrigger className="w-60">
259
258
  <SelectValue />
@@ -270,32 +269,32 @@ export function ProviderForm({
270
269
 
271
270
  <Field orientation="horizontal">
272
271
  <FieldContent>
273
- <FieldLabel>{t('form.badge')}</FieldLabel>
274
- <FieldDescription>{t('form.badgeDesc')}</FieldDescription>
272
+ <FieldLabel>{m['admin-ai_form_badge']()}</FieldLabel>
273
+ <FieldDescription>{m['admin-ai_form_badgeDesc']()}</FieldDescription>
275
274
  </FieldContent>
276
275
  <Input
277
276
  value={values.badge}
278
- onChange={(e) => patch({ badge: e.target.value })}
277
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ badge: e.target.value })}
279
278
  className="w-40"
280
- placeholder={t('form.badgePlaceholder')}
279
+ placeholder={m['admin-ai_form_badgePlaceholder']()}
281
280
  />
282
281
  </Field>
283
282
 
284
283
  <Field orientation="horizontal">
285
284
  <FieldContent>
286
- <FieldLabel>{t('form.brandColor')}</FieldLabel>
287
- <FieldDescription>{t('form.brandColorDesc')}</FieldDescription>
285
+ <FieldLabel>{m['admin-ai_form_brandColor']()}</FieldLabel>
286
+ <FieldDescription>{m['admin-ai_form_brandColorDesc']()}</FieldDescription>
288
287
  </FieldContent>
289
288
  <div className="flex items-center gap-2">
290
289
  <input
291
290
  type="color"
292
291
  value={values.brandColor}
293
- onChange={(e) => patch({ brandColor: e.target.value })}
292
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ brandColor: e.target.value })}
294
293
  className="size-9 rounded border"
295
294
  />
296
295
  <Input
297
296
  value={values.brandColor}
298
- onChange={(e) => patch({ brandColor: e.target.value })}
297
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ brandColor: e.target.value })}
299
298
  className="w-28 font-mono"
300
299
  />
301
300
  </div>
@@ -303,30 +302,30 @@ export function ProviderForm({
303
302
 
304
303
  <Field orientation="horizontal">
305
304
  <FieldContent>
306
- <FieldLabel>{t('form.iconSlug')}</FieldLabel>
307
- <FieldDescription>{t('form.iconSlugDesc')}</FieldDescription>
305
+ <FieldLabel>{m['admin-ai_form_iconSlug']()}</FieldLabel>
306
+ <FieldDescription>{m['admin-ai_form_iconSlugDesc']()}</FieldDescription>
308
307
  </FieldContent>
309
308
  <Input
310
309
  value={values.iconSlug}
311
- onChange={(e) => patch({ iconSlug: e.target.value })}
310
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ iconSlug: e.target.value })}
312
311
  className="w-60"
313
- placeholder={t('form.iconSlugPlaceholder')}
312
+ placeholder={m['admin-ai_form_iconSlugPlaceholder']()}
314
313
  />
315
314
  </Field>
316
315
  </FieldGroup>
317
316
  </FieldSet>
318
317
 
319
318
  <FieldSet>
320
- <FieldLegend>{t('form.integrationConfig')}</FieldLegend>
319
+ <FieldLegend>{m['admin-ai_form_integrationConfig']()}</FieldLegend>
321
320
  <FieldGroup>
322
321
  <Field orientation="horizontal">
323
322
  <FieldContent>
324
- <FieldLabel>{t('form.defaultBaseUrl')}</FieldLabel>
325
- <FieldDescription>{t('form.defaultBaseUrlDesc')}</FieldDescription>
323
+ <FieldLabel>{m['admin-ai_form_defaultBaseUrl']()}</FieldLabel>
324
+ <FieldDescription>{m['admin-ai_form_defaultBaseUrlDesc']()}</FieldDescription>
326
325
  </FieldContent>
327
326
  <Input
328
327
  value={values.defaultBaseUrl}
329
- onChange={(e) => patch({ defaultBaseUrl: e.target.value })}
328
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ defaultBaseUrl: e.target.value })}
330
329
  className="w-72"
331
330
  placeholder="https://api.example.com/v1"
332
331
  />
@@ -334,11 +333,11 @@ export function ProviderForm({
334
333
 
335
334
  <Field orientation="horizontal">
336
335
  <FieldContent>
337
- <FieldLabel>{t('form.keyUrl')}</FieldLabel>
336
+ <FieldLabel>{m['admin-ai_form_keyUrl']()}</FieldLabel>
338
337
  </FieldContent>
339
338
  <Input
340
339
  value={values.keyUrl}
341
- onChange={(e) => patch({ keyUrl: e.target.value })}
340
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ keyUrl: e.target.value })}
342
341
  className="w-72"
343
342
  placeholder="https://..."
344
343
  />
@@ -346,11 +345,11 @@ export function ProviderForm({
346
345
 
347
346
  <Field orientation="horizontal">
348
347
  <FieldContent>
349
- <FieldLabel>{t('form.docsUrl')}</FieldLabel>
348
+ <FieldLabel>{m['admin-ai_form_docsUrl']()}</FieldLabel>
350
349
  </FieldContent>
351
350
  <Input
352
351
  value={values.docsUrl}
353
- onChange={(e) => patch({ docsUrl: e.target.value })}
352
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ docsUrl: e.target.value })}
354
353
  className="w-72"
355
354
  placeholder="https://..."
356
355
  />
@@ -358,11 +357,11 @@ export function ProviderForm({
358
357
 
359
358
  <Field orientation="horizontal">
360
359
  <FieldContent>
361
- <FieldLabel>{t('form.apikeyPlaceholder')}</FieldLabel>
360
+ <FieldLabel>{m['admin-ai_form_apikeyPlaceholder']()}</FieldLabel>
362
361
  </FieldContent>
363
362
  <Input
364
363
  value={values.keyPlaceholder}
365
- onChange={(e) => patch({ keyPlaceholder: e.target.value })}
364
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ keyPlaceholder: e.target.value })}
366
365
  className="w-60"
367
366
  placeholder="sk-..."
368
367
  />
@@ -372,35 +371,35 @@ export function ProviderForm({
372
371
 
373
372
  <Field orientation="horizontal">
374
373
  <FieldContent>
375
- <FieldLabel>{t('form.localDeploy')}</FieldLabel>
376
- <FieldDescription>{t('form.localDeployDesc')}</FieldDescription>
374
+ <FieldLabel>{m['admin-ai_form_localDeploy']()}</FieldLabel>
375
+ <FieldDescription>{m['admin-ai_form_localDeployDesc']()}</FieldDescription>
377
376
  </FieldContent>
378
377
  <Switch
379
378
  checked={values.isLocal}
380
- onCheckedChange={(v) => patch({ isLocal: v })}
379
+ onCheckedChange={(v: boolean) => patch({ isLocal: v })}
381
380
  />
382
381
  </Field>
383
382
 
384
383
  <Field orientation="horizontal">
385
384
  <FieldContent>
386
- <FieldLabel>{t('form.platformEnabled')}</FieldLabel>
387
- <FieldDescription>{t('form.platformEnabledDesc')}</FieldDescription>
385
+ <FieldLabel>{m['admin-ai_form_platformEnabled']()}</FieldLabel>
386
+ <FieldDescription>{m['admin-ai_form_platformEnabledDesc']()}</FieldDescription>
388
387
  </FieldContent>
389
388
  <Switch
390
389
  checked={values.enabled}
391
- onCheckedChange={(v) => patch({ enabled: v })}
390
+ onCheckedChange={(v: boolean) => patch({ enabled: v })}
392
391
  />
393
392
  </Field>
394
393
 
395
394
  <Field orientation="horizontal">
396
395
  <FieldContent>
397
- <FieldLabel>{t('form.sortOrder')}</FieldLabel>
398
- <FieldDescription>{t('form.sortOrderDesc')}</FieldDescription>
396
+ <FieldLabel>{m['admin-ai_form_sortOrder']()}</FieldLabel>
397
+ <FieldDescription>{m['admin-ai_form_sortOrderDesc']()}</FieldDescription>
399
398
  </FieldContent>
400
399
  <Input
401
400
  type="number"
402
401
  value={values.sortOrder}
403
- onChange={(e) => patch({ sortOrder: Number(e.target.value) })}
402
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => patch({ sortOrder: Number(e.target.value) })}
404
403
  className="w-24"
405
404
  />
406
405
  </Field>
@@ -408,8 +407,8 @@ export function ProviderForm({
408
407
  </FieldSet>
409
408
 
410
409
  <FieldSet>
411
- <FieldLegend>{t('form.modelList')}</FieldLegend>
412
- <FieldDescription>{t('form.modelListDesc')}</FieldDescription>
410
+ <FieldLegend>{m['admin-ai_form_modelList']()}</FieldLegend>
411
+ <FieldDescription>{m['admin-ai_form_modelListDesc']()}</FieldDescription>
413
412
  <ModelListEditor
414
413
  models={values.models}
415
414
  onChange={(m) => patch({ models: m })}
@@ -418,7 +417,7 @@ export function ProviderForm({
418
417
 
419
418
  <div className="sticky bottom-0 z-20 flex items-center justify-end gap-3 border-t bg-background pt-6 pb-6">
420
419
  <Button asChild variant="outline">
421
- <Link to="/ai-providers">{t('common.cancel')}</Link>
420
+ <Link to="/ai-providers">{m.common_common_cancel()}</Link>
422
421
  </Button>
423
422
  <Button onClick={handleSubmit} disabled={saveMutation.isPending}>
424
423
  {saveMutation.isPending ? (
@@ -426,7 +425,7 @@ export function ProviderForm({
426
425
  ) : (
427
426
  <Save className="size-4" data-icon="inline-start" />
428
427
  )}
429
- {mode === "create" ? t('common.create') : t('common.save')}
428
+ {mode === "create" ? m.common_common_create() : m.common_button_save()}
430
429
  </Button>
431
430
  </div>
432
431
  </div>
@@ -17,7 +17,7 @@
17
17
 
18
18
  // apps/admin/src/components/ai-provider/provider-table.tsx
19
19
  import { useState } from "react"
20
- import { useTranslation } from 'react-i18next'
20
+ import { m } from '@/paraglide/messages.js'
21
21
  import { useMutation } from "@tanstack/react-query"
22
22
  import { Link, useRouter } from "@tanstack/react-router"
23
23
  import { Edit, Plus, Trash2, Users } from "lucide-react"
@@ -44,7 +44,6 @@ import { deleteAiProviderAdminFn } from "@/server/serverFns/ai-provider/delete"
44
44
  const ADAPTER_LABEL_MAP = new Map(ADAPTER_KINDS.map((k) => [k.value, k.label]))
45
45
 
46
46
  export function ProviderTable({ data }: { data: AiProviderRow[] }) {
47
- const { t } = useTranslation('admin-ai')
48
47
  const router = useRouter()
49
48
  const [pendingDelete, setPendingDelete] = useState<AiProviderRow | null>(null)
50
49
 
@@ -54,29 +53,29 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
54
53
  data: { id: vars.id, enabled: vars.enabled },
55
54
  }),
56
55
  onSuccess: async () => {
57
- toast.success(t('table.toast.updated'))
56
+ toast.success(m['admin-ai_table_toast_updated']())
58
57
  await router.invalidate()
59
58
  },
60
59
  onError: (err) =>
61
- toast.error(err instanceof Error ? err.message : t('market.form.operationFailed')),
60
+ toast.error(err instanceof Error ? err.message : m.common_market_form_operationFailed()),
62
61
  })
63
62
 
64
63
  const deleteMutation = useMutation({
65
64
  mutationFn: (id: string) => deleteAiProviderAdminFn({ data: { id } }),
66
65
  onSuccess: async () => {
67
- toast.success(t('table.toast.deleted'))
66
+ toast.success(m['admin-ai_table_toast_deleted']())
68
67
  setPendingDelete(null)
69
68
  await router.invalidate()
70
69
  },
71
70
  onError: (err) =>
72
- toast.error(err instanceof Error ? err.message : t('table.toast.deleteFailed')),
71
+ toast.error(err instanceof Error ? err.message : m['admin-ai_table_toast_deleteFailed']()),
73
72
  })
74
73
 
75
74
  const columns: AdminColumnDef<AiProviderRow>[] = [
76
75
  {
77
76
  accessorKey: "displayName",
78
- header: t('table.columns.provider'),
79
- cell: ({ row }) => (
77
+ header: m['admin-ai_table_columns_provider'](),
78
+ cell: ({ row }: any) => (
80
79
  <div className="flex items-center gap-3">
81
80
  <ProviderIcon provider={row.original} size={36} />
82
81
  <div>
@@ -90,8 +89,8 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
90
89
  },
91
90
  {
92
91
  accessorKey: "adapterKind",
93
- header: t('table.columns.adapterKind'),
94
- cell: ({ row }) => (
92
+ header: m['admin-ai_table_columns_adapterKind'](),
93
+ cell: ({ row }: any) => (
95
94
  <Badge variant="outline">
96
95
  {ADAPTER_LABEL_MAP.get(row.original.adapterKind) ??
97
96
  row.original.adapterKind}
@@ -100,15 +99,15 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
100
99
  },
101
100
  {
102
101
  accessorKey: "category",
103
- header: t('table.columns.category'),
104
- cell: ({ row }) => (
102
+ header: m['admin-ai_table_columns_category'](),
103
+ cell: ({ row }: any) => (
105
104
  <Badge variant="secondary">{row.original.category}</Badge>
106
105
  ),
107
106
  },
108
107
  {
109
108
  accessorKey: "badge",
110
- header: t('table.columns.badge'),
111
- cell: ({ row }) => {
109
+ header: m['admin-ai_table_columns_badge'](),
110
+ cell: ({ row }: any) => {
112
111
  const b = row.original.badge
113
112
  if (!b) return <span className="text-muted-foreground">—</span>
114
113
  const style = PROVIDER_BADGE_STYLES[b] ?? "bg-muted text-muted-foreground"
@@ -121,8 +120,8 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
121
120
  },
122
121
  {
123
122
  accessorKey: "models",
124
- header: t('table.columns.modelCount'),
125
- cell: ({ row }) => (
123
+ header: m['admin-ai_table_columns_modelCount'](),
124
+ cell: ({ row }: any) => (
126
125
  <span className="text-sm">
127
126
  {Array.isArray(row.original.models) ? row.original.models.length : 0}
128
127
  </span>
@@ -133,10 +132,10 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
133
132
  header: () => (
134
133
  <span className="inline-flex items-center gap-1">
135
134
  <Users className="size-3.5" />
136
- {t('table.columns.userConfigCount')}
135
+ {m['admin-ai_table_columns_userConfigCount']()}
137
136
  </span>
138
137
  ),
139
- cell: ({ row }) => (
138
+ cell: ({ row }: any) => (
140
139
  <span
141
140
  className={
142
141
  row.original.configCount > 0
@@ -150,25 +149,25 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
150
149
  },
151
150
  {
152
151
  accessorKey: "enabled",
153
- header: t('table.columns.platformEnabled'),
154
- cell: ({ row }) => (
152
+ header: m['admin-ai_table_columns_platformEnabled'](),
153
+ cell: ({ row }: any) => (
155
154
  <Switch
156
155
  checked={row.original.enabled}
157
- onCheckedChange={(checked) =>
156
+ onCheckedChange={(checked: boolean) =>
158
157
  toggleMutation.mutate({
159
158
  id: row.original.id,
160
159
  enabled: checked,
161
160
  })
162
161
  }
163
162
  disabled={toggleMutation.isPending}
164
- aria-label={t('table.enabledToggle')}
163
+ aria-label={m['admin-ai_table_enabledToggle']()}
165
164
  />
166
165
  ),
167
166
  },
168
167
  {
169
168
  id: "actions",
170
- header: t('table.columns.actions'),
171
- cell: ({ row }) => {
169
+ header: m['admin-ai_table_columns_actions'](),
170
+ cell: ({ row }: any) => {
172
171
  const canDelete = row.original.configCount === 0
173
172
  return (
174
173
  <div className="flex items-center gap-1">
@@ -178,7 +177,7 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
178
177
  params={{ id: row.original.id }}
179
178
  >
180
179
  <Edit className="size-4" />
181
- {t('table.edit')}
180
+ {m['admin-ai_table_edit']()}
182
181
  </Link>
183
182
  </Button>
184
183
  {canDelete ? (
@@ -189,17 +188,17 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
189
188
  onClick={() => setPendingDelete(row.original)}
190
189
  >
191
190
  <Trash2 className="size-4" />
192
- {t('table.delete')}
191
+ {m['admin-ai_table_delete']()}
193
192
  </Button>
194
193
  ) : (
195
194
  <Button
196
195
  variant="ghost"
197
196
  size="sm"
198
197
  disabled
199
- title={t('table.deleteDisabled', { count: row.original.configCount })}
198
+ title={m['admin-ai_table_deleteDisabled']({ count: row.original.configCount })}
200
199
  >
201
200
  <Trash2 className="size-4" />
202
- {t('table.delete')}
201
+ {m['admin-ai_table_delete']()}
203
202
  </Button>
204
203
  )}
205
204
  </div>
@@ -212,15 +211,15 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
212
211
  <div className="space-y-4">
213
212
  <div className="flex items-center justify-between">
214
213
  <div>
215
- <h2 className="text-lg font-semibold">{t('table.pageTitle')}</h2>
214
+ <h2 className="text-lg font-semibold">{m['admin-ai_table_pageTitle']()}</h2>
216
215
  <p className="text-sm text-muted-foreground">
217
- {t('table.pageDescription', { count: data.length })}
216
+ {m['admin-ai_table_pageDescription']({ count: data.length })}
218
217
  </p>
219
218
  </div>
220
219
  <Button asChild>
221
220
  <Link to="/ai-providers/new">
222
221
  <Plus className="size-4" />
223
- {t('table.addProvider')}
222
+ {m['admin-ai_table_addProvider']()}
224
223
  </Link>
225
224
  </Button>
226
225
  </div>
@@ -228,19 +227,19 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
228
227
 
229
228
  <AlertDialog
230
229
  open={!!pendingDelete}
231
- onOpenChange={(open) => !open && setPendingDelete(null)}
230
+ onOpenChange={(open: boolean) => !open && setPendingDelete(null)}
232
231
  >
233
232
  <AlertDialogContent>
234
233
  <AlertDialogHeader>
235
234
  <AlertDialogTitle>
236
- {t('table.deleteConfirm.title', { name: pendingDelete?.displayName })}
235
+ {m['admin-ai_table_deleteConfirm_title']({ name: pendingDelete?.displayName ?? '' })}
237
236
  </AlertDialogTitle>
238
237
  <AlertDialogDescription>
239
- {t('table.deleteConfirm.description')}
238
+ {m['admin-ai_table_deleteConfirm_description']()}
240
239
  </AlertDialogDescription>
241
240
  </AlertDialogHeader>
242
241
  <AlertDialogFooter>
243
- <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
242
+ <AlertDialogCancel>{m.common_common_cancel()}</AlertDialogCancel>
244
243
  <AlertDialogAction
245
244
  className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
246
245
  onClick={() =>
@@ -248,7 +247,7 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
248
247
  }
249
248
  disabled={deleteMutation.isPending}
250
249
  >
251
- {t('table.deleteConfirm.confirm')}
250
+ {m['admin-ai_table_deleteConfirm_confirm']()}
252
251
  </AlertDialogAction>
253
252
  </AlertDialogFooter>
254
253
  </AlertDialogContent>