stagent 0.1.0
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/LICENSE +191 -0
- package/README.md +399 -0
- package/components.json +21 -0
- package/dist/cli.js +171 -0
- package/drizzle.config.ts +12 -0
- package/next.config.mjs +15 -0
- package/package.json +114 -0
- package/postcss.config.mjs +8 -0
- package/public/icon-512.png +0 -0
- package/public/icon.svg +13 -0
- package/public/readme/home-workspace.png +0 -0
- package/public/readme/inbox-approvals.png +0 -0
- package/public/readme/workflow-blueprints.png +0 -0
- package/public/stagent-s-128.png +0 -0
- package/public/stagent-s-64.png +0 -0
- package/src/app/api/blueprints/[id]/instantiate/route.ts +27 -0
- package/src/app/api/blueprints/[id]/route.ts +39 -0
- package/src/app/api/blueprints/import/route.ts +68 -0
- package/src/app/api/blueprints/route.ts +29 -0
- package/src/app/api/command-palette/recent/route.ts +31 -0
- package/src/app/api/data/clear/route.ts +22 -0
- package/src/app/api/data/seed/route.ts +22 -0
- package/src/app/api/documents/[id]/file/route.ts +44 -0
- package/src/app/api/documents/[id]/route.ts +123 -0
- package/src/app/api/documents/route.ts +59 -0
- package/src/app/api/logs/stream/route.ts +101 -0
- package/src/app/api/notifications/[id]/route.ts +36 -0
- package/src/app/api/notifications/mark-all-read/route.ts +13 -0
- package/src/app/api/notifications/pending-approvals/route.ts +10 -0
- package/src/app/api/notifications/pending-approvals/stream/route.ts +101 -0
- package/src/app/api/notifications/route.ts +34 -0
- package/src/app/api/permissions/route.ts +46 -0
- package/src/app/api/profiles/[id]/route.ts +79 -0
- package/src/app/api/profiles/[id]/test/route.ts +42 -0
- package/src/app/api/profiles/import/route.ts +108 -0
- package/src/app/api/profiles/route.ts +50 -0
- package/src/app/api/projects/[id]/route.ts +72 -0
- package/src/app/api/projects/route.ts +53 -0
- package/src/app/api/schedules/[id]/route.ts +185 -0
- package/src/app/api/schedules/route.ts +117 -0
- package/src/app/api/settings/budgets/route.ts +24 -0
- package/src/app/api/settings/openai/route.ts +24 -0
- package/src/app/api/settings/route.ts +21 -0
- package/src/app/api/settings/test/route.ts +26 -0
- package/src/app/api/tasks/[id]/cancel/route.ts +21 -0
- package/src/app/api/tasks/[id]/execute/route.ts +90 -0
- package/src/app/api/tasks/[id]/logs/route.ts +95 -0
- package/src/app/api/tasks/[id]/output/route.ts +47 -0
- package/src/app/api/tasks/[id]/respond/route.ts +64 -0
- package/src/app/api/tasks/[id]/resume/route.ts +76 -0
- package/src/app/api/tasks/[id]/route.ts +77 -0
- package/src/app/api/tasks/assist/route.ts +35 -0
- package/src/app/api/tasks/route.ts +82 -0
- package/src/app/api/uploads/[id]/route.ts +81 -0
- package/src/app/api/uploads/cleanup/route.ts +7 -0
- package/src/app/api/uploads/route.ts +66 -0
- package/src/app/api/workflows/[id]/execute/route.ts +82 -0
- package/src/app/api/workflows/[id]/route.ts +133 -0
- package/src/app/api/workflows/[id]/status/route.ts +54 -0
- package/src/app/api/workflows/[id]/steps/[stepId]/retry/route.ts +22 -0
- package/src/app/api/workflows/route.ts +61 -0
- package/src/app/apple-icon.tsx +31 -0
- package/src/app/costs/page.tsx +256 -0
- package/src/app/dashboard/page.tsx +44 -0
- package/src/app/documents/[id]/page.tsx +46 -0
- package/src/app/documents/page.tsx +45 -0
- package/src/app/error.tsx +26 -0
- package/src/app/global-error.tsx +23 -0
- package/src/app/globals.css +733 -0
- package/src/app/icon.tsx +30 -0
- package/src/app/inbox/loading.tsx +15 -0
- package/src/app/inbox/page.tsx +35 -0
- package/src/app/layout.tsx +78 -0
- package/src/app/manifest.ts +32 -0
- package/src/app/monitor/page.tsx +37 -0
- package/src/app/page.tsx +162 -0
- package/src/app/profiles/[id]/edit/page.tsx +39 -0
- package/src/app/profiles/[id]/page.tsx +33 -0
- package/src/app/profiles/new/page.tsx +22 -0
- package/src/app/profiles/page.tsx +19 -0
- package/src/app/projects/[id]/page.tsx +134 -0
- package/src/app/projects/loading.tsx +17 -0
- package/src/app/projects/page.tsx +32 -0
- package/src/app/schedules/[id]/page.tsx +47 -0
- package/src/app/schedules/page.tsx +18 -0
- package/src/app/settings/loading.tsx +24 -0
- package/src/app/settings/page.tsx +27 -0
- package/src/app/tasks/[id]/page.tsx +45 -0
- package/src/app/tasks/new/page.tsx +27 -0
- package/src/app/workflows/[id]/edit/page.tsx +66 -0
- package/src/app/workflows/[id]/page.tsx +37 -0
- package/src/app/workflows/blueprints/[id]/page.tsx +40 -0
- package/src/app/workflows/blueprints/new/page.tsx +20 -0
- package/src/app/workflows/blueprints/page.tsx +11 -0
- package/src/app/workflows/new/page.tsx +36 -0
- package/src/app/workflows/page.tsx +18 -0
- package/src/components/charts/donut-ring.tsx +64 -0
- package/src/components/charts/mini-bar.tsx +75 -0
- package/src/components/charts/sparkline.tsx +107 -0
- package/src/components/costs/cost-dashboard.tsx +877 -0
- package/src/components/costs/cost-filters.tsx +179 -0
- package/src/components/dashboard/activity-feed.tsx +95 -0
- package/src/components/dashboard/greeting.tsx +30 -0
- package/src/components/dashboard/priority-queue.tsx +79 -0
- package/src/components/dashboard/quick-actions.tsx +62 -0
- package/src/components/dashboard/recent-projects.tsx +79 -0
- package/src/components/dashboard/stats-cards.tsx +114 -0
- package/src/components/documents/document-browser.tsx +235 -0
- package/src/components/documents/document-detail-view.tsx +367 -0
- package/src/components/documents/document-grid.tsx +78 -0
- package/src/components/documents/document-preview.tsx +68 -0
- package/src/components/documents/document-table.tsx +119 -0
- package/src/components/documents/document-upload-dialog.tsx +153 -0
- package/src/components/documents/types.ts +6 -0
- package/src/components/documents/utils.ts +57 -0
- package/src/components/monitoring/connection-indicator.tsx +14 -0
- package/src/components/monitoring/log-entry.tsx +79 -0
- package/src/components/monitoring/log-filters.tsx +57 -0
- package/src/components/monitoring/log-stream.tsx +144 -0
- package/src/components/monitoring/monitor-overview-wrapper.tsx +64 -0
- package/src/components/monitoring/monitor-overview.tsx +119 -0
- package/src/components/notifications/failure-action.tsx +38 -0
- package/src/components/notifications/inbox-list.tsx +165 -0
- package/src/components/notifications/message-response.tsx +196 -0
- package/src/components/notifications/notification-item.tsx +250 -0
- package/src/components/notifications/pending-approval-host.tsx +478 -0
- package/src/components/notifications/permission-action.tsx +37 -0
- package/src/components/notifications/permission-response-actions.tsx +126 -0
- package/src/components/notifications/unread-badge.tsx +35 -0
- package/src/components/profiles/profile-browser.tsx +117 -0
- package/src/components/profiles/profile-card.tsx +78 -0
- package/src/components/profiles/profile-detail-view.tsx +564 -0
- package/src/components/profiles/profile-form-view.tsx +480 -0
- package/src/components/profiles/profile-import-dialog.tsx +113 -0
- package/src/components/projects/project-card.tsx +58 -0
- package/src/components/projects/project-create-dialog.tsx +140 -0
- package/src/components/projects/project-detail.tsx +68 -0
- package/src/components/projects/project-edit-dialog.tsx +219 -0
- package/src/components/projects/project-list.tsx +108 -0
- package/src/components/schedules/schedule-create-dialog.tsx +403 -0
- package/src/components/schedules/schedule-detail-view.tsx +274 -0
- package/src/components/schedules/schedule-list.tsx +242 -0
- package/src/components/schedules/schedule-status-badge.tsx +16 -0
- package/src/components/settings/api-key-form.tsx +141 -0
- package/src/components/settings/auth-config-section.tsx +141 -0
- package/src/components/settings/auth-method-selector.tsx +67 -0
- package/src/components/settings/auth-status-badge.tsx +40 -0
- package/src/components/settings/auth-status-dot.tsx +59 -0
- package/src/components/settings/budget-guardrails-section.tsx +842 -0
- package/src/components/settings/data-management-section.tsx +141 -0
- package/src/components/settings/openai-runtime-section.tsx +104 -0
- package/src/components/settings/permissions-section.tsx +91 -0
- package/src/components/shared/app-sidebar.tsx +123 -0
- package/src/components/shared/card-skeleton.tsx +42 -0
- package/src/components/shared/command-palette.tsx +250 -0
- package/src/components/shared/confirm-dialog.tsx +52 -0
- package/src/components/shared/empty-state.tsx +24 -0
- package/src/components/shared/error-state.tsx +32 -0
- package/src/components/shared/form-section-card.tsx +33 -0
- package/src/components/shared/section-heading.tsx +14 -0
- package/src/components/shared/stagent-logo.tsx +21 -0
- package/src/components/shared/theme-toggle.tsx +46 -0
- package/src/components/tasks/ai-assist-panel.tsx +210 -0
- package/src/components/tasks/content-preview.tsx +89 -0
- package/src/components/tasks/empty-board.tsx +12 -0
- package/src/components/tasks/file-upload.tsx +120 -0
- package/src/components/tasks/kanban-board.tsx +275 -0
- package/src/components/tasks/kanban-column.tsx +75 -0
- package/src/components/tasks/skeleton-board.tsx +21 -0
- package/src/components/tasks/task-attachments.tsx +114 -0
- package/src/components/tasks/task-card.tsx +101 -0
- package/src/components/tasks/task-create-panel.tsx +360 -0
- package/src/components/tasks/task-detail-view.tsx +356 -0
- package/src/components/ui/alert-dialog.tsx +196 -0
- package/src/components/ui/badge.tsx +50 -0
- package/src/components/ui/button.tsx +71 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/popover.tsx +89 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +63 -0
- package/src/components/ui/sonner.tsx +36 -0
- package/src/components/ui/switch.tsx +35 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +57 -0
- package/src/components/workflows/blueprint-editor.tsx +109 -0
- package/src/components/workflows/blueprint-gallery.tsx +155 -0
- package/src/components/workflows/blueprint-preview.tsx +240 -0
- package/src/components/workflows/loop-status-view.tsx +272 -0
- package/src/components/workflows/swarm-dashboard.tsx +185 -0
- package/src/components/workflows/workflow-form-view.tsx +1376 -0
- package/src/components/workflows/workflow-list.tsx +230 -0
- package/src/components/workflows/workflow-status-view.tsx +477 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/instrumentation.ts +7 -0
- package/src/lib/agents/claude-agent.ts +737 -0
- package/src/lib/agents/execution-manager.ts +27 -0
- package/src/lib/agents/profiles/assignment-validation.ts +75 -0
- package/src/lib/agents/profiles/builtins/code-reviewer/SKILL.md +21 -0
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +28 -0
- package/src/lib/agents/profiles/builtins/data-analyst/SKILL.md +25 -0
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/devops-engineer/SKILL.md +34 -0
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/document-writer/SKILL.md +16 -0
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/general/SKILL.md +13 -0
- package/src/lib/agents/profiles/builtins/general/profile.yaml +18 -0
- package/src/lib/agents/profiles/builtins/health-fitness-coach/SKILL.md +34 -0
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/learning-coach/SKILL.md +35 -0
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/SKILL.md +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/researcher/SKILL.md +15 -0
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +34 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/technical-writer/SKILL.md +31 -0
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +29 -0
- package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +23 -0
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +24 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +26 -0
- package/src/lib/agents/profiles/compatibility.ts +109 -0
- package/src/lib/agents/profiles/registry.ts +293 -0
- package/src/lib/agents/profiles/test-runner.ts +18 -0
- package/src/lib/agents/profiles/test-types.ts +20 -0
- package/src/lib/agents/profiles/types.ts +43 -0
- package/src/lib/agents/router.ts +56 -0
- package/src/lib/agents/runtime/catalog.ts +85 -0
- package/src/lib/agents/runtime/claude-sdk.ts +12 -0
- package/src/lib/agents/runtime/claude.ts +370 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +289 -0
- package/src/lib/agents/runtime/index.ts +167 -0
- package/src/lib/agents/runtime/openai-codex.ts +1089 -0
- package/src/lib/agents/runtime/task-assist-types.ts +8 -0
- package/src/lib/agents/runtime/types.ts +30 -0
- package/src/lib/constants/settings.ts +13 -0
- package/src/lib/constants/status-colors.ts +44 -0
- package/src/lib/constants/task-status.ts +49 -0
- package/src/lib/data/clear.ts +63 -0
- package/src/lib/data/seed-data/documents.ts +715 -0
- package/src/lib/data/seed-data/logs.ts +195 -0
- package/src/lib/data/seed-data/notifications.ts +141 -0
- package/src/lib/data/seed-data/profiles.ts +175 -0
- package/src/lib/data/seed-data/projects.ts +61 -0
- package/src/lib/data/seed-data/schedules.ts +108 -0
- package/src/lib/data/seed-data/tasks.ts +341 -0
- package/src/lib/data/seed-data/usage-ledger.ts +130 -0
- package/src/lib/data/seed-data/workflows.ts +213 -0
- package/src/lib/data/seed.ts +129 -0
- package/src/lib/db/index.ts +221 -0
- package/src/lib/db/migrations/0000_aromatic_gargoyle.sql +59 -0
- package/src/lib/db/migrations/0001_first_iron_patriot.sql +6 -0
- package/src/lib/db/migrations/0002_add_resume_count.sql +1 -0
- package/src/lib/db/migrations/0003_add_settings.sql +5 -0
- package/src/lib/db/migrations/0004_add_documents.sql +20 -0
- package/src/lib/db/migrations/0005_add_document_preprocessing.sql +4 -0
- package/src/lib/db/migrations/0006_add_agent_profile.sql +2 -0
- package/src/lib/db/migrations/0007_add_usage_metering_ledger.sql +30 -0
- package/src/lib/db/migrations/0008_add_document_version.sql +1 -0
- package/src/lib/db/migrations/meta/0000_snapshot.json +416 -0
- package/src/lib/db/migrations/meta/0001_snapshot.json +461 -0
- package/src/lib/db/migrations/meta/0002_snapshot.json +469 -0
- package/src/lib/db/migrations/meta/_journal.json +27 -0
- package/src/lib/db/schema.ts +227 -0
- package/src/lib/documents/cleanup.ts +50 -0
- package/src/lib/documents/context-builder.ts +75 -0
- package/src/lib/documents/output-scanner.ts +166 -0
- package/src/lib/documents/processor.ts +120 -0
- package/src/lib/documents/processors/image.ts +21 -0
- package/src/lib/documents/processors/office.ts +36 -0
- package/src/lib/documents/processors/pdf.ts +12 -0
- package/src/lib/documents/processors/spreadsheet.ts +18 -0
- package/src/lib/documents/processors/text.ts +8 -0
- package/src/lib/documents/registry.ts +25 -0
- package/src/lib/notifications/actionable.ts +108 -0
- package/src/lib/notifications/permissions.ts +169 -0
- package/src/lib/queries/chart-data.ts +184 -0
- package/src/lib/schedules/interval-parser.ts +110 -0
- package/src/lib/schedules/scheduler.ts +220 -0
- package/src/lib/settings/auth.ts +98 -0
- package/src/lib/settings/budget-guardrails.ts +590 -0
- package/src/lib/settings/helpers.ts +23 -0
- package/src/lib/settings/openai-auth.ts +80 -0
- package/src/lib/settings/permissions.ts +102 -0
- package/src/lib/usage/ledger.ts +489 -0
- package/src/lib/usage/pricing.ts +68 -0
- package/src/lib/utils/crypto.ts +90 -0
- package/src/lib/utils/format-timestamp.ts +46 -0
- package/src/lib/utils/session-cleanup.ts +26 -0
- package/src/lib/utils/stagent-paths.ts +18 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validators/blueprint.ts +43 -0
- package/src/lib/validators/profile.ts +64 -0
- package/src/lib/validators/project.ts +17 -0
- package/src/lib/validators/settings.ts +57 -0
- package/src/lib/validators/task.ts +30 -0
- package/src/lib/workflows/blueprints/builtins/code-review-pipeline.yaml +72 -0
- package/src/lib/workflows/blueprints/builtins/documentation-generation.yaml +62 -0
- package/src/lib/workflows/blueprints/builtins/investment-research.yaml +81 -0
- package/src/lib/workflows/blueprints/builtins/meal-planning.yaml +73 -0
- package/src/lib/workflows/blueprints/builtins/product-research.yaml +72 -0
- package/src/lib/workflows/blueprints/builtins/research-report.yaml +77 -0
- package/src/lib/workflows/blueprints/builtins/sprint-planning.yaml +77 -0
- package/src/lib/workflows/blueprints/builtins/travel-planning.yaml +80 -0
- package/src/lib/workflows/blueprints/instantiator.ts +131 -0
- package/src/lib/workflows/blueprints/registry.ts +128 -0
- package/src/lib/workflows/blueprints/template.ts +58 -0
- package/src/lib/workflows/blueprints/types.ts +38 -0
- package/src/lib/workflows/definition-validation.ts +121 -0
- package/src/lib/workflows/engine.ts +1113 -0
- package/src/lib/workflows/loop-executor.ts +270 -0
- package/src/lib/workflows/parallel.ts +55 -0
- package/src/lib/workflows/swarm.ts +97 -0
- package/src/lib/workflows/types.ts +112 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
6
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
9
|
+
import {
|
|
10
|
+
Copy,
|
|
11
|
+
Pencil,
|
|
12
|
+
Trash2,
|
|
13
|
+
Play,
|
|
14
|
+
Loader2,
|
|
15
|
+
CheckCircle2,
|
|
16
|
+
XCircle,
|
|
17
|
+
Bot,
|
|
18
|
+
Sparkles,
|
|
19
|
+
Tag,
|
|
20
|
+
User,
|
|
21
|
+
Thermometer,
|
|
22
|
+
Repeat,
|
|
23
|
+
FileOutput,
|
|
24
|
+
Wrench,
|
|
25
|
+
ShieldCheck,
|
|
26
|
+
ShieldX,
|
|
27
|
+
FileCode,
|
|
28
|
+
Cpu,
|
|
29
|
+
} from "lucide-react";
|
|
30
|
+
import { toast } from "sonner";
|
|
31
|
+
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
|
32
|
+
import {
|
|
33
|
+
type AgentRuntimeId,
|
|
34
|
+
DEFAULT_AGENT_RUNTIME,
|
|
35
|
+
listRuntimeCatalog,
|
|
36
|
+
} from "@/lib/agents/runtime/catalog";
|
|
37
|
+
import {
|
|
38
|
+
getProfileRuntimeCompatibility,
|
|
39
|
+
getSupportedRuntimes,
|
|
40
|
+
} from "@/lib/agents/profiles/compatibility";
|
|
41
|
+
import type { AgentProfile } from "@/lib/agents/profiles/types";
|
|
42
|
+
|
|
43
|
+
interface TestResult {
|
|
44
|
+
task: string;
|
|
45
|
+
expectedKeywords: string[];
|
|
46
|
+
foundKeywords: string[];
|
|
47
|
+
missingKeywords: string[];
|
|
48
|
+
passed: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface TestReport {
|
|
52
|
+
profileId: string;
|
|
53
|
+
profileName: string;
|
|
54
|
+
runtimeId: string;
|
|
55
|
+
results: TestResult[];
|
|
56
|
+
totalPassed: number;
|
|
57
|
+
totalFailed: number;
|
|
58
|
+
unsupported?: boolean;
|
|
59
|
+
unsupportedReason?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface ProfileWithBuiltin extends AgentProfile {
|
|
63
|
+
isBuiltin?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ProfileDetailViewProps {
|
|
67
|
+
profileId: string;
|
|
68
|
+
isBuiltin: boolean;
|
|
69
|
+
initialProfile?: AgentProfile;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ProfileDetailView({ profileId, isBuiltin, initialProfile }: ProfileDetailViewProps) {
|
|
73
|
+
const runtimeOptions = listRuntimeCatalog();
|
|
74
|
+
const runtimeLabelMap = new Map(
|
|
75
|
+
runtimeOptions.map((runtime) => [runtime.id, runtime.label])
|
|
76
|
+
);
|
|
77
|
+
const router = useRouter();
|
|
78
|
+
const [profile, setProfile] = useState<ProfileWithBuiltin | null>(initialProfile ?? null);
|
|
79
|
+
const [loaded, setLoaded] = useState(!!initialProfile);
|
|
80
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
81
|
+
const [testReport, setTestReport] = useState<TestReport | null>(null);
|
|
82
|
+
const [runningTests, setRunningTests] = useState(false);
|
|
83
|
+
const [selectedTestRuntime, setSelectedTestRuntime] =
|
|
84
|
+
useState<AgentRuntimeId>(DEFAULT_AGENT_RUNTIME);
|
|
85
|
+
|
|
86
|
+
const refresh = useCallback(async () => {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(`/api/profiles/${profileId}`);
|
|
89
|
+
if (res.ok) {
|
|
90
|
+
setProfile(await res.json());
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// silent
|
|
94
|
+
}
|
|
95
|
+
setLoaded(true);
|
|
96
|
+
}, [profileId]);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
// Skip initial fetch if server provided data — only refresh on mutations
|
|
100
|
+
if (!initialProfile) refresh();
|
|
101
|
+
}, [refresh, initialProfile]);
|
|
102
|
+
|
|
103
|
+
async function handleDelete() {
|
|
104
|
+
setConfirmDelete(false);
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(`/api/profiles/${profileId}`, {
|
|
107
|
+
method: "DELETE",
|
|
108
|
+
});
|
|
109
|
+
if (res.ok) {
|
|
110
|
+
toast.success("Profile deleted");
|
|
111
|
+
router.push("/profiles");
|
|
112
|
+
} else {
|
|
113
|
+
const data = await res.json().catch(() => null);
|
|
114
|
+
toast.error(data?.error ?? "Failed to delete profile");
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
toast.error("Network error");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function handleRunTests() {
|
|
122
|
+
setRunningTests(true);
|
|
123
|
+
setTestReport(null);
|
|
124
|
+
try {
|
|
125
|
+
const res = await fetch(`/api/profiles/${profileId}/test`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { "Content-Type": "application/json" },
|
|
128
|
+
body: JSON.stringify({ runtimeId: selectedTestRuntime }),
|
|
129
|
+
});
|
|
130
|
+
if (res.ok) {
|
|
131
|
+
const report: TestReport = await res.json();
|
|
132
|
+
setTestReport(report);
|
|
133
|
+
if (report.unsupported) {
|
|
134
|
+
toast.warning(report.unsupportedReason ?? "This runtime cannot test the selected profile");
|
|
135
|
+
} else if (report.totalFailed === 0) {
|
|
136
|
+
toast.success(`All ${report.totalPassed} tests passed`);
|
|
137
|
+
} else {
|
|
138
|
+
toast.warning(`${report.totalPassed} passed, ${report.totalFailed} failed`);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
const data = await res.json().catch(() => null);
|
|
142
|
+
toast.error(data?.error ?? "Failed to run tests");
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
toast.error("Network error running tests");
|
|
146
|
+
} finally {
|
|
147
|
+
setRunningTests(false);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!loaded) {
|
|
152
|
+
return (
|
|
153
|
+
<div className="space-y-4">
|
|
154
|
+
<Skeleton className="h-8 w-64" />
|
|
155
|
+
<Skeleton className="h-32 w-full" />
|
|
156
|
+
<Skeleton className="h-48 w-full" />
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!profile) {
|
|
162
|
+
return <p className="text-muted-foreground">Profile not found.</p>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const DomainIcon = profile.domain === "work" ? Bot : Sparkles;
|
|
166
|
+
const lineCount = profile.skillMd ? profile.skillMd.split("\n").length : 0;
|
|
167
|
+
const toolCount = (profile.allowedTools?.length ?? 0);
|
|
168
|
+
const hasPolicy = profile.canUseToolPolicy &&
|
|
169
|
+
((profile.canUseToolPolicy.autoApprove?.length ?? 0) > 0 ||
|
|
170
|
+
(profile.canUseToolPolicy.autoDeny?.length ?? 0) > 0);
|
|
171
|
+
const testTotal = testReport ? testReport.totalPassed + testReport.totalFailed : 0;
|
|
172
|
+
const runtimeCompatibility = runtimeOptions.map((runtime) => ({
|
|
173
|
+
runtime,
|
|
174
|
+
compatibility: getProfileRuntimeCompatibility(profile, runtime.id),
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div className="space-y-6" aria-live="polite">
|
|
179
|
+
{/* Header */}
|
|
180
|
+
<div className="flex items-center justify-between">
|
|
181
|
+
<div className="flex items-center gap-2">
|
|
182
|
+
<h1 className="text-2xl font-bold">{profile.name}</h1>
|
|
183
|
+
<Badge variant={profile.domain === "work" ? "default" : "secondary"}>
|
|
184
|
+
{profile.domain}
|
|
185
|
+
</Badge>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="flex items-center gap-2">
|
|
188
|
+
{!isBuiltin && (
|
|
189
|
+
<Button
|
|
190
|
+
variant="outline"
|
|
191
|
+
size="sm"
|
|
192
|
+
onClick={() => router.push(`/profiles/${profileId}/edit`)}
|
|
193
|
+
>
|
|
194
|
+
<Pencil className="h-3.5 w-3.5 mr-1" />
|
|
195
|
+
Edit
|
|
196
|
+
</Button>
|
|
197
|
+
)}
|
|
198
|
+
<Button
|
|
199
|
+
variant="outline"
|
|
200
|
+
size="sm"
|
|
201
|
+
onClick={() => router.push(`/profiles/${profileId}/edit?duplicate=true`)}
|
|
202
|
+
>
|
|
203
|
+
<Copy className="h-3.5 w-3.5 mr-1" />
|
|
204
|
+
Duplicate
|
|
205
|
+
</Button>
|
|
206
|
+
{!isBuiltin && (
|
|
207
|
+
<Button
|
|
208
|
+
variant="outline"
|
|
209
|
+
size="sm"
|
|
210
|
+
className="text-destructive"
|
|
211
|
+
onClick={() => setConfirmDelete(true)}
|
|
212
|
+
>
|
|
213
|
+
<Trash2 className="h-3.5 w-3.5 mr-1" />
|
|
214
|
+
Delete
|
|
215
|
+
</Button>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Bento Grid: Identity + Configuration + Runtime Coverage + Tools & Policy */}
|
|
221
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
222
|
+
{/* Identity Card */}
|
|
223
|
+
<Card className="surface-card">
|
|
224
|
+
<CardHeader className="pb-2">
|
|
225
|
+
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
226
|
+
<DomainIcon className="h-4 w-4 text-muted-foreground" />
|
|
227
|
+
Identity
|
|
228
|
+
</CardTitle>
|
|
229
|
+
</CardHeader>
|
|
230
|
+
<CardContent className="space-y-3">
|
|
231
|
+
<p className="text-sm text-muted-foreground">{profile.description}</p>
|
|
232
|
+
{profile.tags && profile.tags.length > 0 && (
|
|
233
|
+
<div className="flex flex-wrap gap-1">
|
|
234
|
+
{profile.tags.map((tag) => (
|
|
235
|
+
<Badge key={tag} variant="secondary" className="text-xs">
|
|
236
|
+
{tag}
|
|
237
|
+
</Badge>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
{(profile.version || profile.author) && (
|
|
242
|
+
<div className="flex items-center gap-3 border-t border-border/60 pt-1 text-xs text-muted-foreground">
|
|
243
|
+
{profile.version && (
|
|
244
|
+
<span className="flex items-center gap-1">
|
|
245
|
+
<Tag className="h-3 w-3" />v{profile.version}
|
|
246
|
+
</span>
|
|
247
|
+
)}
|
|
248
|
+
{profile.author && (
|
|
249
|
+
<span className="flex items-center gap-1">
|
|
250
|
+
<User className="h-3 w-3" />{profile.author}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
</CardContent>
|
|
256
|
+
</Card>
|
|
257
|
+
|
|
258
|
+
{/* Configuration Card */}
|
|
259
|
+
<Card className="surface-card">
|
|
260
|
+
<CardHeader className="pb-2">
|
|
261
|
+
<CardTitle className="text-sm font-medium">Configuration</CardTitle>
|
|
262
|
+
</CardHeader>
|
|
263
|
+
<CardContent className="space-y-3">
|
|
264
|
+
{/* Temperature Gauge */}
|
|
265
|
+
{profile.temperature !== undefined && (
|
|
266
|
+
<div className="flex items-center gap-2">
|
|
267
|
+
<Thermometer className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
268
|
+
<span className="text-xs text-muted-foreground w-20">Temperature</span>
|
|
269
|
+
<div className="flex-1 h-1.5 rounded-full bg-muted">
|
|
270
|
+
<div
|
|
271
|
+
className="h-full rounded-full bg-primary"
|
|
272
|
+
style={{ width: `${(profile.temperature / 2) * 100}%` }}
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
<span className="text-xs font-medium w-8 text-right">{profile.temperature}</span>
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
{/* Max Turns */}
|
|
279
|
+
{profile.maxTurns !== undefined && (
|
|
280
|
+
<div className="flex items-center gap-2">
|
|
281
|
+
<Repeat className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
282
|
+
<span className="text-xs text-muted-foreground">Max Turns</span>
|
|
283
|
+
<span className="text-xl font-bold ml-auto">{profile.maxTurns}</span>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
{/* Output Format */}
|
|
287
|
+
{profile.outputFormat && (
|
|
288
|
+
<div className="flex items-center gap-2">
|
|
289
|
+
<FileOutput className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
290
|
+
<span className="text-xs text-muted-foreground">Output</span>
|
|
291
|
+
<Badge variant="outline" className="text-xs ml-auto">
|
|
292
|
+
{profile.outputFormat}
|
|
293
|
+
</Badge>
|
|
294
|
+
</div>
|
|
295
|
+
)}
|
|
296
|
+
{!profile.temperature && !profile.maxTurns && !profile.outputFormat && (
|
|
297
|
+
<p className="text-sm text-muted-foreground">Default configuration</p>
|
|
298
|
+
)}
|
|
299
|
+
</CardContent>
|
|
300
|
+
</Card>
|
|
301
|
+
|
|
302
|
+
{/* Runtime Coverage */}
|
|
303
|
+
<Card className="surface-card">
|
|
304
|
+
<CardHeader className="pb-2">
|
|
305
|
+
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
306
|
+
<Cpu className="h-4 w-4 text-muted-foreground" />
|
|
307
|
+
Runtime Coverage
|
|
308
|
+
</CardTitle>
|
|
309
|
+
</CardHeader>
|
|
310
|
+
<CardContent className="space-y-3">
|
|
311
|
+
{runtimeCompatibility.map(({ runtime, compatibility }) => (
|
|
312
|
+
<div
|
|
313
|
+
key={runtime.id}
|
|
314
|
+
className="surface-card-muted rounded-lg border border-border/60 p-3"
|
|
315
|
+
>
|
|
316
|
+
<div className="flex items-center justify-between gap-3">
|
|
317
|
+
<span className="text-sm font-medium">{runtime.label}</span>
|
|
318
|
+
<Badge
|
|
319
|
+
variant={compatibility.supported ? "secondary" : "outline"}
|
|
320
|
+
className={
|
|
321
|
+
compatibility.supported
|
|
322
|
+
? "bg-status-completed/10 text-status-completed"
|
|
323
|
+
: "text-muted-foreground"
|
|
324
|
+
}
|
|
325
|
+
>
|
|
326
|
+
{compatibility.supported ? "Supported" : "Unsupported"}
|
|
327
|
+
</Badge>
|
|
328
|
+
</div>
|
|
329
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
330
|
+
{compatibility.supported
|
|
331
|
+
? compatibility.instructionsSource === "runtime-override"
|
|
332
|
+
? "Uses runtime-specific instructions"
|
|
333
|
+
: "Uses shared SKILL.md instructions"
|
|
334
|
+
: "Blocked before execution"}
|
|
335
|
+
</p>
|
|
336
|
+
</div>
|
|
337
|
+
))}
|
|
338
|
+
<p className="text-xs text-muted-foreground">
|
|
339
|
+
{`Supports ${getSupportedRuntimes(profile)
|
|
340
|
+
.map((runtimeId) => runtimeLabelMap.get(runtimeId) ?? runtimeId)
|
|
341
|
+
.join(", ")}`}
|
|
342
|
+
</p>
|
|
343
|
+
</CardContent>
|
|
344
|
+
</Card>
|
|
345
|
+
|
|
346
|
+
{/* Tools & Policy Card */}
|
|
347
|
+
<Card className="surface-card md:col-span-2 lg:col-span-1">
|
|
348
|
+
<CardHeader className="pb-2">
|
|
349
|
+
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
350
|
+
<Wrench className="h-4 w-4 text-muted-foreground" />
|
|
351
|
+
Tools & Policy
|
|
352
|
+
{toolCount > 0 && (
|
|
353
|
+
<Badge variant="secondary" className="text-xs ml-auto">{toolCount}</Badge>
|
|
354
|
+
)}
|
|
355
|
+
</CardTitle>
|
|
356
|
+
</CardHeader>
|
|
357
|
+
<CardContent className="space-y-3">
|
|
358
|
+
{/* Allowed Tools */}
|
|
359
|
+
{profile.allowedTools && profile.allowedTools.length > 0 ? (
|
|
360
|
+
<div className="flex flex-wrap gap-1">
|
|
361
|
+
{profile.allowedTools.map((tool) => (
|
|
362
|
+
<Badge key={tool} variant="outline" className="text-xs">
|
|
363
|
+
{tool}
|
|
364
|
+
</Badge>
|
|
365
|
+
))}
|
|
366
|
+
</div>
|
|
367
|
+
) : (
|
|
368
|
+
<p className="text-xs text-muted-foreground">All tools allowed</p>
|
|
369
|
+
)}
|
|
370
|
+
|
|
371
|
+
{/* Auto-approve */}
|
|
372
|
+
{hasPolicy && profile.canUseToolPolicy?.autoApprove && profile.canUseToolPolicy.autoApprove.length > 0 && (
|
|
373
|
+
<div>
|
|
374
|
+
<div className="flex items-center gap-1 text-xs text-muted-foreground mb-1">
|
|
375
|
+
<ShieldCheck className="h-3 w-3 text-status-completed" />
|
|
376
|
+
<span>Auto-approve</span>
|
|
377
|
+
</div>
|
|
378
|
+
<div className="flex flex-wrap gap-1">
|
|
379
|
+
{profile.canUseToolPolicy.autoApprove.map((tool) => (
|
|
380
|
+
<Badge
|
|
381
|
+
key={tool}
|
|
382
|
+
variant="outline"
|
|
383
|
+
className="border-status-completed/30 bg-status-completed/10 text-xs text-status-completed"
|
|
384
|
+
>
|
|
385
|
+
{tool}
|
|
386
|
+
</Badge>
|
|
387
|
+
))}
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
)}
|
|
391
|
+
|
|
392
|
+
{/* Auto-deny */}
|
|
393
|
+
{hasPolicy && profile.canUseToolPolicy?.autoDeny && profile.canUseToolPolicy.autoDeny.length > 0 && (
|
|
394
|
+
<div>
|
|
395
|
+
<div className="flex items-center gap-1 text-xs text-muted-foreground mb-1">
|
|
396
|
+
<ShieldX className="h-3 w-3 text-status-failed" />
|
|
397
|
+
<span>Auto-deny</span>
|
|
398
|
+
</div>
|
|
399
|
+
<div className="flex flex-wrap gap-1">
|
|
400
|
+
{profile.canUseToolPolicy.autoDeny.map((tool) => (
|
|
401
|
+
<Badge
|
|
402
|
+
key={tool}
|
|
403
|
+
variant="outline"
|
|
404
|
+
className="border-status-failed/30 bg-status-failed/10 text-xs text-status-failed"
|
|
405
|
+
>
|
|
406
|
+
{tool}
|
|
407
|
+
</Badge>
|
|
408
|
+
))}
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
)}
|
|
412
|
+
|
|
413
|
+
{!toolCount && !hasPolicy && (
|
|
414
|
+
<p className="text-xs text-muted-foreground">No tool restrictions</p>
|
|
415
|
+
)}
|
|
416
|
+
</CardContent>
|
|
417
|
+
</Card>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
{/* Bottom row: SKILL.md + Tests */}
|
|
421
|
+
<div className="grid grid-cols-1 lg:grid-cols-[2fr_1fr] gap-4">
|
|
422
|
+
{/* SKILL.md — collapsible */}
|
|
423
|
+
{profile.skillMd && (
|
|
424
|
+
<details className="group" open>
|
|
425
|
+
<summary className="surface-card flex cursor-pointer list-none items-center gap-2 rounded-lg p-3 text-sm font-medium transition-colors hover:bg-accent/50">
|
|
426
|
+
<FileCode className="h-4 w-4 text-muted-foreground" />
|
|
427
|
+
<span>SKILL.md</span>
|
|
428
|
+
<Badge variant="secondary" className="text-xs ml-auto">
|
|
429
|
+
{lineCount} lines
|
|
430
|
+
</Badge>
|
|
431
|
+
<span className="text-muted-foreground text-xs group-open:rotate-90 transition-transform">▶</span>
|
|
432
|
+
</summary>
|
|
433
|
+
<div className="surface-panel mt-2 rounded-lg p-4">
|
|
434
|
+
<pre className="surface-scroll max-h-64 overflow-auto whitespace-pre-wrap rounded-lg p-4 text-xs">
|
|
435
|
+
{profile.skillMd}
|
|
436
|
+
</pre>
|
|
437
|
+
</div>
|
|
438
|
+
</details>
|
|
439
|
+
)}
|
|
440
|
+
|
|
441
|
+
{/* Tests */}
|
|
442
|
+
{profile.tests && profile.tests.length > 0 && (
|
|
443
|
+
<Card className="surface-card">
|
|
444
|
+
<CardHeader className="pb-2">
|
|
445
|
+
<div className="flex items-center justify-between">
|
|
446
|
+
<CardTitle className="text-sm font-medium">
|
|
447
|
+
Tests ({profile.tests.length})
|
|
448
|
+
</CardTitle>
|
|
449
|
+
<div className="flex items-center gap-2">
|
|
450
|
+
<select
|
|
451
|
+
value={selectedTestRuntime}
|
|
452
|
+
onChange={(event) =>
|
|
453
|
+
setSelectedTestRuntime(event.target.value as AgentRuntimeId)
|
|
454
|
+
}
|
|
455
|
+
className="surface-control h-7 rounded-md border border-border/60 px-2 text-xs"
|
|
456
|
+
>
|
|
457
|
+
{runtimeOptions.map((runtime) => (
|
|
458
|
+
<option key={runtime.id} value={runtime.id}>
|
|
459
|
+
{runtime.label}
|
|
460
|
+
</option>
|
|
461
|
+
))}
|
|
462
|
+
</select>
|
|
463
|
+
<Button
|
|
464
|
+
size="sm"
|
|
465
|
+
variant="outline"
|
|
466
|
+
onClick={handleRunTests}
|
|
467
|
+
disabled={runningTests}
|
|
468
|
+
className="h-7"
|
|
469
|
+
>
|
|
470
|
+
{runningTests ? (
|
|
471
|
+
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
|
472
|
+
) : (
|
|
473
|
+
<Play className="mr-1 h-3 w-3" />
|
|
474
|
+
)}
|
|
475
|
+
{runningTests ? "Running..." : "Run Tests"}
|
|
476
|
+
</Button>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
</CardHeader>
|
|
480
|
+
<CardContent className="space-y-2">
|
|
481
|
+
{testReport?.unsupported && (
|
|
482
|
+
<div className="surface-card-muted rounded-md border border-warning/30 bg-warning/10 p-2 text-xs text-warning">
|
|
483
|
+
{testReport.unsupportedReason ??
|
|
484
|
+
`${runtimeLabelMap.get(selectedTestRuntime) ?? selectedTestRuntime} cannot test this profile yet`}
|
|
485
|
+
</div>
|
|
486
|
+
)}
|
|
487
|
+
{/* Test Summary Bar */}
|
|
488
|
+
{testReport && !testReport.unsupported && (
|
|
489
|
+
<div className="space-y-1">
|
|
490
|
+
<div className="flex h-1.5 rounded-full overflow-hidden bg-muted">
|
|
491
|
+
<div
|
|
492
|
+
className="bg-status-completed"
|
|
493
|
+
style={{ width: `${(testReport.totalPassed / testTotal) * 100}%` }}
|
|
494
|
+
/>
|
|
495
|
+
<div
|
|
496
|
+
className="bg-status-failed"
|
|
497
|
+
style={{ width: `${(testReport.totalFailed / testTotal) * 100}%` }}
|
|
498
|
+
/>
|
|
499
|
+
</div>
|
|
500
|
+
<p className="text-xs text-muted-foreground">
|
|
501
|
+
{testReport.totalPassed}/{testTotal} passed
|
|
502
|
+
</p>
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
{/* Test Items */}
|
|
506
|
+
<div className="space-y-1.5">
|
|
507
|
+
{profile.tests.map((test, i) => {
|
|
508
|
+
const result = testReport?.unsupported ? undefined : testReport?.results[i];
|
|
509
|
+
return (
|
|
510
|
+
<div key={i} className="surface-card-muted rounded-md border p-2 text-sm">
|
|
511
|
+
<div className="flex items-start gap-2">
|
|
512
|
+
{result && (
|
|
513
|
+
result.passed ? (
|
|
514
|
+
<CheckCircle2 className="mt-0.5 h-3.5 w-3.5 shrink-0 text-status-completed" />
|
|
515
|
+
) : (
|
|
516
|
+
<XCircle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-status-failed" />
|
|
517
|
+
)
|
|
518
|
+
)}
|
|
519
|
+
<div className="flex-1 min-w-0">
|
|
520
|
+
<p className="text-xs font-medium truncate">{test.task}</p>
|
|
521
|
+
<div className="mt-1 flex flex-wrap gap-0.5">
|
|
522
|
+
{test.expectedKeywords.map((kw) => {
|
|
523
|
+
const found = result?.foundKeywords.includes(kw);
|
|
524
|
+
const missing = result?.missingKeywords.includes(kw);
|
|
525
|
+
return (
|
|
526
|
+
<Badge
|
|
527
|
+
key={kw}
|
|
528
|
+
variant="outline"
|
|
529
|
+
className={`text-[10px] px-1.5 py-0 ${
|
|
530
|
+
found
|
|
531
|
+
? "border-status-completed/30 bg-status-completed/10 text-status-completed"
|
|
532
|
+
: missing
|
|
533
|
+
? "border-status-failed/30 bg-status-failed/10 text-status-failed"
|
|
534
|
+
: ""
|
|
535
|
+
}`}
|
|
536
|
+
>
|
|
537
|
+
{kw}
|
|
538
|
+
</Badge>
|
|
539
|
+
);
|
|
540
|
+
})}
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
);
|
|
546
|
+
})}
|
|
547
|
+
</div>
|
|
548
|
+
</CardContent>
|
|
549
|
+
</Card>
|
|
550
|
+
)}
|
|
551
|
+
</div>
|
|
552
|
+
|
|
553
|
+
<ConfirmDialog
|
|
554
|
+
open={confirmDelete}
|
|
555
|
+
onOpenChange={setConfirmDelete}
|
|
556
|
+
title="Delete Profile"
|
|
557
|
+
description="This will permanently delete this custom profile."
|
|
558
|
+
confirmLabel="Delete"
|
|
559
|
+
onConfirm={handleDelete}
|
|
560
|
+
destructive
|
|
561
|
+
/>
|
|
562
|
+
</div>
|
|
563
|
+
);
|
|
564
|
+
}
|