merge: integrate remote and local main histories
# Conflicts: # .project-docs/20-architecture/module-map.md # .project-docs/30-worklog/current-state.md # .project-docs/50-evidence/evidence-index.md # src/pages/MyPlugins/index.tsx # tests/unit/pi-agent-server-process-real.test.ts # tests/unit/pi-managed-worker-opener.test.ts # tests/unit/plugin-marketplace-pages.test.tsx
This commit is contained in:
@@ -170,6 +170,10 @@ export function CodingChatPanel({
|
||||
? `new:${activeProject.id}:${selectedAgent.id}`
|
||||
: null;
|
||||
const draftKey = targetConversationId ?? provisionalDraftKey;
|
||||
const abortConversation = useCallback(async (conversationId: string) => {
|
||||
await abortCodingConversation(conversationId);
|
||||
await loadConversationSnapshot(conversationId, 'silent');
|
||||
}, [loadConversationSnapshot]);
|
||||
const promptMode = draftKey ? modesByDraftKey[draftKey] ?? 'prompt' : 'prompt';
|
||||
const provisionalDraft = provisionalDraftKey ? provisionalDrafts[provisionalDraftKey] ?? '' : '';
|
||||
const submissionError = draftKey ? submissionErrors[draftKey] ?? null : null;
|
||||
@@ -598,6 +602,9 @@ export function CodingChatPanel({
|
||||
onRename={async (title) => {
|
||||
if (targetConversationId) await patchConversation(targetConversationId, { title });
|
||||
}}
|
||||
onAbort={async () => {
|
||||
if (targetConversationId) await abortConversation(targetConversationId);
|
||||
}}
|
||||
onRecover={async () => {
|
||||
if (targetConversationId) await recoverConversation(targetConversationId);
|
||||
}}
|
||||
@@ -698,13 +705,15 @@ export function CodingChatPanel({
|
||||
onSubmit={handleSubmit}
|
||||
onAbort={() => {
|
||||
if (!targetConversationId) return;
|
||||
void abortCodingConversation(targetConversationId).catch((error) => {
|
||||
if (!draftKey) return;
|
||||
setSubmissionErrors((current) => ({
|
||||
...current,
|
||||
[draftKey]: localSubmissionError(error),
|
||||
}));
|
||||
});
|
||||
const conversationId = targetConversationId;
|
||||
void abortConversation(conversationId)
|
||||
.catch((error) => {
|
||||
if (!draftKey) return;
|
||||
setSubmissionErrors((current) => ({
|
||||
...current,
|
||||
[draftKey]: localSubmissionError(error),
|
||||
}));
|
||||
});
|
||||
}}
|
||||
onRecover={() => {
|
||||
if (targetConversationId) {
|
||||
|
||||
@@ -30,6 +30,7 @@ const THINKING_OPTIONS: Array<{ value: ConversationThinkingLevel; label: string
|
||||
{ value: 'low', label: '低' },
|
||||
{ value: 'medium', label: '中等' },
|
||||
{ value: 'high', label: '高' },
|
||||
{ value: 'max', label: '最高' },
|
||||
];
|
||||
|
||||
function thinkingLabel(level: ConversationThinkingLevel): string {
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { abortCodingConversation } from '@/lib/coding-conversations';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import type { ConversationSnapshot } from '@/types/coding-conversation';
|
||||
@@ -54,11 +53,13 @@ export function CodingConversationHeader({
|
||||
conversation,
|
||||
snapshot,
|
||||
onRename,
|
||||
onAbort,
|
||||
onRecover,
|
||||
}: {
|
||||
conversation: CodingConversationMetadata | null;
|
||||
snapshot: ConversationSnapshot | null;
|
||||
onRename(title: string): Promise<void>;
|
||||
onAbort(): Promise<void>;
|
||||
onRecover(): Promise<void>;
|
||||
}) {
|
||||
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||
@@ -128,7 +129,7 @@ export function CodingConversationHeader({
|
||||
disabled={Boolean(busyAction) || !conversation}
|
||||
aria-label="中止"
|
||||
title="中止"
|
||||
onClick={() => perform('abort', async () => abortCodingConversation(conversation!.id))}
|
||||
onClick={() => perform('abort', onAbort)}
|
||||
>
|
||||
{busyAction === 'abort'
|
||||
? <LoaderCircle className="h-4 w-4 animate-spin" aria-hidden="true" />
|
||||
|
||||
@@ -86,6 +86,23 @@ const NODE_STATUS_LABELS: Record<string, string> = {
|
||||
|
||||
type TextualBlock = Exclude<ConversationContentBlock, { kind: 'image' }>;
|
||||
type ThinkingBlock = Extract<ConversationContentBlock, { kind: 'thinking' }>;
|
||||
type CapabilityToolDetails = Extract<KnownToolDetails, { schema: 'makelore-capability.v1' }>;
|
||||
|
||||
function capabilityBillingLabel(billing: CapabilityToolDetails['billing']): string {
|
||||
switch (billing.status) {
|
||||
case 'not_started': return '尚未开始计费';
|
||||
case 'included': return '已包含';
|
||||
case 'external': return '由外部账户结算';
|
||||
case 'reserved': return `已预留 ${billing.reserved_points} Token Point`;
|
||||
case 'dispatched': return `处理中 · 已预留 ${billing.reserved_points} Token Point`;
|
||||
case 'released': return '预留已释放,未收费';
|
||||
case 'pending_review': return '账单待审核';
|
||||
case 'expired': return '预留已过期';
|
||||
case 'settled': return `已结算 ${billing.actual_points} Token Point`;
|
||||
case 'refunded': return `已退款 ${billing.actual_points} Token Point`;
|
||||
case 'receipt_unavailable': return '收费状态未同步,请勿重复发起';
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessItem =
|
||||
| { kind: 'thinking'; id: string; block: ThinkingBlock }
|
||||
@@ -607,6 +624,38 @@ const ToolDetails = memo(function ToolDetails({ details }: { details: KnownToolD
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{details.success ? '成功' : details.error ?? '请求失败'} · HTTP {details.status}
|
||||
</p>
|
||||
<p className="mt-1 text-muted-foreground">{capabilityBillingLabel(details.billing)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (details.schema === 'makelore-model-tool.v1') {
|
||||
return (
|
||||
<div className="rounded-xl bg-foreground/[0.035] p-3 text-xs">
|
||||
<p className="font-semibold">模型联网搜索 · {details.modelId}</p>
|
||||
{details.status === 'succeeded' ? (
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{details.sources.length > 0
|
||||
? `已返回 ${details.sources.length} 个来源`
|
||||
: '提供方未返回结构化来源'}
|
||||
</p>
|
||||
) : (
|
||||
<p className="mt-1 text-destructive">{details.error.code} · {details.error.message}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (details.schema === 'makelore-device-package.v1') {
|
||||
const subject = details.preview?.displayName
|
||||
?? details.index?.packages.at(-1)?.displayName
|
||||
?? '本机包';
|
||||
return (
|
||||
<div className="rounded-xl bg-foreground/[0.035] p-3 text-xs">
|
||||
<p className="font-semibold">本机插件 · {subject}</p>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{details.operation === 'prepare'
|
||||
? `等待确认${details.preview?.includesExecutableCode ? ' · 包含可执行扩展' : ''}`
|
||||
: `操作完成 · 共 ${details.index?.packages.length ?? 0} 个本机包`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -811,6 +860,16 @@ function toolDetailsProgress(details: KnownToolDetails | undefined): string | nu
|
||||
? `${details.operation} · 成功`
|
||||
: `${details.operation} · ${details.error ?? '请求失败'}`;
|
||||
}
|
||||
if (details.schema === 'makelore-model-tool.v1') {
|
||||
return details.status === 'succeeded'
|
||||
? `联网搜索 · ${details.sources.length} 个来源`
|
||||
: `联网搜索 · ${details.error.code}`;
|
||||
}
|
||||
if (details.schema === 'makelore-device-package.v1') {
|
||||
return details.operation === 'prepare'
|
||||
? `本机插件 · 等待确认安装 ${details.preview?.displayName ?? ''}`.trim()
|
||||
: `本机插件 · ${details.operation}`;
|
||||
}
|
||||
const task = details.tasks.find((candidate) => (
|
||||
candidate.status === 'running' || candidate.status === 'queued'
|
||||
)) ?? details.tasks.at(-1);
|
||||
|
||||
Reference in New Issue
Block a user