返回 DeepSeek-Reasonix
InboxQueueRow.tsx
根目录 / desktop / frontend / src / components / InboxQueueRow.tsx
1 // Adapted from zai-org/ZCode ConversationQueuePanel.tsx at 872ad960.
2 // Apache-2.0; see licenses/ZCode.txt. Changed: Reasonix controls, keyboard
3 // sorting, explicit retry, and edit-in-place rather than withdrawal.
4 import { useRef, useState, type ReactNode } from "react";
5 import { AnchoredPopover } from "./AnchoredPopover";
6 import { useSortable } from "@dnd-kit/sortable";
7 import { CSS } from "@dnd-kit/utilities";
8 import { GripVertical, Pencil, MoreHorizontal, CornerDownRight } from "lucide-react";
9 import type { PendingGuidance } from "./ComposerGuidanceShelf";
10 import { queueActionAnchor } from "../lib/inboxQueueCommands";
11 import { inboxQueueCopy } from "../lib/inboxQueueCopy";
12
13 export function InboxQueueRow({ item, index, ids, locked, editing, children, copy, onEdit, onMove, onDelete, onRetry, onGuide }: {
14 item: PendingGuidance; index: number; ids: string[]; locked: boolean; editing: boolean;
15 copy: ReturnType<typeof inboxQueueCopy>; onEdit: () => void; onMove: (before: string | null) => void;
16 onDelete: () => void; onRetry: () => void; onGuide?: () => void;
17 children?: ReactNode;
18 }) {
19 const [menu, setMenu] = useState(false);
20 const menuAnchor = useRef<HTMLButtonElement>(null);
21 const { attributes, listeners, isDragging, setNodeRef, setActivatorNodeRef, transform, transition } = useSortable({ id: item.id, disabled: locked || editing });
22 return <li ref={setNodeRef} className={`inbox-queue__row${editing ? " inbox-queue__row--editing" : ""}`} data-item-id={item.id}
23 style={{ transform: CSS.Transform.toString(transform ? { ...transform, scaleX: 1, scaleY: 1 } : null), transition, zIndex: isDragging ? 1 : undefined }}>
24 <button type="button" ref={setActivatorNodeRef} className="inbox-queue__handle" disabled={locked || editing} aria-label={`${copy.drag} ${index + 1}`} {...attributes} {...listeners}><GripVertical size={16} /></button>
25 {editing ? children : <>
26 <span className="inbox-queue__ordinal">{index + 1}</span>
27 <span className="inbox-queue__body" title={item.text}><span>{item.text}</span>
28 {item.state !== "queued" && <small>{item.state === "uncertain" ? copy.uncertain : item.blockReason || copy.blocked}</small>}
29 </span>
30 {onGuide && <button type="button" className="inbox-queue__icon" disabled={locked || item.state !== "queued" || item.paused} onClick={onGuide} aria-label={copy.guide} title={copy.guide}><CornerDownRight size={15} /></button>}
31 <button type="button" className="inbox-queue__icon" disabled={locked} onClick={onEdit} aria-label={copy.edit} title={copy.edit}><Pencil size={15} /></button>
32 <div className="inbox-queue__more" onKeyDown={event => { if (event.key === "Escape") { setMenu(false); event.stopPropagation(); } }}>
33 <button ref={menuAnchor} type="button" className="inbox-queue__icon" disabled={locked} aria-label={copy.more} aria-expanded={menu} onClick={() => setMenu(!menu)}><MoreHorizontal size={16} /></button>
34 <AnchoredPopover open={menu} anchorRef={menuAnchor} onClose={() => setMenu(false)} className="inbox-queue__menu" align="end">
35 {(["first", "up", "down", "last"] as const).map(action => {
36 const before = queueActionAnchor(ids, item.id, action);
37 return <button type="button" key={action} disabled={locked || before === undefined} onClick={() => { if (before !== undefined) onMove(before); setMenu(false); }}>{copy[action]}</button>;
38 })}
39 {(item.state === "uncertain" || item.state === "blocked") && <button type="button" disabled={locked} onClick={() => { onRetry(); setMenu(false); }}>{copy.retry}</button>}
40 <button type="button" disabled={locked} onClick={() => { onDelete(); setMenu(false); }}>{copy.remove}</button>
41 </AnchoredPopover>
42 </div>
43 </>}
44 </li>;
45 }
46
46 lines Plain Text