功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+178
View File
@@ -0,0 +1,178 @@
import type { JobResponse, RunEndpointResponse, ServerInstanceResponse, ServerMetricsResponse } from "../api/types";
import { safeDiagnosticText } from "../utils/safeDiagnosticText";
import type { OperationRecord } from "./workspace";
export type OperationsModuleState<T> =
| { status: "loading"; refreshedAt?: string }
| { status: "error"; reason: string; diagnosticId: string; refreshedAt?: string }
| { status: "ready"; data: T; refreshedAt: string };
export interface JobBuckets {
active: JobResponse[];
retrying: JobResponse[];
failed: JobResponse[];
succeeded: JobResponse[];
cancelled: JobResponse[];
}
export interface ServerOperationsSummary {
instance: ServerInstanceResponse;
metrics?: ServerMetricsResponse;
activeJobs: number;
failedJobs: number;
latestJob?: JobResponse;
}
export interface EndpointOperationsSummary {
total: number;
online: number;
degraded: number;
offline: number;
disabled: number;
activeJobs: number;
queuedJobs: number;
}
export interface OperationTrayItem {
id: string;
intent: string;
targetLabel: string;
status: OperationRecord["status"];
message?: string;
errorReason?: string;
diagnosticId?: string;
updatedAt: string;
}
const activeJobStates = new Set<JobResponse["state"]>(["queued", "accepted", "running", "retrying"]);
export function isActiveJob(job: JobResponse): boolean {
return activeJobStates.has(job.state);
}
export function jobBuckets(jobs: JobResponse[]): JobBuckets {
const sorted = [...jobs].sort((left, right) => timestamp(right.updatedAt) - timestamp(left.updatedAt));
return {
active: sorted.filter(isActiveJob),
retrying: sorted.filter((job) => job.state === "retrying"),
failed: sorted.filter((job) => job.state === "failed"),
succeeded: sorted.filter((job) => job.state === "succeeded"),
cancelled: sorted.filter((job) => job.state === "cancelled")
};
}
export function summarizeServerOperations(
instances: ServerInstanceResponse[],
metrics: Map<string, ServerMetricsResponse>,
jobs: JobResponse[]
): ServerOperationsSummary[] {
const jobsByServer = new Map<string, JobResponse[]>();
for (const job of jobs) {
if (!job.serverInstanceId) {
continue;
}
const serverJobs = jobsByServer.get(job.serverInstanceId) ?? [];
serverJobs.push(job);
jobsByServer.set(job.serverInstanceId, serverJobs);
}
return instances
.map((instance) => {
const serverJobs = [...(jobsByServer.get(instance.id) ?? [])].sort(
(left, right) => timestamp(right.updatedAt) - timestamp(left.updatedAt)
);
return {
instance,
metrics: metrics.get(instance.id),
activeJobs: serverJobs.filter(isActiveJob).length,
failedJobs: serverJobs.filter((job) => job.state === "failed").length,
latestJob: serverJobs[0]
};
})
.sort(compareServerOperations);
}
export function summarizeEndpointOperations(endpoints: RunEndpointResponse[]): EndpointOperationsSummary {
return endpoints.reduce<EndpointOperationsSummary>(
(summary, endpoint) => ({
total: summary.total + 1,
online: summary.online + Number(endpoint.status === "online"),
degraded: summary.degraded + Number(endpoint.status === "degraded"),
offline: summary.offline + Number(endpoint.status === "offline"),
disabled: summary.disabled + Number(endpoint.status === "disabled"),
activeJobs: summary.activeJobs + endpoint.capacity.runningJobs,
queuedJobs: summary.queuedJobs + endpoint.capacity.queuedJobs
}),
{ total: 0, online: 0, degraded: 0, offline: 0, disabled: 0, activeJobs: 0, queuedJobs: 0 }
);
}
export function projectOperationForTray(operation: OperationRecord): OperationTrayItem {
return {
id: operation.id,
intent: operation.intent,
targetLabel: safeOperationTarget(operation.targetKind, operation.targetId),
status: operation.status,
message: safeDiagnosticText(operation.message),
errorReason: safeDiagnosticText(operation.errorReason),
diagnosticId: safeDiagnosticId(operation.diagnosticId),
updatedAt: operation.updatedAt
};
}
export function moduleFreshnessLabel(refreshedAt?: string): string {
if (!refreshedAt) {
return "尚未刷新";
}
const time = new Date(refreshedAt);
return Number.isNaN(time.getTime()) ? "刷新时间未知" : `刷新于 ${time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
}
function compareServerOperations(left: ServerOperationsSummary, right: ServerOperationsSummary): number {
const attentionDifference = serverAttentionRank(right) - serverAttentionRank(left);
if (attentionDifference !== 0) {
return attentionDifference;
}
const updatedDifference = timestamp(right.instance.updatedAt) - timestamp(left.instance.updatedAt);
return updatedDifference !== 0 ? updatedDifference : left.instance.name.localeCompare(right.instance.name, "zh-CN");
}
function serverAttentionRank(summary: ServerOperationsSummary): number {
if (summary.instance.state === "failed") {
return 50;
}
if (summary.failedJobs > 0) {
return 40;
}
if (summary.instance.state === "installing") {
return 30;
}
if (summary.activeJobs > 0) {
return 20;
}
if (summary.instance.state === "running") {
return 10;
}
return 0;
}
function safeOperationTarget(kind: OperationRecord["targetKind"], targetId: string): string {
const kindLabel: Record<OperationRecord["targetKind"], string> = {
server: "服务器",
plugin: "插件",
config: "配置",
llm: "AI 提供商",
platform: "平台"
};
const safeId = /^[a-zA-Z0-9._:-]{1,96}$/.test(targetId) ? targetId : "受保护目标";
return `${kindLabel[kind]} / ${safeId}`;
}
function safeDiagnosticId(value: string | undefined): string | undefined {
return value && /^[a-zA-Z0-9._:-]{1,96}$/.test(value) ? value : value ? "受保护诊断" : undefined;
}
function timestamp(value: string): number {
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? 0 : parsed;
}