@allbluecn/admin-app 0.3.2 → 0.3.4
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/allblue-ui/package.json +6 -0
- package/allblue-ui/src/components/confirm-dialog.tsx +94 -0
- package/allblue-ui/src/components/infinite-scroll-sentinel.tsx +37 -0
- package/allblue-ui/src/components/notion-avatar.tsx +126 -0
- package/allblue-ui/src/components/user-avatar.tsx +82 -0
- package/{src/components → allblue-ui/src}/data-table/data-table-pagination.tsx +14 -5
- package/{src/components → allblue-ui/src}/data-table/data-table.tsx +1 -1
- package/{src/components → allblue-ui/src}/data-table/table-detail-layout.tsx +5 -6
- package/allblue-ui/src/hooks/use-load-more.ts +57 -0
- package/allblue-ui/src/index.ts +31 -0
- package/allblue-ui/src/lib/datetime.ts +226 -0
- package/allblue-ui/src/lib/sanitize.ts +18 -0
- package/allblue-ui/src/lib/utils.ts +1 -0
- package/{src/components → allblue-ui/src}/ui/alert-dialog.tsx +1 -3
- package/{src/components → allblue-ui/src}/ui/checkbox.tsx +0 -1
- package/{src/components → allblue-ui/src}/ui/dialog.tsx +1 -1
- package/{src/components → allblue-ui/src}/ui/dropdown-menu.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/field.tsx +2 -2
- package/{src/components → allblue-ui/src}/ui/input-group.tsx +3 -4
- package/allblue-ui/src/ui/input.tsx +19 -0
- package/{src/components → allblue-ui/src}/ui/pagination.tsx +1 -1
- package/{src/components → allblue-ui/src}/ui/select.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/separator.tsx +1 -0
- package/{src/components → allblue-ui/src}/ui/sheet.tsx +1 -2
- package/{src/components → allblue-ui/src}/ui/sidebar.tsx +7 -7
- package/{src/components → allblue-ui/src}/ui/switch.tsx +1 -0
- package/{src/components → allblue-ui/src}/ui/table.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/toast.tsx +44 -18
- package/{src/components → allblue-ui/src}/ui/tooltip.tsx +1 -0
- package/package.json +21 -18
- package/src/components/admin/admin-user-menu.tsx +4 -4
- package/src/components/admin/developer-log.tsx +16 -32
- package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
- package/src/components/ai-prompts/preview-panel.tsx +1 -1
- package/src/components/ai-prompts/prompt-editor.tsx +8 -9
- package/src/components/ai-prompts/prompt-table.tsx +6 -6
- package/src/components/ai-prompts/section-editor.tsx +3 -3
- package/src/components/ai-prompts/variable-panel.tsx +2 -2
- package/src/components/ai-prompts/version-history.tsx +21 -41
- package/src/components/ai-provider/model-list-editor.tsx +2 -3
- package/src/components/ai-provider/provider-form.tsx +7 -7
- package/src/components/ai-provider/provider-table.tsx +17 -40
- package/src/components/auth/admin-login-form.tsx +7 -7
- package/src/components/auth/icon-input.tsx +2 -2
- package/src/components/auth/password-input.tsx +2 -2
- package/src/components/component/component-form-dialog.tsx +10 -18
- package/src/components/component/component-import-dialog.tsx +6 -6
- package/src/components/component/component-preview.tsx +311 -0
- package/src/components/component/component-table.tsx +3 -3
- package/src/components/dependency/changelog-dialog.tsx +2 -2
- package/src/components/dependency/columns.tsx +3 -3
- package/src/components/dependency/dependency-table.tsx +7 -7
- package/src/components/dependency/edit-dialog.tsx +8 -8
- package/src/components/invitations/invitations-page.tsx +26 -49
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
- package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
- package/src/components/layout/sidebar-02/index.tsx +1 -1
- package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
- package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
- package/src/components/market/columns.tsx +4 -4
- package/src/components/market/market-app-form.tsx +6 -7
- package/src/components/market/market-app-list.tsx +14 -29
- package/src/components/monitoring/error-list.tsx +2 -2
- package/src/components/monitoring/metrics-stats.tsx +1 -1
- package/src/components/monitoring/request-log-table.tsx +2 -2
- package/src/components/oauth-providers/provider-page.tsx +20 -38
- package/src/components/permissions/assign-dialog.tsx +135 -0
- package/src/components/permissions/create-role-dialog.tsx +135 -0
- package/src/components/permissions/types.ts +49 -0
- package/src/components/user/columns.tsx +6 -6
- package/src/components/user/settings-activity-log.tsx +7 -7
- package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
- package/src/components/user/settings-profile/social-links.tsx +63 -0
- package/src/components/user/settings-profile.tsx +26 -125
- package/src/components/user/user-create-dialog.tsx +6 -6
- package/src/components/user/user-detail/error-boundary.tsx +19 -0
- package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
- package/src/components/user/user-filters.tsx +4 -4
- package/src/lib/auth/page-gate.ts +28 -0
- package/src/lib/auth.config.ts +25 -9
- package/src/lib/i18n/detect.ts +1 -1
- package/src/lib/loader-serial.ts +14 -0
- package/src/providers/index.tsx +1 -1
- package/src/routeTree.gen.ts +67 -78
- package/src/routes/__root.tsx +4 -4
- package/src/routes/_nav/audit/route.lazy.tsx +1 -2
- package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
- package/src/routes/_nav/components/route.lazy.tsx +14 -34
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
- package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
- package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
- package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/health/route.tsx +14 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
- package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
- package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
- package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
- package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
- package/src/routes/_nav/permissions/route.tsx +1 -1
- package/src/routes/_nav/route.tsx +5 -13
- package/src/routes/_nav/sso/route.lazy.tsx +2 -3
- package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
- package/src/routes/_nav/users/-components/users-table.tsx +1 -1
- package/src/routes/_nav/users/route.lazy.tsx +1 -1
- package/src/routes/login/route.lazy.tsx +1 -1
- package/src/routes/setup/route.lazy.tsx +2 -2
- package/src/server/__tests__/admin-billing.test.ts +191 -16
- package/src/server/__tests__/compliance.test.ts +44 -7
- package/src/server/__tests__/permissions.test.ts +10 -5
- package/src/server/__tests__/prompt-defaults.test.ts +3 -3
- package/src/server/admin-ai-alerts.ts +2 -1
- package/src/server/admin-ai-usage.ts +2 -1
- package/src/server/ai-memory/store.ts +6 -2
- package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
- package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
- package/src/server/knowledge-public/github-sync.ts +2 -1
- package/src/server/license-issue.ts +2 -1
- package/src/server/middlewares/auth-rate-limit.ts +117 -0
- package/src/server/observability/health-check.ts +4 -1
- package/src/server/observability/metrics.ts +17 -15
- package/src/server/observability/request-middleware.ts +2 -1
- package/src/server/observability/timing.ts +14 -16
- package/src/server/serverFns/admin-billing.ts +107 -12
- package/src/server/serverFns/ai-memory/list-users.ts +2 -1
- package/src/server/serverFns/ai-prompts/activate.ts +2 -1
- package/src/server/serverFns/ai-prompts/get.ts +3 -2
- package/src/server/serverFns/ai-prompts/list.ts +3 -2
- package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
- package/src/server/serverFns/ai-prompts/save.ts +5 -3
- package/src/server/serverFns/ai-provider/create.ts +4 -1
- package/src/server/serverFns/ai-provider/delete.ts +4 -1
- package/src/server/serverFns/ai-provider/get.ts +4 -1
- package/src/server/serverFns/ai-provider/list.ts +28 -1
- package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
- package/src/server/serverFns/ai-provider/update.ts +4 -1
- package/src/server/serverFns/anyhub/market.ts +8 -1
- package/src/server/serverFns/audit.ts +14 -1
- package/src/server/serverFns/auth/setup.ts +3 -1
- package/src/server/serverFns/compliance.ts +32 -3
- package/src/server/serverFns/components/import.ts +69 -14
- package/src/server/serverFns/components/list.ts +4 -1
- package/src/server/serverFns/components/mutations.ts +9 -1
- package/src/server/serverFns/dependency/list.ts +28 -5
- package/src/server/serverFns/dependency/sync.ts +7 -2
- package/src/server/serverFns/dependency/update.ts +4 -1
- package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
- package/src/server/serverFns/developer-logs.ts +5 -1
- package/src/server/serverFns/invitations.ts +7 -1
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
- package/src/server/serverFns/licenses.ts +8 -2
- package/src/server/serverFns/monitoring/get-errors.ts +2 -1
- package/src/server/serverFns/monitoring/get-health.ts +2 -1
- package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
- package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
- package/src/server/serverFns/monitoring/report-error.ts +2 -1
- package/src/server/serverFns/monitoring/reset.ts +2 -1
- package/src/server/serverFns/oauth-providers.ts +4 -1
- package/src/server/serverFns/permissions.ts +18 -11
- package/src/server/serverFns/sentry/config.ts +11 -8
- package/src/server/serverFns/sso-config.ts +17 -2
- package/src/server/serverFns/user/create.ts +6 -1
- package/src/server/serverFns/user/delete.ts +6 -1
- package/src/server/serverFns/user/detail.ts +5 -2
- package/src/server/serverFns/user/list.ts +19 -4
- package/src/server/serverFns/user/reset-password.ts +10 -2
- package/src/server/serverFns/user/update-role.ts +10 -2
- package/src/server/serverFns/user/update-status.ts +10 -2
- package/src/styles/globals.css +4 -0
- package/src/components/ui/input.tsx +0 -19
- /package/{src → allblue-ui/src}/hooks/use-mobile.ts +0 -0
- /package/{src → allblue-ui/src}/lib/avatar.ts +0 -0
- /package/{src/components → allblue-ui/src}/ui/alert.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/avatar.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/badge.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/button.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/card.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/collapsible.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/label.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/skeleton.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/textarea.tsx +0 -0
- /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
- /package/src/routes/{api.sentry-test.ts → api/sentry-test.ts} +0 -0
|
@@ -4,7 +4,7 @@ import { Route } from '@/routes/_nav/developer-logs/route'
|
|
|
4
4
|
import type { DeveloperLogEntry } from '@allbluecn/kernel'
|
|
5
5
|
import { useQueryClient } from '@tanstack/react-query'
|
|
6
6
|
import { useServerFn } from '@tanstack/react-start'
|
|
7
|
-
import { toast } from '
|
|
7
|
+
import { toast } from '@allbluecn/ui/toast'
|
|
8
8
|
import {
|
|
9
9
|
Plus,
|
|
10
10
|
Pencil,
|
|
@@ -19,22 +19,12 @@ import {
|
|
|
19
19
|
DialogHeader,
|
|
20
20
|
DialogTitle,
|
|
21
21
|
DialogFooter,
|
|
22
|
-
} from '
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
AlertDialogDescription,
|
|
29
|
-
AlertDialogFooter,
|
|
30
|
-
AlertDialogHeader,
|
|
31
|
-
AlertDialogTitle,
|
|
32
|
-
AlertDialogTrigger,
|
|
33
|
-
} from '@/components/ui/alert-dialog'
|
|
34
|
-
import { Input } from '@/components/ui/input'
|
|
35
|
-
import { Textarea } from '@/components/ui/textarea'
|
|
36
|
-
import { Label } from '@/components/ui/label'
|
|
37
|
-
import { Button } from '@/components/ui/button'
|
|
22
|
+
} from '@allbluecn/ui/dialog'
|
|
23
|
+
import { ConfirmDialog } from '@allbluecn/ui/components/confirm-dialog'
|
|
24
|
+
import { Input } from '@allbluecn/ui/input'
|
|
25
|
+
import { Textarea } from '@allbluecn/ui/textarea'
|
|
26
|
+
import { Label } from '@allbluecn/ui/label'
|
|
27
|
+
import { Button } from '@allbluecn/ui/button'
|
|
38
28
|
import { createDeveloperLogFn, updateDeveloperLogFn, deleteDeveloperLogFn } from '@/server/serverFns/developer-logs'
|
|
39
29
|
import { autoGenerateLogsFn, backfillLogDetailsFn } from '@/server/serverFns/developer-log-auto-generate'
|
|
40
30
|
|
|
@@ -315,21 +305,15 @@ function DeveloperLogPage() {
|
|
|
315
305
|
<Button variant="ghost" size="icon" className="size-7" onClick={() => handleEdit(entry)}>
|
|
316
306
|
<Pencil className="size-3.5" />
|
|
317
307
|
</Button>
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<AlertDialogFooter>
|
|
328
|
-
<AlertDialogCancel>{m.common_button_cancel()}</AlertDialogCancel>
|
|
329
|
-
<AlertDialogAction onClick={() => handleDelete(entry.id)} className="bg-destructive hover:bg-destructive/90">{m.common_button_confirm()}</AlertDialogAction>
|
|
330
|
-
</AlertDialogFooter>
|
|
331
|
-
</AlertDialogContent>
|
|
332
|
-
</AlertDialog>
|
|
308
|
+
<ConfirmDialog
|
|
309
|
+
trigger={<Button variant="ghost" size="icon" className="size-7 text-destructive"><Trash2 className="size-3.5" /></Button>}
|
|
310
|
+
title={m.common_button_delete()}
|
|
311
|
+
description={m.common_developerLogs_deleteConfirm()}
|
|
312
|
+
cancelLabel={m.common_button_cancel()}
|
|
313
|
+
confirmLabel={m.common_button_confirm()}
|
|
314
|
+
confirmClassName="bg-destructive hover:bg-destructive/90"
|
|
315
|
+
onConfirm={() => handleDelete(entry.id)}
|
|
316
|
+
/>
|
|
333
317
|
</div>
|
|
334
318
|
</td>
|
|
335
319
|
</tr>
|
|
@@ -20,37 +20,27 @@ import { useMemo, useState } from "react"
|
|
|
20
20
|
import {
|
|
21
21
|
Eraser,
|
|
22
22
|
Loader2,
|
|
23
|
-
RotateCw,
|
|
24
23
|
Search,
|
|
25
24
|
TriangleAlert,
|
|
26
25
|
Trash2,
|
|
27
26
|
} from "lucide-react"
|
|
28
27
|
|
|
29
28
|
import { m } from "@/paraglide/messages.js"
|
|
30
|
-
import { formatTimestamp } from "@allbluecn/
|
|
31
|
-
import { toast } from "
|
|
32
|
-
import { Alert, AlertDescription, AlertTitle } from "
|
|
33
|
-
import { Badge } from "
|
|
34
|
-
import { Button } from "
|
|
35
|
-
import { Card, CardContent } from "
|
|
36
|
-
import {
|
|
37
|
-
AlertDialog,
|
|
38
|
-
AlertDialogAction,
|
|
39
|
-
AlertDialogCancel,
|
|
40
|
-
AlertDialogContent,
|
|
41
|
-
AlertDialogDescription,
|
|
42
|
-
AlertDialogFooter,
|
|
43
|
-
AlertDialogHeader,
|
|
44
|
-
AlertDialogTitle,
|
|
45
|
-
} from "@/components/ui/alert-dialog"
|
|
29
|
+
import { formatTimestamp } from "@allbluecn/ui/lib/datetime"
|
|
30
|
+
import { toast } from "@allbluecn/ui/toast"
|
|
31
|
+
import { Alert, AlertDescription, AlertTitle } from "@allbluecn/ui/alert"
|
|
32
|
+
import { Badge } from "@allbluecn/ui/badge"
|
|
33
|
+
import { Button } from "@allbluecn/ui/button"
|
|
34
|
+
import { Card, CardContent } from "@allbluecn/ui/card"
|
|
35
|
+
import { ConfirmDialog } from "@allbluecn/ui/components/confirm-dialog"
|
|
46
36
|
import {
|
|
47
37
|
Select,
|
|
48
38
|
SelectContent,
|
|
49
39
|
SelectItem,
|
|
50
40
|
SelectTrigger,
|
|
51
41
|
SelectValue,
|
|
52
|
-
} from "
|
|
53
|
-
import { DataTable, type AdminColumnDef } from "
|
|
42
|
+
} from "@allbluecn/ui/select"
|
|
43
|
+
import { DataTable, type AdminColumnDef } from "@allbluecn/ui/data-table"
|
|
54
44
|
import { listMemoryFactsFn } from "@/server/serverFns/ai-memory/list"
|
|
55
45
|
import { deleteMemoryFactFn } from "@/server/serverFns/ai-memory/delete"
|
|
56
46
|
import { clearMemoryFactsFn } from "@/server/serverFns/ai-memory/clear"
|
|
@@ -322,62 +312,32 @@ export function MemoryFactsPanel({
|
|
|
322
312
|
</CardContent>
|
|
323
313
|
</Card>
|
|
324
314
|
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
</AlertDialogCancel>
|
|
339
|
-
<AlertDialogAction
|
|
340
|
-
disabled={deletingId !== null}
|
|
341
|
-
onClick={(event) => {
|
|
342
|
-
if (!pendingDelete) return
|
|
343
|
-
event.preventDefault()
|
|
344
|
-
void handleDelete(pendingDelete, () => setPendingDelete(null))
|
|
345
|
-
}}
|
|
346
|
-
>
|
|
347
|
-
{m["admin-ai-memory_delete_confirm_action"]()}
|
|
348
|
-
</AlertDialogAction>
|
|
349
|
-
</AlertDialogFooter>
|
|
350
|
-
</AlertDialogContent>
|
|
351
|
-
</AlertDialog>
|
|
315
|
+
<ConfirmDialog
|
|
316
|
+
open={pendingDelete !== null}
|
|
317
|
+
onOpenChange={(open) => { if (!open) setPendingDelete(null) }}
|
|
318
|
+
title={m["admin-ai-memory_delete_confirm_title"]()}
|
|
319
|
+
description={m["admin-ai-memory_delete_confirm_description"]()}
|
|
320
|
+
cancelLabel={m["admin-ai-memory_delete_cancel"]()}
|
|
321
|
+
confirmLabel={m["admin-ai-memory_delete_confirm_action"]()}
|
|
322
|
+
confirmDisabled={deletingId !== null}
|
|
323
|
+
onConfirm={() => {
|
|
324
|
+
if (!pendingDelete) return
|
|
325
|
+
void handleDelete(pendingDelete, () => setPendingDelete(null))
|
|
326
|
+
}}
|
|
327
|
+
/>
|
|
352
328
|
|
|
353
|
-
<
|
|
329
|
+
<ConfirmDialog
|
|
354
330
|
open={confirmingClear}
|
|
355
331
|
onOpenChange={(open) => {
|
|
356
332
|
if (!open) setConfirmingClear(false)
|
|
357
333
|
}}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
{m["admin-ai-memory_clear_confirm_description"]()}
|
|
366
|
-
</AlertDialogDescription>
|
|
367
|
-
</AlertDialogHeader>
|
|
368
|
-
<AlertDialogFooter>
|
|
369
|
-
<AlertDialogCancel>
|
|
370
|
-
{m["admin-ai-memory_clear_cancel"]()}
|
|
371
|
-
</AlertDialogCancel>
|
|
372
|
-
<AlertDialogAction
|
|
373
|
-
disabled={loading}
|
|
374
|
-
onClick={() => void handleClear()}
|
|
375
|
-
>
|
|
376
|
-
{m["admin-ai-memory_clear_confirm_action"]()}
|
|
377
|
-
</AlertDialogAction>
|
|
378
|
-
</AlertDialogFooter>
|
|
379
|
-
</AlertDialogContent>
|
|
380
|
-
</AlertDialog>
|
|
334
|
+
title={m["admin-ai-memory_clear_confirm_title"]()}
|
|
335
|
+
description={m["admin-ai-memory_clear_confirm_description"]()}
|
|
336
|
+
cancelLabel={m["admin-ai-memory_clear_cancel"]()}
|
|
337
|
+
confirmLabel={m["admin-ai-memory_clear_confirm_action"]()}
|
|
338
|
+
confirmDisabled={loading}
|
|
339
|
+
onConfirm={() => void handleClear()}
|
|
340
|
+
/>
|
|
381
341
|
</div>
|
|
382
342
|
)
|
|
383
343
|
}
|
|
@@ -20,7 +20,7 @@ import { useCallback, useEffect, useRef, useState } from "react"
|
|
|
20
20
|
import { AlertTriangle, Loader2 } from "lucide-react"
|
|
21
21
|
|
|
22
22
|
import { m } from "@/paraglide/messages.js"
|
|
23
|
-
import { Input } from "
|
|
23
|
+
import { Input } from "@allbluecn/ui/input"
|
|
24
24
|
import {
|
|
25
25
|
PROMPT_PREVIEW_DEBOUNCE_MS,
|
|
26
26
|
useDebouncedCallback,
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
// apps/admin/src/components/ai-prompts/prompt-editor.tsx
|
|
19
19
|
import { useCallback, useEffect, useState } from "react"
|
|
20
20
|
import type {
|
|
21
|
-
SystemPromptSectionDTO,
|
|
22
21
|
SystemPromptTemplateDTO,
|
|
23
22
|
} from "@allbluecn/kernel"
|
|
24
23
|
import { useMutation } from "@tanstack/react-query"
|
|
@@ -26,18 +25,18 @@ import { Link } from "@tanstack/react-router"
|
|
|
26
25
|
import { ArrowLeft, Loader2, Save, ShieldCheck } from "lucide-react"
|
|
27
26
|
|
|
28
27
|
import { m } from "@/paraglide/messages.js"
|
|
29
|
-
import { toast } from "
|
|
30
|
-
import { Badge } from "
|
|
31
|
-
import { Button } from "
|
|
32
|
-
import { Field, FieldContent, FieldLabel } from "
|
|
33
|
-
import { Input } from "
|
|
34
|
-
import { Switch } from "
|
|
35
|
-
import { Textarea } from "
|
|
28
|
+
import { toast } from "@allbluecn/ui/toast"
|
|
29
|
+
import { Badge } from "@allbluecn/ui/badge"
|
|
30
|
+
import { Button } from "@allbluecn/ui/button"
|
|
31
|
+
import { Field, FieldContent, FieldLabel } from "@allbluecn/ui/field"
|
|
32
|
+
import { Input } from "@allbluecn/ui/input"
|
|
33
|
+
import { Switch } from "@allbluecn/ui/switch"
|
|
34
|
+
import { Textarea } from "@allbluecn/ui/textarea"
|
|
36
35
|
import {
|
|
37
36
|
Tooltip,
|
|
38
37
|
TooltipContent,
|
|
39
38
|
TooltipTrigger,
|
|
40
|
-
} from "
|
|
39
|
+
} from "@allbluecn/ui/tooltip"
|
|
41
40
|
import { activatePromptTemplateFn } from "@/server/serverFns/ai-prompts/activate"
|
|
42
41
|
import { getPromptTemplateFn } from "@/server/serverFns/ai-prompts/get"
|
|
43
42
|
import { rollbackPromptTemplateFn } from "@/server/serverFns/ai-prompts/rollback"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
import { useMutation } from "@tanstack/react-query"
|
|
20
20
|
import { Link, useRouter } from "@tanstack/react-router"
|
|
21
21
|
import { Edit, ShieldCheck } from "lucide-react"
|
|
22
|
-
import { formatTimestamp } from "@allbluecn/
|
|
22
|
+
import { formatTimestamp } from "@allbluecn/ui/lib/datetime"
|
|
23
23
|
|
|
24
24
|
import { m } from "@/paraglide/messages.js"
|
|
25
|
-
import { toast } from "
|
|
26
|
-
import { Badge } from "
|
|
27
|
-
import { Button } from "
|
|
28
|
-
import { Switch } from "
|
|
29
|
-
import { DataTable, type AdminColumnDef } from "
|
|
25
|
+
import { toast } from "@allbluecn/ui/toast"
|
|
26
|
+
import { Badge } from "@allbluecn/ui/badge"
|
|
27
|
+
import { Button } from "@allbluecn/ui/button"
|
|
28
|
+
import { Switch } from "@allbluecn/ui/switch"
|
|
29
|
+
import { DataTable, type AdminColumnDef } from "@allbluecn/ui/data-table"
|
|
30
30
|
import { activatePromptTemplateFn } from "@/server/serverFns/ai-prompts/activate"
|
|
31
31
|
import type { PromptTemplateRow } from "./prompt-utils"
|
|
32
32
|
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
import { ArrowDown, ArrowUp, Plus, Trash2 } from "lucide-react"
|
|
20
20
|
|
|
21
21
|
import { m } from "@/paraglide/messages.js"
|
|
22
|
-
import { Button } from "
|
|
23
|
-
import { Input } from "
|
|
24
|
-
import { Textarea } from "
|
|
22
|
+
import { Button } from "@allbluecn/ui/button"
|
|
23
|
+
import { Input } from "@allbluecn/ui/input"
|
|
24
|
+
import { Textarea } from "@allbluecn/ui/textarea"
|
|
25
25
|
import {
|
|
26
26
|
appendSection,
|
|
27
27
|
moveSection,
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
import { AlertTriangle, Plus, Trash2 } from "lucide-react"
|
|
20
20
|
|
|
21
21
|
import { m } from "@/paraglide/messages.js"
|
|
22
|
-
import { Button } from "
|
|
23
|
-
import { Input } from "
|
|
22
|
+
import { Button } from "@allbluecn/ui/button"
|
|
23
|
+
import { Input } from "@allbluecn/ui/input"
|
|
24
24
|
import {
|
|
25
25
|
addVariable,
|
|
26
26
|
removeVariable,
|
|
@@ -18,26 +18,17 @@
|
|
|
18
18
|
// apps/admin/src/components/ai-prompts/version-history.tsx
|
|
19
19
|
import { useState } from "react"
|
|
20
20
|
import { History, Loader2, RotateCcw } from "lucide-react"
|
|
21
|
-
import { formatTimestamp } from "@allbluecn/
|
|
21
|
+
import { formatTimestamp } from "@allbluecn/ui/lib/datetime"
|
|
22
22
|
|
|
23
23
|
import { m } from "@/paraglide/messages.js"
|
|
24
|
-
import { Badge } from "
|
|
25
|
-
import { Button } from "
|
|
24
|
+
import { Badge } from "@allbluecn/ui/badge"
|
|
25
|
+
import { Button } from "@allbluecn/ui/button"
|
|
26
26
|
import {
|
|
27
27
|
Collapsible,
|
|
28
28
|
CollapsibleContent,
|
|
29
29
|
CollapsibleTrigger,
|
|
30
|
-
} from "
|
|
31
|
-
import {
|
|
32
|
-
AlertDialog,
|
|
33
|
-
AlertDialogAction,
|
|
34
|
-
AlertDialogCancel,
|
|
35
|
-
AlertDialogContent,
|
|
36
|
-
AlertDialogDescription,
|
|
37
|
-
AlertDialogFooter,
|
|
38
|
-
AlertDialogHeader,
|
|
39
|
-
AlertDialogTitle,
|
|
40
|
-
} from "@/components/ui/alert-dialog"
|
|
30
|
+
} from "@allbluecn/ui/collapsible"
|
|
31
|
+
import { ConfirmDialog } from "@allbluecn/ui/components/confirm-dialog"
|
|
41
32
|
import type { PromptVersionSummary } from "./prompt-utils"
|
|
42
33
|
|
|
43
34
|
interface VersionHistoryProps {
|
|
@@ -125,33 +116,22 @@ export function VersionHistory({
|
|
|
125
116
|
})}
|
|
126
117
|
</CollapsibleContent>
|
|
127
118
|
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<AlertDialogAction
|
|
145
|
-
disabled={rollbackPending}
|
|
146
|
-
onClick={() => {
|
|
147
|
-
if (rollbackTarget !== null) onRollback(rollbackTarget)
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
{m["admin-ai_prompts_rollback_confirm_action"]()}
|
|
151
|
-
</AlertDialogAction>
|
|
152
|
-
</AlertDialogFooter>
|
|
153
|
-
</AlertDialogContent>
|
|
154
|
-
</AlertDialog>
|
|
119
|
+
<ConfirmDialog
|
|
120
|
+
open={rollbackTarget !== null}
|
|
121
|
+
onOpenChange={(open) => { if (!open) setRollbackTarget(null) }}
|
|
122
|
+
title={m["admin-ai_prompts_rollback_confirm_title"]({
|
|
123
|
+
version: rollbackTarget ?? 0,
|
|
124
|
+
})}
|
|
125
|
+
description={m["admin-ai_prompts_rollback_confirm_description"]({
|
|
126
|
+
version: rollbackTarget ?? 0,
|
|
127
|
+
})}
|
|
128
|
+
cancelLabel={m.common_common_cancel()}
|
|
129
|
+
confirmLabel={m["admin-ai_prompts_rollback_confirm_action"]()}
|
|
130
|
+
confirmDisabled={rollbackPending}
|
|
131
|
+
onConfirm={() => {
|
|
132
|
+
if (rollbackTarget !== null) onRollback(rollbackTarget)
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
155
135
|
</Collapsible>
|
|
156
136
|
)
|
|
157
137
|
}
|
|
@@ -17,9 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
// apps/admin/src/components/ai-provider/model-list-editor.tsx
|
|
19
19
|
import { Plus, Trash2 } from "lucide-react"
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { Input } from "@/components/ui/input"
|
|
20
|
+
import { Button } from "@allbluecn/ui/button"
|
|
21
|
+
import { Input } from "@allbluecn/ui/input"
|
|
23
22
|
import { cn } from "@/lib/utils"
|
|
24
23
|
import {
|
|
25
24
|
MODEL_CAPABILITY_OPTIONS,
|
|
@@ -21,7 +21,7 @@ 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"
|
|
24
|
-
import { toast } from '
|
|
24
|
+
import { toast } from '@allbluecn/ui/toast'
|
|
25
25
|
import {
|
|
26
26
|
ADAPTER_KINDS,
|
|
27
27
|
PROVIDER_CATEGORIES,
|
|
@@ -29,17 +29,17 @@ import {
|
|
|
29
29
|
type AdapterKind,
|
|
30
30
|
type ProviderCategory,
|
|
31
31
|
} from "@allbluecn/kernel"
|
|
32
|
-
import { Button } from "
|
|
33
|
-
import { Input } from "
|
|
34
|
-
import { Textarea } from "
|
|
35
|
-
import { Switch } from "
|
|
32
|
+
import { Button } from "@allbluecn/ui/button"
|
|
33
|
+
import { Input } from "@allbluecn/ui/input"
|
|
34
|
+
import { Textarea } from "@allbluecn/ui/textarea"
|
|
35
|
+
import { Switch } from "@allbluecn/ui/switch"
|
|
36
36
|
import {
|
|
37
37
|
Select,
|
|
38
38
|
SelectContent,
|
|
39
39
|
SelectItem,
|
|
40
40
|
SelectTrigger,
|
|
41
41
|
SelectValue,
|
|
42
|
-
} from "
|
|
42
|
+
} from "@allbluecn/ui/select"
|
|
43
43
|
import {
|
|
44
44
|
Field,
|
|
45
45
|
FieldContent,
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
FieldSet,
|
|
50
50
|
FieldLegend,
|
|
51
51
|
FieldSeparator,
|
|
52
|
-
} from "
|
|
52
|
+
} from "@allbluecn/ui/field"
|
|
53
53
|
import { createAiProviderAdminFn } from "@/server/serverFns/ai-provider/create"
|
|
54
54
|
import { updateAiProviderAdminFn } from "@/server/serverFns/ai-provider/update"
|
|
55
55
|
import { ModelListEditor } from "./model-list-editor"
|
|
@@ -21,22 +21,13 @@ 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, RefreshCw, Trash2, Users } from "lucide-react"
|
|
24
|
-
import { toast } from '
|
|
24
|
+
import { toast } from '@allbluecn/ui/toast'
|
|
25
25
|
import { ADAPTER_KINDS, PROVIDER_BADGE_STYLES, type AiProviderRow } from "@allbluecn/kernel"
|
|
26
|
-
import { Button } from "
|
|
27
|
-
import { Switch } from "
|
|
28
|
-
import { Badge } from "
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
AlertDialogAction,
|
|
32
|
-
AlertDialogCancel,
|
|
33
|
-
AlertDialogContent,
|
|
34
|
-
AlertDialogDescription,
|
|
35
|
-
AlertDialogFooter,
|
|
36
|
-
AlertDialogHeader,
|
|
37
|
-
AlertDialogTitle,
|
|
38
|
-
} from "@/components/ui/alert-dialog"
|
|
39
|
-
import { DataTable, type AdminColumnDef } from "@/components/data-table/data-table"
|
|
26
|
+
import { Button } from "@allbluecn/ui/button"
|
|
27
|
+
import { Switch } from "@allbluecn/ui/switch"
|
|
28
|
+
import { Badge } from "@allbluecn/ui/badge"
|
|
29
|
+
import { ConfirmDialog } from "@allbluecn/ui/components/confirm-dialog"
|
|
30
|
+
import { DataTable, type AdminColumnDef } from "@allbluecn/ui/data-table"
|
|
40
31
|
import { ProviderIcon } from "./provider-icon"
|
|
41
32
|
import { updateAiProviderAdminFn } from "@/server/serverFns/ai-provider/update"
|
|
42
33
|
import { deleteAiProviderAdminFn } from "@/server/serverFns/ai-provider/delete"
|
|
@@ -245,33 +236,19 @@ export function ProviderTable({ data }: { data: AiProviderRow[] }) {
|
|
|
245
236
|
</div>
|
|
246
237
|
<DataTable columns={columns} data={data} />
|
|
247
238
|
|
|
248
|
-
<
|
|
239
|
+
<ConfirmDialog
|
|
249
240
|
open={!!pendingDelete}
|
|
250
241
|
onOpenChange={(open: boolean) => !open && setPendingDelete(null)}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
<AlertDialogFooter>
|
|
262
|
-
<AlertDialogCancel>{m.common_common_cancel()}</AlertDialogCancel>
|
|
263
|
-
<AlertDialogAction
|
|
264
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
265
|
-
onClick={() =>
|
|
266
|
-
pendingDelete && deleteMutation.mutate(pendingDelete.id)
|
|
267
|
-
}
|
|
268
|
-
disabled={deleteMutation.isPending}
|
|
269
|
-
>
|
|
270
|
-
{m['admin-ai_table_deleteConfirm_confirm']()}
|
|
271
|
-
</AlertDialogAction>
|
|
272
|
-
</AlertDialogFooter>
|
|
273
|
-
</AlertDialogContent>
|
|
274
|
-
</AlertDialog>
|
|
242
|
+
title={m['admin-ai_table_deleteConfirm_title']({ name: pendingDelete?.displayName ?? '' })}
|
|
243
|
+
description={m['admin-ai_table_deleteConfirm_description']()}
|
|
244
|
+
cancelLabel={m.common_common_cancel()}
|
|
245
|
+
confirmLabel={m['admin-ai_table_deleteConfirm_confirm']()}
|
|
246
|
+
confirmClassName="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
247
|
+
confirmDisabled={deleteMutation.isPending}
|
|
248
|
+
onConfirm={() =>
|
|
249
|
+
pendingDelete && deleteMutation.mutate(pendingDelete.id)
|
|
250
|
+
}
|
|
251
|
+
/>
|
|
275
252
|
</div>
|
|
276
253
|
)
|
|
277
254
|
}
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
import { LogOut,
|
|
18
|
+
import { LogOut, Mail, Users, BarChart3, Shield, Settings, ArrowRight, ArrowLeft, Target } from "lucide-react";
|
|
19
19
|
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { useState } from "react";
|
|
21
21
|
import { signIn, signOut } from "@/lib/auth-client";
|
|
22
|
-
import { Button } from "
|
|
23
|
-
import { Checkbox } from "
|
|
24
|
-
import { Field, FieldLabel } from "
|
|
25
|
-
import { Card, CardContent } from "
|
|
26
|
-
import { toast } from '
|
|
27
|
-
import { UserAvatar } from "@allbluecn/
|
|
22
|
+
import { Button } from "@allbluecn/ui/button";
|
|
23
|
+
import { Checkbox } from "@allbluecn/ui/checkbox";
|
|
24
|
+
import { Field, FieldLabel } from "@allbluecn/ui/field";
|
|
25
|
+
import { Card, CardContent } from "@allbluecn/ui/card";
|
|
26
|
+
import { toast } from '@allbluecn/ui/toast';
|
|
27
|
+
import { UserAvatar } from "@allbluecn/ui/components/user-avatar";
|
|
28
28
|
import type { AvatarConfig, ShapeType } from "@allbluecn/kernel";
|
|
29
29
|
import { AuthLayout, AuthLayoutLeft, AuthLayoutRight } from "@/components/auth/auth-layout";
|
|
30
30
|
import { AuthHeader } from "@/components/auth/auth-header";
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
import { Input } from '
|
|
20
|
-
import { Label } from '
|
|
19
|
+
import { Input } from '@allbluecn/ui/input'
|
|
20
|
+
import { Label } from '@allbluecn/ui/label'
|
|
21
21
|
import type { LucideIcon } from 'lucide-react'
|
|
22
22
|
|
|
23
23
|
interface IconInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import { useState } from 'react'
|
|
19
19
|
import { m } from '@/paraglide/messages.js'
|
|
20
|
-
import { Input } from '
|
|
21
|
-
import { Label } from '
|
|
20
|
+
import { Input } from '@allbluecn/ui/input'
|
|
21
|
+
import { Label } from '@allbluecn/ui/label'
|
|
22
22
|
import { Lock } from 'lucide-react'
|
|
23
23
|
|
|
24
24
|
interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
@@ -17,28 +17,28 @@
|
|
|
17
17
|
|
|
18
18
|
import { useState, useEffect } from 'react'
|
|
19
19
|
import { m } from '@/paraglide/messages.js'
|
|
20
|
-
import { Button } from '
|
|
21
|
-
import { Input } from '
|
|
22
|
-
import { Label } from '
|
|
23
|
-
import { Textarea } from '
|
|
24
|
-
import { Switch } from '
|
|
20
|
+
import { Button } from '@allbluecn/ui/button'
|
|
21
|
+
import { Input } from '@allbluecn/ui/input'
|
|
22
|
+
import { Label } from '@allbluecn/ui/label'
|
|
23
|
+
import { Textarea } from '@allbluecn/ui/textarea'
|
|
24
|
+
import { Switch } from '@allbluecn/ui/switch'
|
|
25
25
|
import {
|
|
26
26
|
Select,
|
|
27
27
|
SelectContent,
|
|
28
28
|
SelectItem,
|
|
29
29
|
SelectTrigger,
|
|
30
30
|
SelectValue,
|
|
31
|
-
} from '
|
|
31
|
+
} from '@allbluecn/ui/select'
|
|
32
32
|
import {
|
|
33
33
|
Dialog,
|
|
34
34
|
DialogContent,
|
|
35
35
|
DialogFooter,
|
|
36
36
|
DialogHeader,
|
|
37
37
|
DialogTitle,
|
|
38
|
-
} from '
|
|
39
|
-
import {
|
|
38
|
+
} from '@allbluecn/ui/dialog'
|
|
39
|
+
import { ComponentPreview } from './component-preview'
|
|
40
40
|
import { createComponentFn, updateComponentFn } from '@/server/serverFns/components/mutations'
|
|
41
|
-
import { toast } from '
|
|
41
|
+
import { toast } from '@allbluecn/ui/toast'
|
|
42
42
|
import type { ComponentItem } from './component-table'
|
|
43
43
|
|
|
44
44
|
const CATEGORY_KEYS = ['base', 'block', 'template'] as const
|
|
@@ -153,19 +153,11 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
|
|
|
153
153
|
if (!component) {
|
|
154
154
|
return <span className="text-xs text-muted-foreground">{m.common_component_form_previewUnavailable()}</span>
|
|
155
155
|
}
|
|
156
|
-
const config = getComponent(component.prototypeKey ?? '')
|
|
157
|
-
const Comp = config?.component
|
|
158
|
-
if (Comp) {
|
|
159
|
-
return (
|
|
160
|
-
<div className="pointer-events-none flex h-full w-full items-center justify-center overflow-hidden p-3">
|
|
161
|
-
<Comp props={propsState} style={(component.defaultStyle as Record<string, any>) ?? {}} />
|
|
162
|
-
</div>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
156
|
return (
|
|
166
157
|
<ComponentPreview
|
|
167
158
|
type={component.type}
|
|
168
159
|
name={component.name}
|
|
160
|
+
prototypeKey={component.prototypeKey}
|
|
169
161
|
defaultProps={propsState}
|
|
170
162
|
defaultStyle={component.defaultStyle as Record<string, any>}
|
|
171
163
|
size="md"
|