File size: 4,252 Bytes
921d377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"use client";

import { useEffect, useRef, useState } from "react";
import MessageList from "./MessageList";
import Composer from "./Composer";

type ActivityStage = "idle" | "parsing" | "thinking";

function ExpertActivity({ stage }: { stage: ActivityStage }) {
  if (stage === "idle") return null;

  const isParsing = stage === "parsing";

  return (
    <div
      style={{
        background: "#080808",
        borderRadius: 16,
        padding: "16px 18px",
        marginBottom: 14,
        border: "1px solid rgba(60,60,70,0.35)",
        color: "#ddd",
      }}
    >
      <style>{`
        @keyframes hpDotBounce {
          0%, 80%, 100% { transform: translateY(0); opacity: 0.7; }
          40% { transform: translateY(-6px); opacity: 1; }
        }
        @keyframes hpGrokPulse {
          0% { background: #222; transform: scale(1); opacity: .75; box-shadow: 0 0 2px rgba(255,255,255,.1); }
          8% { background: #fff; transform: scale(1.55); opacity: 1; box-shadow: 0 0 8px rgba(255,255,255,.9), 0 0 14px rgba(180,220,255,.6); }
          18% { background: #aaa; transform: scale(1.15); opacity: .9; box-shadow: 0 0 4px rgba(255,255,255,.4); }
          28%, 100% { background: #222; transform: scale(1); opacity: .75; box-shadow: 0 0 2px rgba(255,255,255,.08); }
        }
      `}</style>
      <div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 10 }}>
        {isParsing ? (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 5px)", gridTemplateRows: "repeat(3, 5px)", gap: 7 }}>
            {Array.from({ length: 9 }).map((_, i) => (
              <span
                key={i}
                style={{
                  width: 5,
                  height: 5,
                  borderRadius: "50%",
                  animation: "hpGrokPulse 2.4s infinite ease-in-out",
                  animationDelay: `${i * 0.2}s`,
                }}
              />
            ))}
          </div>
        ) : (
          <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
            {[0, 1, 2].map((i) => (
              <span
                key={i}
                style={{
                  width: 6,
                  height: 6,
                  borderRadius: "50%",
                  background: "#888",
                  animation: "hpDotBounce 1.3s infinite ease-in-out",
                  animationDelay: `${i * 0.15}s`,
                }}
              />
            ))}
          </div>
        )}
        <div style={{ color: "#7aaae0", fontSize: 15 }}>
          {isParsing ? "Parsing question · sending to backend" : "Thinking"}
        </div>
      </div>
      <div style={{ color: "#8e8e9a", fontSize: 13 }}>
        {isParsing
          ? "Analyzing query structure and preparing tool context."
          : "Processing reasoning step-by-step."}
      </div>
    </div>
  );
}

export default function ChatWindow() {
  const [messages, setMessages] = useState<string[]>([]);
  const [stage, setStage] = useState<ActivityStage>("idle");
  const parseTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    return () => {
      if (parseTimer.current) clearTimeout(parseTimer.current);
    };
  }, []);

  async function sendMessage(text: string) {
    setStage("parsing");
    parseTimer.current = setTimeout(() => setStage("thinking"), 1200);

    try {
      const resp = await fetch("http://localhost:8000/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ session_id: "default", message: text }),
      });
      const data = await resp.json();

      if (parseTimer.current) clearTimeout(parseTimer.current);
      setStage("thinking");

      setMessages((prev) => [...prev, `User: ${text}`, `Expert: ${data.answer}`]);
    } catch (err) {
      if (parseTimer.current) clearTimeout(parseTimer.current);
      setMessages((prev) => [...prev, `User: ${text}`, `Expert: Failed to get response: ${String(err)}`]);
    } finally {
      setStage("idle");
    }
  }

  return (
    <div style={{ padding: 24 }}>
      <MessageList messages={messages} />
      <ExpertActivity stage={stage} />
      <Composer onSend={sendMessage} />
    </div>
  );
}