File size: 5,320 Bytes
aa63765
 
 
345b8ff
aa63765
 
 
 
 
 
 
 
 
 
 
345b8ff
aa63765
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345b8ff
 
 
 
 
 
 
aa63765
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"use client";

import { useMemo, useState } from "react";
import { useTenant } from "@/contexts/TenantContext";

type Message = {
  role: "user" | "assistant" | "system";
  content: string;
  meta?: string;
};

const API_BASE =
  process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") || "http://localhost:8000";

export function ChatPanel() {
  const { tenantId } = useTenant();
  const [message, setMessage] = useState("");
  const [isSending, setIsSending] = useState(false);
  const [history, setHistory] = useState<Message[]>([
    {
      role: "assistant",
      content:
        "Hi there! I’m the IntegraChat orchestrator. Ask anything about your tenant data and I will route the right MCP tools.",
      meta: "Agent ready",
    },
  ]);
  const [lastDecision, setLastDecision] = useState<string | null>(null);

  const conversationPayload = useMemo(
    () =>
      history
        .filter((m) => m.role !== "system")
        .map((m) => ({
          role: m.role,
          content: m.content,
        })),
    [history],
  );

  async function handleSend() {
    if (!message.trim() || isSending) return;
    const userMessage: Message = { role: "user", content: message.trim() };
    const optimisticHistory = [...history, userMessage];
    setHistory(optimisticHistory);
    setMessage("");
    setIsSending(true);

    try {
      const response = await fetch(`${API_BASE}/agent/message`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          tenant_id: tenantId,
          message: userMessage.content,
          conversation_history: conversationPayload,
          temperature: 0,
        }),
      });

      if (!response.ok) {
        throw new Error(
          `API error (${response.status}) – check backend/api/main.py`,
        );
      }

      const data = await response.json();
      const assistantText =
        data?.text ??
        "Agent responded but text field was empty. Inspect FastAPI logs for clues.";
      setHistory((prev) => [
        ...prev,
        {
          role: "assistant",
          content: assistantText,
          meta: data?.decision?.reason ?? "response",
        },
      ]);
      setLastDecision(
        data?.decision
          ? `${data.decision.action} · ${data.decision.tool ?? "llm"}`
          : null,
      );
    } catch (err) {
      console.error(err);
      setHistory((prev) => [
        ...prev,
        {
          role: "assistant",
          content:
            err instanceof Error
              ? err.message
              : "Failed to reach the FastAPI gateway.",
          meta: "error",
        },
      ]);
      setLastDecision("error");
    } finally {
      setIsSending(false);
    }
  }

  return (
    <section
      id="chat"
      className="gradient-border relative rounded-[28px] p-1 text-white"
    >
      <div className="glass-panel relative rounded-[26px] p-6">
        <div>
          <p className="text-sm uppercase tracking-[0.5em] text-cyan-200/70">
            Orchestrator Console
          </p>
          <h2 className="mt-2 text-3xl font-semibold">
            Talk to your enterprise agent
          </h2>
        </div>
        <div className="mt-6 h-[360px] space-y-3 overflow-y-auto rounded-2xl border border-white/10 bg-slate-950/40 p-4 scrollArea">
          {history.map((msg, idx) => (
            <div
              key={`${msg.role}-${idx}`}
              className={`flex gap-3 rounded-2xl px-4 py-3 ${
                msg.role === "user"
                  ? "bg-slate-900/70 text-slate-100"
                  : "bg-cyan-500/10 text-slate-100"
              }`}
            >
              <span className="text-xs font-semibold uppercase tracking-widest text-cyan-200/80">
                {msg.role}
              </span>
              <div className="space-y-1 text-sm">
                <p>{msg.content}</p>
                {msg.meta && (
                  <p className="text-xs text-slate-400">{msg.meta}</p>
                )}
              </div>
            </div>
          ))}
        </div>

        <div className="mt-5 flex flex-col gap-3 md:flex-row">
          <textarea
            placeholder="Ask about policies, knowledge base hits, or route through RAG/Web/Admin..."
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            className="flex-1 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white outline-none focus:border-cyan-200/80"
            rows={3}
          />
          <button
            onClick={handleSend}
            disabled={isSending}
            className="min-w-[160px] rounded-2xl bg-gradient-to-r from-sky-400 to-cyan-500 px-6 py-3 font-semibold text-slate-950 shadow-lg shadow-cyan-500/30 transition hover:-translate-y-0.5 disabled:cursor-not-allowed disabled:opacity-60"
          >
            {isSending ? "Routing…" : "Send to MCP"}
          </button>
        </div>

        <div className="mt-4 flex items-center gap-3 text-sm text-slate-300">
          <span className="h-2 w-2 rounded-full bg-emerald-400 shadow-[0_0_12px_#34d399]" />
          {lastDecision
            ? `Last decision: ${lastDecision}`
            : "No tool invocation yet"}
        </div>
      </div>
    </section>
  );
}