create-windy 0.4.1 → 0.5.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/dist/cli.js +258 -121
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/agent-server/adk_web_agents/canonical_debug/agent.py +29 -9
- package/template/apps/agent-server/adk_web_agents/services.py +25 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +40 -11
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug_sessions.py +325 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +70 -6
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/app.py +16 -4
- package/template/apps/agent-server/src/southwind_agent_server/approval_resolution.py +30 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +9 -0
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +2 -1
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +3 -1
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +95 -130
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine_validation.py +133 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +89 -20
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_insert.py +34 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_queries.py +234 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_replay.py +44 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_status.py +83 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_store.py +331 -0
- package/template/apps/agent-server/src/southwind_agent_server/postgres_report_summary.py +304 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_lifecycle.py +217 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_metrics.py +149 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_presenter.py +280 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_routes.py +126 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_store.py +344 -0
- package/template/apps/agent-server/src/southwind_agent_server/report_types.py +111 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_factory.py +31 -2
- package/template/apps/agent-server/src/southwind_agent_server/service.py +56 -51
- package/template/apps/agent-server/src/southwind_agent_server/signal_events.py +18 -1
- package/template/apps/agent-server/tests/test_adk_adapter.py +7 -9
- package/template/apps/agent-server/tests/test_adk_debug_sessions.py +215 -0
- package/template/apps/agent-server/tests/test_adk_web_canonical_debug.py +48 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +58 -6
- package/template/apps/agent-server/tests/test_postgres_report_status.py +59 -0
- package/template/apps/agent-server/tests/test_postgres_report_summary.py +298 -0
- package/template/apps/agent-server/tests/test_provider_attempt_reporting.py +152 -0
- package/template/apps/agent-server/tests/test_report_attempt_lifecycle.py +126 -0
- package/template/apps/agent-server/tests/test_report_resilience.py +181 -0
- package/template/apps/agent-server/tests/test_report_store.py +286 -0
- package/template/apps/agent-server/tests/test_report_summary_parity.py +115 -0
- package/template/apps/agent-server/tests/test_report_terminal_sync.py +177 -0
- package/template/apps/server/src/agent/debug-session-facade-provider-failure.test.ts +269 -0
- package/template/apps/server/src/agent/debug-session-facade.test.ts +335 -0
- package/template/apps/server/src/agent/debug-session-facade.ts +302 -0
- package/template/apps/server/src/agent/debug-session-id.test.ts +37 -0
- package/template/apps/server/src/agent/debug-session-id.ts +37 -0
- package/template/apps/server/src/agent/debug-session-provider-reader.ts +54 -0
- package/template/apps/server/src/agent/debug-session-routes.test.ts +64 -0
- package/template/apps/server/src/agent/debug-session-routes.ts +73 -0
- package/template/apps/server/src/agent/debug-session-validation.ts +106 -0
- package/template/apps/server/src/agent/drizzle-run-binding-repository.ts +2 -0
- package/template/apps/server/src/agent/provider-input-governance.test.ts +18 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +37 -1
- package/template/apps/server/src/agent/remote-runtime.test.ts +7 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-authorization.test.ts +98 -0
- package/template/apps/server/src/agent/run-authorization.ts +32 -1
- package/template/apps/server/src/agent/run-binding-repository.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +5 -0
- package/template/apps/server/src/agent/run-facade-adk-debug.test.ts +39 -0
- package/template/apps/server/src/agent/run-facade.test.ts +78 -0
- package/template/apps/server/src/agent/run-facade.ts +33 -3
- package/template/apps/server/src/agent/run-input-commitment.ts +50 -0
- package/template/apps/server/src/agent/run-input-verification.ts +41 -0
- package/template/apps/server/src/agent/runtime-bootstrap-report.test.ts +136 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +66 -1
- package/template/apps/server/src/agent/tool-host.test.ts +1 -0
- package/template/apps/server/src/agent/unavailable-routes.ts +4 -0
- package/template/apps/server/src/ai-runtime-report/actor-directory.ts +94 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.test.ts +27 -0
- package/template/apps/server/src/ai-runtime-report/actor-handle.ts +81 -0
- package/template/apps/server/src/ai-runtime-report/contracts.ts +91 -0
- package/template/apps/server/src/ai-runtime-report/remote-parser.ts +212 -0
- package/template/apps/server/src/ai-runtime-report/remote-port.ts +138 -0
- package/template/apps/server/src/ai-runtime-report/routes.ts +172 -0
- package/template/apps/server/src/ai-runtime-report/scope.ts +63 -0
- package/template/apps/server/src/ai-runtime-report/service.test.ts +271 -0
- package/template/apps/server/src/ai-runtime-report/service.ts +206 -0
- package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -0
- package/template/apps/server/src/license/runtime-service.test.ts +6 -0
- package/template/apps/server/src/module-host/ai-operation-port.test.ts +63 -0
- package/template/apps/server/src/module-host/ai-operation-port.ts +30 -5
- package/template/apps/server/src/module-host/debug-session-provider-registration.test.ts +162 -0
- package/template/apps/server/src/module-host/debug-session-provider-registration.ts +84 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +7 -0
- package/template/apps/server/src/module-host/initialize.ts +6 -197
- package/template/apps/server/src/module-host/request-context.ts +1 -0
- package/template/apps/server/src/module-host/scoped-host.ts +214 -0
- package/template/apps/server/src/module-host.ts +1 -0
- package/template/apps/server/src/runtime-adk-debug.test.ts +25 -1
- package/template/apps/server/src/runtime-adk-debug.ts +8 -2
- package/template/apps/server/src/system/operations.test.ts +54 -0
- package/template/apps/server/src/system/operations.ts +23 -1
- package/template/apps/server/src/system/seed.test.ts +4 -0
- package/template/apps/server/src/system/system-resource-monitoring.test.ts +82 -0
- package/template/apps/server/src/system/system-resource-monitoring.ts +300 -0
- package/template/apps/web/src/layout/AppSidebar.vue +6 -1
- package/template/apps/web/src/layout/AppSidebar.webtest.ts +2 -0
- package/template/apps/web/src/pages/auth/LoginPage.vue +6 -1
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.vue +20 -3
- package/template/apps/web/src/pages/settings/BrandingSettingsSection.webtest.ts +80 -0
- package/template/apps/web/src/pages/system/AiRuntimePage.vue +129 -0
- package/template/apps/web/src/pages/system/SystemOperationsPage.vue +2 -1
- package/template/apps/web/src/pages/system/ai-runtime-presenter.ts +60 -0
- package/template/apps/web/src/pages/system/ai-runtime-presenter.webtest.ts +48 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +34 -2
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +4 -3
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +26 -1
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.vue +98 -0
- package/template/apps/web/src/pages/system/components/OperationGpuMonitor.webtest.ts +61 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.vue +53 -0
- package/template/apps/web/src/pages/system/components/OperationServiceMonitor.webtest.ts +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +14 -1
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeActivityChart.vue +85 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeCharts.webtest.ts +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +98 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +27 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunDetailDialog.vue +168 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeRunTable.vue +130 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.vue +82 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts +102 -0
- package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeTokenByModelChart.vue +101 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.ts +185 -0
- package/template/apps/web/src/pages/system/composables/useAiRuntimeReport.webtest.ts +158 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +21 -7
- package/template/apps/web/src/router/error-navigation.ts +22 -0
- package/template/apps/web/src/router/index.ts +12 -23
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +4 -0
- package/template/apps/web/src/services/ai-runtime-api.ts +164 -0
- package/template/apps/web/src/services/ai-runtime-api.webtest.ts +111 -0
- package/template/apps/web/src/services/operations-api.ts +28 -0
- package/template/docker-compose.agent-debug.yml +1 -1
- package/template/docs/architecture/ai-runtime.md +2 -1
- package/template/docs/architecture/module-host.md +3 -0
- package/template/docs/platform/agent-run-attestation.md +65 -0
- package/template/docs/platform/agent-runtime-observability.md +135 -0
- package/template/docs/platform/agent-runtime.md +47 -3
- package/template/docs/platform/operations-overview.md +13 -0
- package/template/docs/platform/platform-authentication.md +4 -3
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0045_tiny_cassandra_nova.sql +65 -0
- package/template/packages/database/drizzle/0046_tranquil_living_mummy.sql +6 -0
- package/template/packages/database/drizzle/0047_oval_luke_cage.sql +18 -0
- package/template/packages/database/drizzle/meta/0045_snapshot.json +8814 -0
- package/template/packages/database/drizzle/meta/0046_snapshot.json +8820 -0
- package/template/packages/database/drizzle/meta/0047_snapshot.json +8835 -0
- package/template/packages/database/drizzle/meta/_journal.json +21 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/agent-runtime-schema.test.ts +54 -0
- package/template/packages/database/src/schema/agent-runtime.ts +114 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-admin-routes.ts +10 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +1 -1
- package/template/packages/modules/src/system-api-control-plane.ts +17 -1
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +8 -0
- package/template/packages/modules/src/system-module-catalog.test.ts +35 -0
- package/template/packages/modules/src/system-module-catalog.ts +15 -4
- package/template/packages/modules/src/system-modules-access.test.ts +69 -0
- package/template/packages/modules/src/system-modules.ts +13 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/ai-operation-port.ts +5 -0
- package/template/packages/server-sdk/src/debug-session-provider.ts +63 -0
- package/template/packages/server-sdk/src/module-host.ts +5 -0
- package/template/packages/shared/index.ts +3 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/ai-runtime-report.ts +134 -0
- 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>
|
package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeSummaryCards.webtest.ts
ADDED
|
@@ -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
|
+
});
|