create-windy 0.4.1 → 0.5.1

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 (174) hide show
  1. package/dist/cli.js +292 -125
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
  5. package/template/apps/agent-server/adk_web_agents/services.py +25 -0
  6. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
  7. package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
  8. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
  9. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
  10. package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
  11. package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
  12. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
  13. package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
  14. package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
  15. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
  16. package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
  18. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
  21. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
  22. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
  25. package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
  26. package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
  27. package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
  28. package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
  29. package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
  30. package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
  31. package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
  32. package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
  33. package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
  34. package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
  35. package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
  36. package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
  37. package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
  38. package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
  39. package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
  40. package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
  41. package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
  42. package/template/apps/agent-server/tests/test_report_store.py +286 -0
  43. package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
  44. package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
  45. package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
  46. package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
  47. package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
  48. package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
  49. package/template/apps/server/src/agent/debug-session-id.ts +37 -0
  50. package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
  51. package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
  52. package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
  53. package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
  54. package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
  55. package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
  56. package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
  57. package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
  58. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  59. package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
  60. package/template/apps/server/src/agent/run-authorization.ts +32 -1
  61. package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
  62. package/template/apps/server/src/agent/run-contracts.ts +5 -0
  63. package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
  64. package/template/apps/server/src/agent/run-facade.test.ts +78 -0
  65. package/template/apps/server/src/agent/run-facade.ts +33 -3
  66. package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
  67. package/template/apps/server/src/agent/run-input-verification.ts +41 -0
  68. package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
  69. package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
  70. package/template/apps/server/src/agent/tool-host.test.ts +1 -0
  71. package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
  72. package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
  73. package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
  74. package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
  75. package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
  76. package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
  77. package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
  78. package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
  79. package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
  80. package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
  81. package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
  82. package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
  83. package/template/apps/server/src/license/runtime-service.test.ts +6 -0
  84. package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
  85. package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
  86. package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
  87. package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
  88. package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
  89. package/template/apps/server/src/module-host/initialize.ts +6 -197
  90. package/template/apps/server/src/module-host/request-context.ts +1 -0
  91. package/template/apps/server/src/module-host/scoped-host.ts +214 -0
  92. package/template/apps/server/src/module-host.ts +1 -0
  93. package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
  94. package/template/apps/server/src/runtime-adk-debug.ts +8 -2
  95. package/template/apps/server/src/system/operations.test.ts +54 -0
  96. package/template/apps/server/src/system/operations.ts +23 -1
  97. package/template/apps/server/src/system/seed.test.ts +4 -0
  98. package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
  99. package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
  100. package/template/apps/web/src/layout/AppSidebar.vue +6 -1
  101. package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
  102. package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
  103. package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
  104. package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
  105. package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
  106. package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
  107. package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
  108. package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
  109. package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
  110. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
  111. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
  112. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
  113. package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
  114. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
  115. package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
  116. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
  117. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
  118. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
  119. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
  120. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
  121. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
  122. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
  123. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
  124. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
  125. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
  126. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
  127. package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
  128. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
  129. package/template/apps/web/src/router/error-navigation.ts +22 -0
  130. package/template/apps/web/src/router/index.ts +12 -23
  131. package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
  132. package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
  133. package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
  134. package/template/apps/web/src/services/operations-api.ts +28 -0
  135. package/template/docker-compose.agent-debug.yml +1 -1
  136. package/template/docs/architecture/ai-runtime.md +2 -1
  137. package/template/docs/architecture/module-host.md +3 -0
  138. package/template/docs/platform/agent-run-attestation.md +65 -0
  139. package/template/docs/platform/agent-runtime-observability.md +135 -0
  140. package/template/docs/platform/agent-runtime.md +47 -3
  141. package/template/docs/platform/operations-overview.md +13 -0
  142. package/template/docs/platform/platform-authentication.md +4 -3
  143. package/template/packages/config/package.json +1 -1
  144. package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
  145. package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
  146. package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
  147. package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
  148. package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
  149. package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
  150. package/template/packages/database/drizzle/meta/_journal.json +21 -0
  151. package/template/packages/database/package.json +1 -1
  152. package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
  153. package/template/packages/database/src/schema/agent-runtime.ts +114 -0
  154. package/template/packages/jobs/package.json +1 -1
  155. package/template/packages/modules/package.json +1 -1
  156. package/template/packages/modules/src/system-admin-routes.ts +10 -0
  157. package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
  158. package/template/packages/modules/src/system-api-control-plane.ts +17 -1
  159. package/template/packages/modules/src/system-audit-actions.ts +10 -0
  160. package/template/packages/modules/src/system-features.ts +8 -0
  161. package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
  162. package/template/packages/modules/src/system-module-catalog.ts +15 -4
  163. package/template/packages/modules/src/system-modules-access.test.ts +69 -0
  164. package/template/packages/modules/src/system-modules.ts +13 -0
  165. package/template/packages/modules/src/system-permissions.ts +2 -0
  166. package/template/packages/server-sdk/index.ts +11 -0
  167. package/template/packages/server-sdk/package.json +2 -1
  168. package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
  169. package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
  170. package/template/packages/server-sdk/src/module-host.ts +5 -0
  171. package/template/packages/shared/index.ts +3 -0
  172. package/template/packages/shared/package.json +1 -1
  173. package/template/packages/shared/src/ai-runtime-report.ts +134 -0
  174. package/template/scripts/lib/postgres-regression.ts +201 -0
@@ -0,0 +1,98 @@
1
+ <script setup lang="ts">
2
+ import { shallowRef } from "vue";
3
+ import { SearchIcon } from "@lucide/vue";
4
+ import { Button } from "@/components/ui/button";
5
+ import type { AiRuntimeActor, AiRuntimeRange } from "@/services/ai-runtime-api";
6
+
7
+ defineProps<{
8
+ range: AiRuntimeRange;
9
+ actorHandle?: string;
10
+ actors: readonly AiRuntimeActor[];
11
+ disabled?: boolean;
12
+ }>();
13
+
14
+ const emit = defineEmits<{
15
+ "update:range": [value: AiRuntimeRange];
16
+ "update:actorHandle": [value: string | undefined];
17
+ "search:actors": [value: string];
18
+ }>();
19
+ const actorSearch = shallowRef("");
20
+
21
+ const ranges: readonly { value: AiRuntimeRange; label: string }[] = [
22
+ { value: "day", label: "近 24 小时" },
23
+ { value: "month", label: "近 30 天" },
24
+ { value: "retained", label: "保留期" },
25
+ ];
26
+
27
+ function selectActor(event: Event) {
28
+ const value = (event.target as HTMLSelectElement).value;
29
+ emit("update:actorHandle", value || undefined);
30
+ }
31
+
32
+ function searchActors() {
33
+ emit("search:actors", actorSearch.value.trim());
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <div
39
+ class="flex flex-col gap-3 rounded-lg border bg-card p-3 md:flex-row md:items-center md:justify-between"
40
+ >
41
+ <div class="inline-flex w-fit gap-1 rounded-md bg-muted p-1">
42
+ <Button
43
+ v-for="item in ranges"
44
+ :key="item.value"
45
+ size="sm"
46
+ :variant="range === item.value ? 'secondary' : 'ghost'"
47
+ :disabled="disabled"
48
+ @click="emit('update:range', item.value)"
49
+ >
50
+ {{ item.label }}
51
+ </Button>
52
+ </div>
53
+
54
+ <div class="flex flex-col gap-2 sm:flex-row sm:items-center">
55
+ <label class="flex items-center gap-2 text-sm text-muted-foreground">
56
+ 查找
57
+ <span class="flex">
58
+ <input
59
+ v-model="actorSearch"
60
+ class="h-9 w-44 rounded-l-md border border-input bg-background px-3 text-sm text-foreground outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30"
61
+ placeholder="姓名或账号"
62
+ :disabled="disabled"
63
+ @keyup.enter="searchActors"
64
+ />
65
+ <Button
66
+ class="rounded-l-none"
67
+ size="icon"
68
+ variant="outline"
69
+ :disabled="disabled"
70
+ aria-label="搜索操作者"
71
+ @click="searchActors"
72
+ >
73
+ <SearchIcon />
74
+ </Button>
75
+ </span>
76
+ </label>
77
+ <label class="flex items-center gap-2 text-sm text-muted-foreground">
78
+ 操作者
79
+ <select
80
+ class="h-9 min-w-48 rounded-md border border-input bg-background px-3 text-sm text-foreground shadow-xs outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30"
81
+ :value="actorHandle || ''"
82
+ :disabled="disabled"
83
+ @change="selectActor"
84
+ >
85
+ <option value="">全部操作者</option>
86
+ <option
87
+ v-for="actor in actors"
88
+ :key="actor.handle"
89
+ :value="actor.handle"
90
+ >
91
+ {{ actor.displayName
92
+ }}{{ actor.username ? `(${actor.username})` : "" }}
93
+ </option>
94
+ </select>
95
+ </label>
96
+ </div>
97
+ </div>
98
+ </template>
@@ -0,0 +1,27 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test } from "vitest";
3
+ import AiRuntimeFilters from "./AiRuntimeFilters.vue";
4
+
5
+ describe("AI Runtime 操作者筛选", () => {
6
+ test("按姓名搜索后选择服务端返回的操作者", async () => {
7
+ const wrapper = mount(AiRuntimeFilters, {
8
+ props: {
9
+ range: "day",
10
+ actors: [
11
+ {
12
+ handle: "actor_opaque",
13
+ displayName: "张三",
14
+ username: "zhangsan",
15
+ },
16
+ ],
17
+ },
18
+ });
19
+
20
+ await wrapper.get('input[placeholder="姓名或账号"]').setValue("张三");
21
+ await wrapper.get('button[aria-label="搜索操作者"]').trigger("click");
22
+ await wrapper.get("select").setValue("actor_opaque");
23
+
24
+ expect(wrapper.emitted("search:actors")).toEqual([["张三"]]);
25
+ expect(wrapper.emitted("update:actorHandle")).toEqual([["actor_opaque"]]);
26
+ });
27
+ });
@@ -0,0 +1,168 @@
1
+ <script setup lang="ts">
2
+ import { Button } from "@/components/ui/button";
3
+ import {
4
+ Dialog,
5
+ DialogClose,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from "@/components/ui/dialog";
12
+ import { Badge } from "@/components/ui/badge";
13
+ import {
14
+ Table,
15
+ TableBody,
16
+ TableCell,
17
+ TableHead,
18
+ TableHeader,
19
+ TableRow,
20
+ } from "@/components/ui/table";
21
+ import type { AiRuntimeRunDetail } from "@/services/ai-runtime-api";
22
+ import {
23
+ cacheStatusLabel,
24
+ formatDateTime,
25
+ formatDuration,
26
+ formatTokenCount,
27
+ runStatusLabel,
28
+ } from "../../ai-runtime-presenter";
29
+
30
+ defineProps<{
31
+ detail?: AiRuntimeRunDetail;
32
+ loading?: boolean;
33
+ error?: string;
34
+ }>();
35
+ const open = defineModel<boolean>("open", { required: true });
36
+ </script>
37
+
38
+ <template>
39
+ <Dialog v-model:open="open">
40
+ <DialogContent class="max-h-[85vh] max-w-5xl overflow-y-auto">
41
+ <DialogHeader>
42
+ <DialogTitle>Agent Run 详情</DialogTitle>
43
+ <DialogDescription>
44
+ 只展示受治理的运行元数据和计量事实,不包含输入、输出正文或 Tool 参数。
45
+ </DialogDescription>
46
+ </DialogHeader>
47
+
48
+ <p v-if="loading" class="py-12 text-center text-sm text-muted-foreground">
49
+ 正在加载运行详情…
50
+ </p>
51
+ <p v-else-if="error" class="py-8 text-sm text-destructive">
52
+ {{ error }}
53
+ </p>
54
+ <template v-else-if="detail">
55
+ <dl class="grid gap-x-6 gap-y-4 text-sm sm:grid-cols-2 lg:grid-cols-4">
56
+ <div>
57
+ <dt class="text-muted-foreground">结果</dt>
58
+ <dd class="mt-1">
59
+ <Badge variant="secondary">{{
60
+ runStatusLabel(detail.status)
61
+ }}</Badge>
62
+ </dd>
63
+ </div>
64
+ <div>
65
+ <dt class="text-muted-foreground">操作者</dt>
66
+ <dd class="mt-1 font-medium">
67
+ {{ detail.actor?.displayName || "已删除或服务主体" }}
68
+ </dd>
69
+ </div>
70
+ <div>
71
+ <dt class="text-muted-foreground">总耗时</dt>
72
+ <dd class="mt-1">{{ formatDuration(detail.durationMs) }}</dd>
73
+ </div>
74
+ <div>
75
+ <dt class="text-muted-foreground">总 Token</dt>
76
+ <dd class="mt-1">{{ formatTokenCount(detail.totalTokens) }}</dd>
77
+ </div>
78
+ <div class="sm:col-span-2">
79
+ <dt class="text-muted-foreground">Operation</dt>
80
+ <dd class="mt-1 break-all font-mono text-xs">
81
+ {{ detail.operationKey }}
82
+ </dd>
83
+ </div>
84
+ <div>
85
+ <dt class="text-muted-foreground">开始时间</dt>
86
+ <dd class="mt-1">{{ formatDateTime(detail.startedAt) }}</dd>
87
+ </div>
88
+ <div>
89
+ <dt class="text-muted-foreground">结束时间</dt>
90
+ <dd class="mt-1">{{ formatDateTime(detail.finishedAt) }}</dd>
91
+ </div>
92
+ <div v-if="detail.traceId" class="sm:col-span-2">
93
+ <dt class="text-muted-foreground">Trace ID</dt>
94
+ <dd class="mt-1 break-all font-mono text-xs">
95
+ {{ detail.traceId }}
96
+ </dd>
97
+ </div>
98
+ <div v-if="detail.safeErrorCode" class="sm:col-span-2">
99
+ <dt class="text-muted-foreground">安全错误码</dt>
100
+ <dd class="mt-1 font-mono text-xs">{{ detail.safeErrorCode }}</dd>
101
+ </div>
102
+ </dl>
103
+
104
+ <section class="space-y-3">
105
+ <h3 class="text-sm font-medium">Provider Attempts</h3>
106
+ <div class="overflow-hidden rounded-md border">
107
+ <Table>
108
+ <TableHeader>
109
+ <TableRow>
110
+ <TableHead>#</TableHead>
111
+ <TableHead>模型</TableHead>
112
+ <TableHead>结果</TableHead>
113
+ <TableHead>Token</TableHead>
114
+ <TableHead>缓存</TableHead>
115
+ <TableHead>首 Token</TableHead>
116
+ <TableHead>耗时</TableHead>
117
+ </TableRow>
118
+ </TableHeader>
119
+ <TableBody>
120
+ <TableRow
121
+ v-for="attempt in detail.attemptItems"
122
+ :key="attempt.sequence"
123
+ >
124
+ <TableCell>{{ attempt.sequence }}</TableCell>
125
+ <TableCell>
126
+ <div>
127
+ {{ attempt.reportedModel || attempt.requestedModel }}
128
+ </div>
129
+ <div class="text-xs text-muted-foreground">
130
+ {{ attempt.deploymentKey }}
131
+ </div>
132
+ </TableCell>
133
+ <TableCell>{{ runStatusLabel(attempt.outcome) }}</TableCell>
134
+ <TableCell>{{
135
+ formatTokenCount(attempt.totalTokens)
136
+ }}</TableCell>
137
+ <TableCell>{{
138
+ cacheStatusLabel(attempt.cacheStatus)
139
+ }}</TableCell>
140
+ <TableCell>{{
141
+ formatDuration(attempt.firstTokenLatencyMs)
142
+ }}</TableCell>
143
+ <TableCell>{{
144
+ formatDuration(attempt.durationMs)
145
+ }}</TableCell>
146
+ </TableRow>
147
+ <TableRow v-if="!detail.attemptItems.length">
148
+ <TableCell
149
+ colspan="7"
150
+ class="h-20 text-center text-muted-foreground"
151
+ >
152
+ 没有可展示的 Provider Attempt。
153
+ </TableCell>
154
+ </TableRow>
155
+ </TableBody>
156
+ </Table>
157
+ </div>
158
+ </section>
159
+ </template>
160
+
161
+ <DialogFooter>
162
+ <DialogClose as-child>
163
+ <Button variant="outline">关闭</Button>
164
+ </DialogClose>
165
+ </DialogFooter>
166
+ </DialogContent>
167
+ </Dialog>
168
+ </template>
@@ -0,0 +1,130 @@
1
+ <script setup lang="ts">
2
+ import DataPagination from "@/components/common/DataPagination.vue";
3
+ import { Badge } from "@/components/ui/badge";
4
+ import { Skeleton } from "@/components/ui/skeleton";
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from "@/components/ui/table";
13
+ import type {
14
+ AiRuntimePage,
15
+ AiRuntimeRunItem,
16
+ } from "@/services/ai-runtime-api";
17
+ import {
18
+ cacheStatusLabel,
19
+ formatDateTime,
20
+ formatDuration,
21
+ formatTokenCount,
22
+ runStatusLabel,
23
+ } from "../../ai-runtime-presenter";
24
+
25
+ defineProps<{
26
+ runs?: AiRuntimePage<AiRuntimeRunItem>;
27
+ loading?: boolean;
28
+ }>();
29
+
30
+ const emit = defineEmits<{
31
+ select: [runId: string];
32
+ "update:page": [page: number];
33
+ "update:page-size": [pageSize: number];
34
+ }>();
35
+
36
+ function statusVariant(status: string) {
37
+ return status === "failed"
38
+ ? "destructive"
39
+ : status === "completed"
40
+ ? "secondary"
41
+ : "outline";
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <div class="space-y-3">
47
+ <div class="overflow-hidden rounded-md border">
48
+ <Table>
49
+ <TableHeader>
50
+ <TableRow>
51
+ <TableHead>开始时间</TableHead>
52
+ <TableHead>操作者</TableHead>
53
+ <TableHead>Operation</TableHead>
54
+ <TableHead>结果</TableHead>
55
+ <TableHead>模型</TableHead>
56
+ <TableHead class="text-right">Token</TableHead>
57
+ <TableHead>缓存</TableHead>
58
+ <TableHead class="text-right">耗时</TableHead>
59
+ </TableRow>
60
+ </TableHeader>
61
+ <TableBody>
62
+ <template v-if="loading && !runs?.items.length">
63
+ <TableRow v-for="row in 4" :key="row">
64
+ <TableCell v-for="column in 8" :key="column">
65
+ <Skeleton class="h-4 w-full max-w-28" />
66
+ </TableCell>
67
+ </TableRow>
68
+ </template>
69
+ <TableRow v-else-if="!runs?.items.length">
70
+ <TableCell
71
+ colspan="8"
72
+ class="h-24 text-center text-muted-foreground"
73
+ >
74
+ 当前筛选范围内没有 Agent Run。
75
+ </TableCell>
76
+ </TableRow>
77
+ <TableRow
78
+ v-for="run in runs?.items"
79
+ v-else
80
+ :key="run.runId"
81
+ class="cursor-pointer"
82
+ tabindex="0"
83
+ @click="emit('select', run.runId)"
84
+ @keyup.enter="emit('select', run.runId)"
85
+ >
86
+ <TableCell class="whitespace-nowrap">
87
+ {{ formatDateTime(run.startedAt) }}
88
+ </TableCell>
89
+ <TableCell>
90
+ <div class="font-medium">
91
+ {{ run.actor?.displayName || "已删除或服务主体" }}
92
+ </div>
93
+ <div
94
+ v-if="run.actor?.username"
95
+ class="text-xs text-muted-foreground"
96
+ >
97
+ {{ run.actor.username }}
98
+ </div>
99
+ </TableCell>
100
+ <TableCell class="max-w-56 truncate font-mono text-xs">
101
+ {{ run.operationKey }}
102
+ </TableCell>
103
+ <TableCell>
104
+ <Badge :variant="statusVariant(run.status)">
105
+ {{ runStatusLabel(run.status) }}
106
+ </Badge>
107
+ </TableCell>
108
+ <TableCell>{{ run.model || "未上报" }}</TableCell>
109
+ <TableCell class="text-right tabular-nums">
110
+ {{ formatTokenCount(run.totalTokens) }}
111
+ </TableCell>
112
+ <TableCell>{{ cacheStatusLabel(run.cacheStatus) }}</TableCell>
113
+ <TableCell class="text-right tabular-nums">
114
+ {{ formatDuration(run.durationMs) }}
115
+ </TableCell>
116
+ </TableRow>
117
+ </TableBody>
118
+ </Table>
119
+ </div>
120
+
121
+ <DataPagination
122
+ v-if="runs"
123
+ :page="runs.page"
124
+ :page-size="runs.pageSize"
125
+ :total="runs.total"
126
+ @update:page="emit('update:page', $event)"
127
+ @update:page-size="emit('update:page-size', $event)"
128
+ />
129
+ </div>
130
+ </template>
@@ -0,0 +1,82 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import {
4
+ ActivityIcon,
5
+ CircleCheckIcon,
6
+ DatabaseIcon,
7
+ GaugeIcon,
8
+ } from "@lucide/vue";
9
+ import {
10
+ Card,
11
+ CardContent,
12
+ CardDescription,
13
+ CardHeader,
14
+ CardTitle,
15
+ } from "@/components/ui/card";
16
+ import type { AiRuntimeSummary } from "@/services/ai-runtime-api";
17
+ import {
18
+ formatCoverage,
19
+ formatPercent,
20
+ formatTokenCount,
21
+ } from "../../ai-runtime-presenter";
22
+
23
+ const props = defineProps<{ summary: AiRuntimeSummary }>();
24
+
25
+ const tokenAttemptCount = computed(() =>
26
+ props.summary.byModel.reduce((total, model) => total + model.attempts, 0),
27
+ );
28
+ const displayedTokenTotal = computed(() =>
29
+ tokenAttemptCount.value > 0 && props.summary.tokens.coverageRate === 0
30
+ ? null
31
+ : props.summary.tokens.total,
32
+ );
33
+ const metrics = computed(() => [
34
+ {
35
+ label: "Token 总量",
36
+ value: formatTokenCount(displayedTokenTotal.value),
37
+ detail: `${formatCoverage(props.summary.tokens.coverageRate)} · 输入 ${formatTokenCount(props.summary.tokens.input)} / 输出 ${formatTokenCount(props.summary.tokens.output)}`,
38
+ icon: DatabaseIcon,
39
+ },
40
+ {
41
+ label: "Run 成功率",
42
+ value: formatPercent(props.summary.runs.successRate),
43
+ detail: `${props.summary.runs.completed} 成功 · ${props.summary.runs.failed} 失败`,
44
+ icon: CircleCheckIcon,
45
+ },
46
+ {
47
+ label: "Attempt 缓存命中率",
48
+ value: formatPercent(props.summary.cache.hitRate),
49
+ detail: `${formatCoverage(props.summary.cache.coverageRate)} · ${props.summary.cache.unknown} 次未上报`,
50
+ icon: GaugeIcon,
51
+ },
52
+ {
53
+ label: "Run 总数",
54
+ value: formatTokenCount(props.summary.runs.total),
55
+ detail: `${props.summary.runs.inProgress} 运行中 · ${props.summary.runs.cancelled} 已取消`,
56
+ icon: ActivityIcon,
57
+ },
58
+ ]);
59
+ </script>
60
+
61
+ <template>
62
+ <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
63
+ <Card v-for="metric in metrics" :key="metric.label">
64
+ <CardHeader class="flex-row items-start justify-between gap-3 pb-2">
65
+ <div class="space-y-1">
66
+ <CardDescription>{{ metric.label }}</CardDescription>
67
+ <CardTitle class="text-3xl tabular-nums">
68
+ {{ metric.value }}
69
+ </CardTitle>
70
+ </div>
71
+ <component
72
+ :is="metric.icon"
73
+ class="size-5 text-muted-foreground"
74
+ aria-hidden="true"
75
+ />
76
+ </CardHeader>
77
+ <CardContent class="text-xs leading-5 text-muted-foreground">
78
+ {{ metric.detail }}
79
+ </CardContent>
80
+ </Card>
81
+ </div>
82
+ </template>
@@ -0,0 +1,102 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test } from "vitest";
3
+ import AiRuntimeSummaryCards from "./AiRuntimeSummaryCards.vue";
4
+
5
+ describe("AI Runtime 汇总卡", () => {
6
+ test("无 Attempt 时清楚区分未知成功率和真实零 Token", () => {
7
+ const wrapper = mount(AiRuntimeSummaryCards, {
8
+ props: {
9
+ summary: {
10
+ generatedAt: "2026-07-31T00:00:00.000Z",
11
+ reportingAvailableFrom: "2026-07-01T00:00:00.000Z",
12
+ retentionDays: 90,
13
+ runs: {
14
+ total: 0,
15
+ completed: 0,
16
+ failed: 0,
17
+ cancelled: 0,
18
+ inProgress: 0,
19
+ successRate: null,
20
+ },
21
+ cache: {
22
+ hit: 0,
23
+ miss: 0,
24
+ unknown: 3,
25
+ hitRate: null,
26
+ tokenRate: null,
27
+ coverageRate: 0,
28
+ },
29
+ tokens: {
30
+ input: 0,
31
+ output: 0,
32
+ cachedInput: 0,
33
+ reasoning: 0,
34
+ total: 0,
35
+ coverageRate: 0,
36
+ },
37
+ byModel: [],
38
+ activity: [],
39
+ },
40
+ },
41
+ });
42
+
43
+ expect(wrapper.text()).toContain("Token 总量");
44
+ expect(wrapper.text()).toContain("0");
45
+ expect(wrapper.text()).toContain("Run 成功率");
46
+ expect(wrapper.text().match(/未上报/g)?.length).toBeGreaterThanOrEqual(2);
47
+ expect(wrapper.text()).not.toContain("100%");
48
+ });
49
+
50
+ test("已有 Attempt 但 Token 覆盖为零时显示未上报", () => {
51
+ const wrapper = mount(AiRuntimeSummaryCards, {
52
+ props: {
53
+ summary: {
54
+ generatedAt: "2026-07-31T00:00:00.000Z",
55
+ reportingAvailableFrom: "2026-07-31T00:00:00.000Z",
56
+ retentionDays: 90,
57
+ runs: {
58
+ total: 1,
59
+ completed: 1,
60
+ failed: 0,
61
+ cancelled: 0,
62
+ inProgress: 0,
63
+ successRate: 1,
64
+ },
65
+ cache: {
66
+ hit: 0,
67
+ miss: 0,
68
+ unknown: 1,
69
+ hitRate: null,
70
+ tokenRate: null,
71
+ coverageRate: 0,
72
+ },
73
+ tokens: {
74
+ input: 0,
75
+ output: 0,
76
+ cachedInput: 0,
77
+ reasoning: 0,
78
+ total: 0,
79
+ coverageRate: 0,
80
+ },
81
+ byModel: [
82
+ {
83
+ deploymentKey: "ai.openai-compatible",
84
+ model: "qwen",
85
+ attempts: 1,
86
+ inputTokens: 0,
87
+ outputTokens: 0,
88
+ cachedInputTokens: 0,
89
+ reasoningTokens: 0,
90
+ totalTokens: 0,
91
+ unknownUsageCount: 1,
92
+ },
93
+ ],
94
+ activity: [],
95
+ },
96
+ },
97
+ });
98
+
99
+ expect(wrapper.get(".text-3xl").text()).toBe("未上报");
100
+ expect(wrapper.text()).toContain("覆盖 0.0%");
101
+ });
102
+ });