File size: 4,013 Bytes
f59fbe2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import { useState } from "react";
import { IconTrash, IconArrowUp, IconPencil, IconCheck, IconX, IconBolt } from "@tabler/icons-react";
import { Button } from "@/components/ui/button";
import { useChatStore } from "@/stores";
import { bridge } from "@/services";
import { Content } from "@/lib/content";

import type { ContentPart } from "shared/legacy-sdk";

function QueueItem({ id, content, isStreaming, onEdit }: { id: string; content: string | ContentPart[]; isStreaming: boolean; onEdit: (id: string) => void }) {
  const { removeFromQueue, moveQueueItemUp, queue } = useChatStore();
  const text = Content.getText(content);
  const hasMedia = Content.hasMedia(content);
  const isFirst = queue[0]?.id === id;

  const handleSteer = async () => {
    const result = await bridge.steerChat(content);
    if (result.ok) {
      removeFromQueue(id);
    }
  };

  return (
    <div className="group flex items-start px-2.5 py-0.5 hover:bg-muted/50 transition-colors">
      <div className="flex-1 min-w-0">
        <p className="text-xs line-clamp-2 text-foreground">{text || (hasMedia ? "(media)" : "")}</p>
        {hasMedia && text && <span className="text-[10px] text-muted-foreground">+ media</span>}
      </div>
      <div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
        {isStreaming && (
          <Button
            variant="ghost"
            size="icon"
            className="size-5 border-0! text-amber-500 hover:text-amber-600"
            onClick={() => {
              void handleSteer();
            }}
            title="Insert now (steer)"
          >
            <IconBolt className="size-3" />
          </Button>
        )}
        <Button variant="ghost" size="icon" className="size-5 border-0!" onClick={() => onEdit(id)}>
          <IconPencil className="size-3" />
        </Button>
        {!isFirst && (
          <Button variant="ghost" size="icon" className="size-5 border-0!" onClick={() => moveQueueItemUp(id)}>
            <IconArrowUp className="size-3" />
          </Button>
        )}
        <Button variant="ghost" size="icon" className="size-5 border-0! text-muted-foreground hover:text-destructive" onClick={() => removeFromQueue(id)}>
          <IconTrash className="size-3" />
        </Button>
      </div>
    </div>
  );
}

function EditingItem({ id, initialContent, onDone }: { id: string; initialContent: string; onDone: () => void }) {
  const [text, setText] = useState(initialContent);
  const { editQueueItem } = useChatStore();

  const handleSave = () => {
    if (text.trim()) {
      editQueueItem(id, text);
    }
    onDone();
  };

  return (
    <div className="flex items-center px-2.5 py-0.5">
      <input
        autoFocus
        value={text}
        onChange={(e) => setText(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === "Enter") handleSave();
          if (e.key === "Escape") onDone();
        }}
        className="flex-1 min-w-0 text-xs bg-transparent border-b border-border outline-none py-0.5"
      />
      <Button variant="ghost" size="icon" className="size-5 border-0!" onClick={handleSave}>
        <IconCheck className="size-3" />
      </Button>
      <Button variant="ghost" size="icon" className="size-5 border-0!" onClick={onDone}>
        <IconX className="size-3" />
      </Button>
    </div>
  );
}

export function QueuedMessagesPanel() {
  const { queue, isStreaming } = useChatStore();
  const [editingId, setEditingId] = useState<string | null>(null);

  if (queue.length === 0) return null;

  return (
    <div className="max-h-48 overflow-y-auto bg-card shrink">
      {queue.map((item) =>
        editingId === item.id ? (
          <EditingItem key={item.id} id={item.id} initialContent={Content.getText(item.content)} onDone={() => setEditingId(null)} />
        ) : (
          <QueueItem key={item.id} id={item.id} content={item.content} isStreaming={isStreaming} onEdit={setEditingId} />
        ),
      )}
    </div>
  );
}