fix: close plugin workspace acceptance gaps

This commit is contained in:
brother7 committed 2026-09-03 14:29:21 +08:00
1 parent 1b1f206dd8
commit c5020ae22c
10 files changed
+885 -53

No files matched your search

+127 -9
View File
@@ -14,6 +14,96 @@ import {
import type { DataServiceInstanceState } from '../../../shared/data-service';
import type { PluginWorkspaceCommand, PluginWorkspaceItem } from './plugin-workspace-model';
interface DetailOperation {
capabilityId: string;
operationId: string;
executionMode: 'synchronous' | 'job' | null;
enabled: boolean | null;
mutation: 'read' | 'write' | 'destructive' | null;
description: string | null;
billing: {
mode: 'included' | 'platform_metered' | 'external_account';
availability: 'available' | 'unavailable' | null;
status: 'billing_unavailable' | null;
notice: string;
entitlementScope: string | null;
unitName: string | null;
unitSize: number | null;
ratePoints: string | null;
minimumChargePoints: string | null;
roundingMode: 'ceil' | null;
pricingVersion: string | number | null;
};
}
function detailOperations(item: PluginWorkspaceItem): DetailOperation[] {
const operations = new Map<string, DetailOperation>();
for (const operation of item.official?.detail?.operations ?? []) {
operations.set(`${operation.capabilityId}:${operation.operation}`, {
capabilityId: operation.capabilityId,
operationId: operation.operation,
executionMode: operation.executionMode,
enabled: operation.enabled,
mutation: null,
description: null,
billing: {
...operation.billing,
availability: null,
},
});
}
for (const capability of item.official?.project?.capabilities ?? []) {
for (const operation of capability.operations) {
const key = `${capability.id}:${operation.id}`;
const catalogOperation = operations.get(key);
operations.set(key, {
capabilityId: capability.id,
operationId: operation.id,
executionMode: catalogOperation?.executionMode ?? null,
enabled: catalogOperation?.enabled ?? null,
mutation: operation.tool?.mutation ?? null,
description: operation.tool?.description ?? null,
billing: {
mode: operation.billing.mode,
availability: operation.billing.availability,
status: null,
notice: operation.billing.notice,
entitlementScope: null,
unitName: operation.billing.unitName ?? null,
unitSize: operation.billing.unitSize ?? null,
ratePoints: operation.billing.ratePoints ?? null,
minimumChargePoints: operation.billing.minimumChargePoints ?? null,
roundingMode: operation.billing.roundingMode ?? null,
pricingVersion: operation.billing.pricingVersion ?? null,
},
});
}
}
return [...operations.values()];
}
function operationGroups(operations: readonly DetailOperation[]): Array<{
capabilityId: string;
operations: DetailOperation[];
}> {
const groups = new Map<string, DetailOperation[]>();
for (const operation of operations) {
const group = groups.get(operation.capabilityId) ?? [];
group.push(operation);
groups.set(operation.capabilityId, group);
}
return [...groups].map(([capabilityId, groupOperations]) => ({
capabilityId,
operations: groupOperations,
}));
}
function operationBillingMode(mode: DetailOperation['billing']['mode']): string {
if (mode === 'included') return '包含';
if (mode === 'platform_metered') return '按 Token Point';
return '外部账号';
}
const SOURCE_TEXT = {
official: '官方',
local: '本机',
@@ -60,6 +150,7 @@ function commandLabel(command: PluginWorkspaceCommand, title: string): string {
case 'enable_local': return `本机全局启用${title}`;
case 'disable_local': return `本机全局停用${title}`;
case 'remove_local': return `移除本机包${title}`;
case 'sign_in': return `登录后免费获取${title}`;
case 'open_agent_assignment': return `分配${title}给伙伴`;
case 'open_settings': return `查看${title}插件设置`;
}
@@ -126,6 +217,7 @@ export function PluginDetails(props: PluginDetailsProps) {
const project = props.item.official?.project ?? null;
const detail = props.item.official?.detail ?? null;
const installation = props.item.official?.installation ?? null;
const operations = detailOperations(props.item);
const showDataService = project?.enabled
&& project.settingsSurface === 'data-service';
const confirmCopy = confirmation
@@ -149,7 +241,10 @@ export function PluginDetails(props: PluginDetailsProps) {
<div className="flex flex-wrap items-center gap-2">
<Badge variant="secondary">{SOURCE_TEXT[props.item.source]}</Badge>
{props.item.stale ? <Badge variant="warning">缓存</Badge> : null}
{props.item.unavailable ? <Badge variant="warning">暂不可用</Badge> : null}
{props.item.suspended ? <Badge variant="warning">已暂停</Badge> : null}
{props.item.retired ? <Badge variant="warning">已退役</Badge> : null}
{props.item.unavailable && !props.item.suspended && !props.item.retired
? <Badge variant="warning">暂不可用</Badge> : null}
</div>
<DialogTitle className="text-balance text-2xl">{props.item.title}</DialogTitle>
<DialogDescription>
@@ -190,6 +285,11 @@ export function PluginDetails(props: PluginDetailsProps) {
<dd className="mt-1 text-sm font-semibold">{PROJECT_TEXT[props.item.projectState]}</dd>
</div>
</dl>
{props.item.deviceReason ? (
<p className="mt-3 rounded-xl bg-warning/10 p-3 text-pretty text-sm text-warning">
{installation?.version ? '旧设备版本已保留' : '设备包不可用'}:{props.item.deviceReason}
</p>
) : null}
</section>
<section aria-labelledby="plugin-actions-heading">
@@ -217,20 +317,38 @@ export function PluginDetails(props: PluginDetailsProps) {
<section aria-labelledby="plugin-capabilities-heading">
<h3 id="plugin-capabilities-heading" className="text-lg font-semibold">能力与权限</h3>
{project?.capabilities.length ? (
{operations.length ? (
<div className="mt-3 space-y-3">
{project.capabilities.map((capability) => (
<article key={capability.id} className="rounded-xl border border-border/70 p-4">
<h4 className="font-mono text-sm font-semibold">{capability.id}</h4>
{operationGroups(operations).map((capability) => (
<article key={capability.capabilityId} className="rounded-xl border border-border/70 p-4">
<h4 className="font-mono text-sm font-semibold">{capability.capabilityId}</h4>
<ul className="mt-3 space-y-3">
{capability.operations.map((operation) => (
<li key={operation.id} className="rounded-lg bg-surface-subtle p-3 text-sm">
<li key={operation.operationId} className="rounded-lg bg-surface-subtle p-3 text-sm">
<div className="flex flex-wrap items-center gap-2">
<code className="font-semibold">{operation.id}</code>
{operation.tool ? <Badge variant="outline">{operation.tool.mutation}</Badge> : <Badge variant="outline">SDK</Badge>}
<code className="font-semibold">{operation.operationId}</code>
{operation.mutation
? <Badge variant="outline">{operation.mutation}</Badge>
: operation.executionMode
? <Badge variant="outline">{operation.executionMode === 'job' ? '异步任务' : '同步'}</Badge>
: <Badge variant="outline">SDK</Badge>}
{operation.enabled === false ? <Badge variant="warning">服务端已停用</Badge> : null}
{operation.billing.availability === 'unavailable'
|| operation.billing.status === 'billing_unavailable'
? <Badge variant="warning">计费暂不可用</Badge> : null}
</div>
{operation.tool ? <p className="mt-2 text-pretty text-muted-foreground">{operation.tool.description}</p> : null}
{operation.description ? <p className="mt-2 text-pretty text-muted-foreground">{operation.description}</p> : null}
<p className="mt-2 text-xs">{operation.billing.notice}</p>
<dl className="mt-2 grid gap-x-4 gap-y-1 text-xs text-muted-foreground sm:grid-cols-2">
<div><dt className="inline">计费方式:</dt><dd className="inline">{operationBillingMode(operation.billing.mode)}</dd></div>
{operation.billing.entitlementScope ? <div><dt className="inline">权益范围:</dt><dd className="inline">{operation.billing.entitlementScope}</dd></div> : null}
{operation.billing.unitName ? <div><dt className="inline">计费单位:</dt><dd className="inline">{operation.billing.unitName}</dd></div> : null}
{operation.billing.unitSize !== null ? <div><dt className="inline">单位大小:</dt><dd className="inline">{operation.billing.unitSize}</dd></div> : null}
{operation.billing.ratePoints !== null ? <div><dt className="inline">每单位 Token Point:</dt><dd className="inline">{operation.billing.ratePoints}</dd></div> : null}
{operation.billing.minimumChargePoints !== null ? <div><dt className="inline">最低扣点:</dt><dd className="inline">{operation.billing.minimumChargePoints}</dd></div> : null}
{operation.billing.pricingVersion !== null ? <div><dt className="inline">价格版本:</dt><dd className="inline">{operation.billing.pricingVersion}</dd></div> : null}
{operation.billing.roundingMode === 'ceil' ? <div><dt className="inline">舍入方式:</dt><dd className="inline">向上取整</dd></div> : null}
</dl>
</li>
))}
</ul>