chore(integration): snapshot local main worktree
This commit is contained in:
@@ -9,13 +9,6 @@ import type {
|
||||
ConversationInteractionResponse,
|
||||
} from '@/types/coding-conversation';
|
||||
|
||||
function settledLabel(status: ConversationInteraction['status']): string {
|
||||
if (status === 'answered') return '已回答';
|
||||
if (status === 'cancelled') return '已取消';
|
||||
if (status === 'rejected') return '已失效';
|
||||
return '等待回答';
|
||||
}
|
||||
|
||||
export function CodingInteractionPanel({
|
||||
conversationId,
|
||||
interactions,
|
||||
@@ -28,8 +21,9 @@ export function CodingInteractionPanel({
|
||||
const [values, setValues] = useState<Record<string, string>>({});
|
||||
const [busyId, setBusyId] = useState<string | null>(null);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const pendingInteractions = interactions.filter(({ status }) => status === 'pending');
|
||||
|
||||
if (interactions.length === 0) return null;
|
||||
if (pendingInteractions.length === 0) return null;
|
||||
|
||||
const respond = (response: ConversationInteractionResponse) => {
|
||||
if (busyId) return;
|
||||
@@ -56,53 +50,81 @@ export function CodingInteractionPanel({
|
||||
aria-label="等待回答的 Agent 交互"
|
||||
data-testid="coding-interactions"
|
||||
>
|
||||
{interactions.map((interaction) => {
|
||||
const pending = interaction.status === 'pending';
|
||||
{pendingInteractions.map((interaction) => {
|
||||
const busy = busyId === interaction.id;
|
||||
const value = values[interaction.id] ?? '';
|
||||
return (
|
||||
<div key={interaction.id} className="rounded-2xl bg-brand-soft/70 p-3 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]">
|
||||
<div key={interaction.id} className="rounded-lg border border-brand/20 bg-brand-soft/55 p-3 shadow-soft">
|
||||
<div className="flex items-start gap-2">
|
||||
<CircleHelp className="mt-0.5 h-4 w-4 shrink-0 text-brand" aria-hidden="true" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex gap-2">
|
||||
<p className="min-w-0 flex-1 text-sm font-semibold">{interaction.title}</p>
|
||||
<span className="text-xs text-muted-foreground">{settledLabel(interaction.status)}</span>
|
||||
<span className="text-xs text-muted-foreground">等待回答</span>
|
||||
</div>
|
||||
{interaction.message && <p className="mt-1 text-pretty text-xs leading-5 text-muted-foreground">{interaction.message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{pending && interaction.kind === 'select' && (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{(interaction.options ?? []).map((option) => (
|
||||
<Button
|
||||
key={option.id}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="h-auto min-h-10 rounded-xl px-3 py-2 text-left"
|
||||
{interaction.kind === 'select' && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(interaction.options ?? []).map((option) => (
|
||||
<Button
|
||||
key={option.id}
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="h-auto min-h-9 rounded-lg px-3 py-2 text-left"
|
||||
disabled={Boolean(busyId)}
|
||||
title={option.description}
|
||||
onClick={() => respond({ interactionId: interaction.id, optionId: option.id })}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<form
|
||||
className="flex items-center gap-2"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const answer = value.trim();
|
||||
if (answer) respond({ interactionId: interaction.id, value: answer });
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
value={value}
|
||||
disabled={Boolean(busyId)}
|
||||
title={option.description}
|
||||
onClick={() => respond({ interactionId: interaction.id, optionId: option.id })}
|
||||
aria-label={`${interaction.title}的其他回答`}
|
||||
placeholder="输入其他回答"
|
||||
className="rounded-lg bg-background"
|
||||
onChange={(event) => setValues((current) => ({
|
||||
...current,
|
||||
[interaction.id]: event.target.value,
|
||||
}))}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
className="min-h-9 shrink-0 rounded-lg"
|
||||
disabled={Boolean(busyId) || !value.trim()}
|
||||
>
|
||||
{option.label}
|
||||
提交回答
|
||||
</Button>
|
||||
))}
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pending && interaction.kind === 'confirm' && (
|
||||
{interaction.kind === 'confirm' && (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Button type="button" className="min-h-10 rounded-xl" disabled={Boolean(busyId)} onClick={() => respond({ interactionId: interaction.id, confirmed: true })}>
|
||||
<Button type="button" className="min-h-9 rounded-lg" disabled={Boolean(busyId)} onClick={() => respond({ interactionId: interaction.id, confirmed: true })}>
|
||||
<Check className="mr-1.5 h-4 w-4" aria-hidden="true" />确认
|
||||
</Button>
|
||||
<Button type="button" variant="outline" className="min-h-10 rounded-xl" disabled={Boolean(busyId)} onClick={() => respond({ interactionId: interaction.id, confirmed: false })}>
|
||||
<Button type="button" variant="outline" className="min-h-9 rounded-lg" disabled={Boolean(busyId)} onClick={() => respond({ interactionId: interaction.id, confirmed: false })}>
|
||||
不确认
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pending && (interaction.kind === 'input' || interaction.kind === 'editor') && (
|
||||
{(interaction.kind === 'input' || interaction.kind === 'editor') && (
|
||||
<div className="mt-3 space-y-2">
|
||||
{interaction.kind === 'editor'
|
||||
? (
|
||||
@@ -111,7 +133,7 @@ export function CodingInteractionPanel({
|
||||
rows={5}
|
||||
disabled={Boolean(busyId)}
|
||||
aria-label={`${interaction.title}的回答`}
|
||||
className="resize-y rounded-xl bg-background"
|
||||
className="resize-y rounded-lg bg-background"
|
||||
onChange={(event) => setValues((current) => ({ ...current, [interaction.id]: event.target.value }))}
|
||||
/>
|
||||
)
|
||||
@@ -120,24 +142,22 @@ export function CodingInteractionPanel({
|
||||
value={value}
|
||||
disabled={Boolean(busyId)}
|
||||
aria-label={`${interaction.title}的回答`}
|
||||
className="rounded-xl bg-background"
|
||||
className="rounded-lg bg-background"
|
||||
onChange={(event) => setValues((current) => ({ ...current, [interaction.id]: event.target.value }))}
|
||||
/>
|
||||
)}
|
||||
<Button type="button" className="min-h-10 rounded-xl" disabled={Boolean(busyId) || !value.trim()} onClick={() => respond({ interactionId: interaction.id, value })}>
|
||||
<Button type="button" className="min-h-9 rounded-lg" disabled={Boolean(busyId) || !value.trim()} onClick={() => respond({ interactionId: interaction.id, value })}>
|
||||
提交回答
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pending && (
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<Button type="button" variant="ghost" className="min-h-10 rounded-xl px-3 text-muted-foreground" disabled={Boolean(busyId)} onClick={() => respond({ interactionId: interaction.id, cancelled: true })}>
|
||||
<X className="mr-1.5 h-4 w-4" aria-hidden="true" />取消请求
|
||||
</Button>
|
||||
{busy && <LoaderCircle className="h-4 w-4 animate-spin text-muted-foreground" aria-label="正在提交回答" />}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<Button type="button" variant="ghost" className="min-h-9 rounded-lg px-3 text-muted-foreground" disabled={Boolean(busyId)} onClick={() => respond({ interactionId: interaction.id, cancelled: true })}>
|
||||
<X className="mr-1.5 h-4 w-4" aria-hidden="true" />取消请求
|
||||
</Button>
|
||||
{busy && <LoaderCircle className="h-4 w-4 animate-spin text-muted-foreground" aria-label="正在提交回答" />}
|
||||
</div>
|
||||
{errors[interaction.id] && <p className="mt-2 text-xs text-destructive" role="alert">{errors[interaction.id]}</p>}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user