File size: 4,337 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
import { useState } from "react";
import { IconPlus, IconChevronDown, IconInfoCircle } from "@tabler/icons-react";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { StreamingConfirmDialog } from "./StreamingConfirmDialog";
import { KimiLogo } from "./KimiLogo";
import { SessionList } from "./SessionList";
import { useChatStore } from "@/stores";
import { ChatStatus, TokenInfo } from "./ChatStatus";

export function Header() {
  const [showSessionList, setShowSessionList] = useState(false);
  const [showSessionInfo, setShowSessionInfo] = useState(false);
  const [showConfirmNew, setShowConfirmNew] = useState(false);
  const { startNewConversation, sessionId, messages, isStreaming } = useChatStore();

  const handleNewSession = async () => {
    // If streaming, show confirmation dialog
    if (isStreaming) {
      setShowConfirmNew(true);
      return;
    }

    await doStartNewSession();
  };

  const doStartNewSession = async () => {
    await startNewConversation();
    setShowSessionList(false);
    setShowConfirmNew(false);
  };

  return (
    <header className="flex items-center justify-between px-3 py-2 border-b border-border shrink-0 @container">
      <div className="flex items-center gap-2 shrink-0">
        <KimiLogo className="size-5 shrink-0" />
        <span className="text-sm font-semibold whitespace-nowrap">Kimi Code</span>
      </div>
      <div className="flex items-center gap-1">
        {sessionId && (
          <Button
            variant="ghost"
            size="xs"
            className="gap-1 h-6 border-0! pl-px! pr-1! text-muted-foreground hover:text-foreground @max-[320px]:hidden"
            onClick={() => setShowSessionInfo(true)}
          >
            <span className="text-[11px] @max-[500px]:hidden">Session</span>
            <IconInfoCircle className="size-3.5 hidden @max-[500px]:block" />
          </Button>
        )}
        <ChatStatus />
        <Popover open={showSessionList} onOpenChange={setShowSessionList}>
          <PopoverTrigger asChild>
            <Button variant="ghost" size="xs" className="gap-1 h-6">
              <span className="text-xs @max-[280px]:hidden">History</span>
              <IconChevronDown className="size-3.5" />
            </Button>
          </PopoverTrigger>
          <PopoverContent align="end" className="w-[20rem] max-w-[calc(100vw-1rem)] p-0">
            <SessionList onClose={() => setShowSessionList(false)} />
          </PopoverContent>
        </Popover>
        <Button
          variant="ghost"
          size="icon-xs"
          onClick={() => {
            void handleNewSession();
          }}
        >
          <IconPlus className="size-3.5" />
        </Button>
      </div>

      <Dialog open={showSessionInfo} onOpenChange={setShowSessionInfo}>
        <DialogContent className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle className="text-sm">Session Details</DialogTitle>
            <DialogDescription className="text-xs">Details for this conversation.</DialogDescription>
          </DialogHeader>
          <div className="space-y-3 py-2">
            <div>
              <div className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1">Session ID</div>
              <code className="text-xs font-mono text-foreground break-all select-all">{sessionId}</code>
            </div>
            <div>
              <div className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1">Messages</div>
              <span className="text-xs text-foreground">{messages.length}</span>
            </div>
            <TokenInfo />
          </div>
        </DialogContent>
      </Dialog>

      <StreamingConfirmDialog
        open={showConfirmNew}
        onOpenChange={(open) => !open && setShowConfirmNew(false)}
        title="Start New Conversation?"
        description="The current conversation is still generating a response. Starting a new one will truncate the output. Are you sure you want to continue?"
        confirmLabel="New Conversation"
        onConfirm={() => {
          void doStartNewSession();
        }}
      />
    </header>
  );
}