latest changes
This commit is contained in:
@@ -8,6 +8,7 @@ const api = axios.create({
|
|||||||
|
|
||||||
api.interceptors.request.use((config) => {
|
api.interceptors.request.use((config) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
console.log("Attaching token to request:", token);
|
||||||
|
|
||||||
config.headers = config.headers || {};
|
config.headers = config.headers || {};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
RefreshCcw,
|
RefreshCcw,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
|
CheckCircle2,
|
||||||
|
BarChart3,
|
||||||
|
Database,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import ErrorBoundary from "@/components/ErrorBoundary";
|
import ErrorBoundary from "@/components/ErrorBoundary";
|
||||||
@@ -785,40 +788,55 @@ function VegaLiteArtifact({
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
|
|
||||||
{/* TOP TABS */}
|
{/* TOP TABS */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("answer")}
|
onClick={() => setActiveTab("answer")}
|
||||||
className={`rounded-xl px-4 py-2 text-sm font-medium transition ${activeTab === "answer"
|
className={`flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium transition ${
|
||||||
? "bg-cyan-500 text-white"
|
activeTab === "answer"
|
||||||
: "bg-white/10 text-slate-300 hover:bg-white/20"
|
? "bg-cyan-500 text-white"
|
||||||
}`}
|
: "bg-white border border-slate-200 text-black hover:bg-slate-50"
|
||||||
>
|
}`}
|
||||||
Answer
|
>
|
||||||
</button>
|
<CheckCircle2
|
||||||
|
size={16}
|
||||||
|
className={activeTab === "answer" ? "text-white" : "text-green-600"}
|
||||||
|
/>
|
||||||
|
Answer
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("chart")}
|
onClick={() => setActiveTab("chart")}
|
||||||
className={`rounded-xl px-4 py-2 text-sm font-medium transition ${activeTab === "chart"
|
className={`flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium transition ${
|
||||||
? "bg-cyan-500 text-white"
|
activeTab === "chart"
|
||||||
: "bg-white/10 text-slate-300 hover:bg-white/20"
|
? "bg-cyan-500 text-white"
|
||||||
}`}
|
: "bg-white border border-slate-200 text-black hover:bg-slate-50"
|
||||||
>
|
}`}
|
||||||
Chart
|
>
|
||||||
</button>
|
<BarChart3
|
||||||
|
size={16}
|
||||||
|
className={activeTab === "chart" ? "text-white" : "text-blue-600"}
|
||||||
|
/>
|
||||||
|
Chart
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("sql")}
|
onClick={() => setActiveTab("sql")}
|
||||||
className={`rounded-xl px-4 py-2 text-sm font-medium transition ${activeTab === "sql"
|
className={`flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium transition ${
|
||||||
? "bg-cyan-500 text-white"
|
activeTab === "sql"
|
||||||
: "bg-white/10 text-slate-300 hover:bg-white/20"
|
? "bg-cyan-500 text-white"
|
||||||
}`}
|
: "bg-white border border-slate-200 text-black hover:bg-slate-50"
|
||||||
>
|
}`}
|
||||||
SQL
|
>
|
||||||
</button>
|
<Database
|
||||||
</div>
|
size={16}
|
||||||
|
className={activeTab === "sql" ? "text-white" : "text-orange-600"}
|
||||||
|
/>
|
||||||
|
SQL
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* EXPORT BUTTONS */}
|
{/* EXPORT BUTTONS */}
|
||||||
<div className="flex flex-wrap gap-3">
|
{/* <div className="flex flex-wrap gap-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={downloadPDF}
|
onClick={downloadPDF}
|
||||||
className="rounded-xl bg-red-500 hover:bg-red-600"
|
className="rounded-xl bg-red-500 hover:bg-red-600"
|
||||||
@@ -850,7 +868,7 @@ function VegaLiteArtifact({
|
|||||||
<ImageIcon className="mr-2 h-4 w-4" />
|
<ImageIcon className="mr-2 h-4 w-4" />
|
||||||
PNG
|
PNG
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+448
-149
@@ -13,6 +13,7 @@ type ChatMessage = {
|
|||||||
role: "user" | "assistant";
|
role: "user" | "assistant";
|
||||||
content: unknown;
|
content: unknown;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
|
clarification?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createId() {
|
function createId() {
|
||||||
@@ -34,11 +35,11 @@ function createMessage(role: ChatMessage["role"], content: unknown): ChatMessage
|
|||||||
export default function ChatCanvas() {
|
export default function ChatCanvas() {
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||||
// const [selectedHistoryId, setSelectedHistoryId] = useState<string | null>(null);
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
const [showHistory, setShowHistory] = useState(true);
|
const [showHistory, setShowHistory] = useState(true);
|
||||||
const [historyQuestions, setHistoryQuestions] = useState<any[]>([]);
|
const [historyQuestions, setHistoryQuestions] = useState<any[]>([]);
|
||||||
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
|
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -52,28 +53,101 @@ export default function ChatCanvas() {
|
|||||||
loadHistory();
|
loadHistory();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// const loadHistory = async () => {
|
||||||
|
// try {
|
||||||
|
// const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
// const res = await api.get("/fetchUserHistory", {
|
||||||
|
// headers: {
|
||||||
|
// Authorization: `Bearer ${token}`,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log("History Response =>", res.data);
|
||||||
|
|
||||||
|
// if (res.data.success) {
|
||||||
|
// setHistoryQuestions(res.data.data);
|
||||||
|
// }
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error("History Error =>", err);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const loadHistory = async () => {
|
const loadHistory = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/suggestions");
|
const res = await api.get("/fetchUserHistory");
|
||||||
|
|
||||||
|
console.log("History Response =>", res.data);
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
setHistoryQuestions(res.data.questions);
|
setHistoryQuestions(res.data.data);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error("History Error =>", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const history = messages.filter((message) => message.role === "user");
|
// const handleHistoryClick = async (item: any) => {
|
||||||
|
// try {
|
||||||
|
// const token = localStorage.getItem("token");
|
||||||
|
|
||||||
// const handleSelectHistory = (message: ChatMessage) => {
|
// const response = await api.post(
|
||||||
// if (typeof message.content === "string") {
|
// "/fetchHistory",
|
||||||
// setInput(message.content);
|
// {
|
||||||
// setSelectedHistoryId(message.id);
|
// response_id: item.response_id,
|
||||||
// setError("");
|
// },
|
||||||
|
// {
|
||||||
|
// headers: {
|
||||||
|
// Authorization: `Bearer ${token}`,
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log("History Detail =>", response.data);
|
||||||
|
|
||||||
|
// if (response.data.success) {
|
||||||
|
// const historyData = response.data.data;
|
||||||
|
|
||||||
|
// setMessages([
|
||||||
|
// createMessage("user", historyData.prompt),
|
||||||
|
// createMessage("assistant", historyData),
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error("Fetch History Error", err);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
const handleHistoryClick = async (item: any) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post(
|
||||||
|
"/fetchHistory",
|
||||||
|
{
|
||||||
|
response_id: item.response_id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("History Detail =>", response.data);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
const historyData = response.data.data;
|
||||||
|
|
||||||
|
setMessages([
|
||||||
|
createMessage("user", historyData.prompt),
|
||||||
|
createMessage("assistant", historyData),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fetch History Error", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const onSubmit = async (event: React.FormEvent) => {
|
const onSubmit = async (event: React.FormEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -96,10 +170,29 @@ export default function ChatCanvas() {
|
|||||||
JSON.stringify(response.data, null, 2)
|
JSON.stringify(response.data, null, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
setMessages((current) => [
|
// setMessages((current) => [
|
||||||
...current,
|
// ...current,
|
||||||
createMessage("assistant", response.data),
|
// createMessage("assistant", response.data),
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
|
if (response.data.type === "clarification") {
|
||||||
|
setMessages((current) => [
|
||||||
|
...current,
|
||||||
|
{
|
||||||
|
id: createId(),
|
||||||
|
role: "assistant",
|
||||||
|
clarification: true,
|
||||||
|
content: response.data.message,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
setMessages((current) => [
|
||||||
|
...current,
|
||||||
|
createMessage("assistant", response.data),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -146,140 +239,260 @@ export default function ChatCanvas() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <main className="min-h-screen bg-muted/30 pb-36">
|
<main className="min-h-screen bg-gradient-to-br from-white via-slate-50 to-gray-100 pb-36">
|
||||||
<main className="min-h-screen bg-gradient-to-br from-white via-slate-50 to-gray-100 pb-36">
|
|
||||||
{/* <header className="sticky top-0 z-10 border-b bg-background px-6 py-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-lg font-semibold">GenBI</h1>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Chat workspace
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={logout}
|
|
||||||
className="h-9"
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header> */}
|
|
||||||
|
|
||||||
{/* <header className="sticky top-0 z-10 border-b bg-background px-6 py-3"> */}
|
{/* <header className="sticky top-0 z-10 border-b bg-background px-6 py-3"> */}
|
||||||
<header className="sticky top-0 z-50 border-b border-slate-200/70 bg-white/80 backdrop-blur-xl px-8 py-4 shadow-sm">
|
<header className="sticky top-0 z-50 border-b border-slate-200 bg-white px-6 py-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold bg-gradient-to-r from-indigo-600 to-cyan-600 bg-clip-text text-transparent">GenBI</h1>
|
|
||||||
<p className="text-sm text-slate-500">
|
|
||||||
Chat workspace
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2 mt-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowHistory(!showHistory)}
|
onClick={() => setShowHistory(!showHistory)}
|
||||||
className="rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-all hover:-translate-y-0.5 hover:bg-slate-50 hover:shadow-md"
|
className="rounded-lg border px-3 py-1.5 text-sm hover:bg-slate-100"
|
||||||
>
|
|
||||||
☰ {showHistory ? "Hide History" : "Show History"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{/* <Button
|
|
||||||
onClick={() => setShowHistory(!showHistory)}
|
|
||||||
className="border bg-white text-black hover:bg-slate-100"
|
|
||||||
>
|
|
||||||
{showHistory ? "Hide History" : "Show History"}
|
|
||||||
</Button> */}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={logout}
|
|
||||||
className="h-9"
|
|
||||||
>
|
>
|
||||||
Logout
|
☰
|
||||||
</Button>
|
</button>
|
||||||
|
|
||||||
|
<h1 className="text-xl font-semibold">
|
||||||
|
Yoda
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* <section className="mx-auto flex flex-col gap-4 px-4 py-6 lg:flex-row"> */}
|
{/* <section className="mx-auto flex flex-col gap-4 px-4 py-6 lg:flex-row"> */}
|
||||||
<section className="mx-auto flex gap-6 px-4 py-6">
|
<section className="mx-auto flex gap-6 px-4 py-6">
|
||||||
{showHistory && (<aside
|
{showHistory && (
|
||||||
className="hidden lg:flex lg:w-80 lg:flex-col lg:sticky lg:top-24 h-[calc(100vh-140px)] overflow-y-auto rounded-3xl border border-slate-200 bg-white/90 backdrop-blur-xl p-5 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
|
||||||
<div className="mb-4">
|
|
||||||
<p className="text-xs uppercase tracking-[0.25em] text-muted-foreground">
|
|
||||||
History
|
|
||||||
</p>
|
|
||||||
<h2 className="mt-2 text-xl font-bold text-slate-900">
|
|
||||||
Recent prompts
|
|
||||||
</h2>
|
|
||||||
<p className="mt-2 text-sm text-muted-foreground">
|
|
||||||
Tap a prompt to reload it into the composer.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <div className="space-y-2">
|
// <aside
|
||||||
{history.length === 0 ? (
|
// className="hidden lg:flex lg:w-80 lg:flex-col lg:sticky lg:top-24 h-[calc(100vh-140px)] overflow-y-auto rounded-3xl border border-slate-200 bg-white/90 backdrop-blur-xl p-5 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
||||||
<div className="rounded-3xl border border-border/80 bg-muted/70 p-4 text-sm text-muted-foreground">
|
// <div className="mb-4">
|
||||||
Your latest prompts will appear here as you chat.
|
// <p className="text-xs uppercase tracking-[0.25em] text-muted-foreground">
|
||||||
</div>
|
// History
|
||||||
) : (
|
// </p>
|
||||||
history
|
// <h2 className="mt-2 text-xl font-bold text-slate-900">
|
||||||
.slice(-10)
|
// Recent prompts
|
||||||
.reverse()
|
// </h2>
|
||||||
.map((message) => (
|
// <p className="mt-2 text-sm text-muted-foreground">
|
||||||
<button
|
// Tap a prompt to reload it into the composer.
|
||||||
key={message.id}
|
// </p>
|
||||||
type="button"
|
// </div>
|
||||||
onClick={() => handleSelectHistory(message)}
|
|
||||||
className={cn(
|
|
||||||
"w-full rounded-3xl border px-3 py-3 text-left text-sm transition",
|
// <div className="space-y-2">
|
||||||
selectedHistoryId === message.id
|
// {historyQuestions.length === 0 ? (
|
||||||
? "border-primary bg-primary/10"
|
// <div className="rounded-3xl border border-border/80 bg-muted/70 p-4 text-sm text-muted-foreground">
|
||||||
: "border-border/70 bg-muted/80 hover:bg-muted/90",
|
// No recent prompts found.
|
||||||
)}
|
// </div>
|
||||||
>
|
// ) : (
|
||||||
<span className="block text-sm text-foreground">
|
// historyQuestions.map((item, index) => (
|
||||||
{typeof message.content === "string"
|
// <button
|
||||||
? message.content
|
// key={item.response_id}
|
||||||
: JSON.stringify(message.content, null, 2)}
|
// type="button"
|
||||||
</span>
|
// onClick={() => handleHistoryClick(item)}
|
||||||
</button>
|
// className="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-left text-sm transition-all duration-200 hover:-translate-y-1 hover:border-indigo-200 hover:bg-indigo-50/40 hover:shadow-lg"
|
||||||
))
|
// >
|
||||||
)}
|
// <span className="block text-sm text-foreground">
|
||||||
</div> */}
|
// {item.prompt}
|
||||||
<div className="space-y-2">
|
// </span>
|
||||||
{historyQuestions.length === 0 ? (
|
|
||||||
<div className="rounded-3xl border border-border/80 bg-muted/70 p-4 text-sm text-muted-foreground">
|
// <p className="text-xs text-slate-500 mt-1">
|
||||||
No recent prompts found.
|
// {new Date(item.created_at).toLocaleString()}
|
||||||
</div>
|
// </p>
|
||||||
) : (
|
// </button>
|
||||||
historyQuestions.map((item, index) => (
|
// ))
|
||||||
<button
|
// )}
|
||||||
key={index}
|
// </div>
|
||||||
type="button"
|
|
||||||
onClick={() => setInput(item.question)}
|
// <div className="border-t p-4">
|
||||||
className="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-left text-sm transition-all duration-200 hover:-translate-y-1 hover:border-indigo-200 hover:bg-indigo-50/40 hover:shadow-lg"
|
// <Button
|
||||||
|
// // variant="outline"
|
||||||
|
// onClick={logout}
|
||||||
|
// className="w-full"
|
||||||
|
// >
|
||||||
|
// Logout
|
||||||
|
// </Button>
|
||||||
|
// </div>
|
||||||
|
// </aside>
|
||||||
|
|
||||||
|
<aside
|
||||||
|
className="
|
||||||
|
hidden
|
||||||
|
lg:flex
|
||||||
|
lg:w-72
|
||||||
|
lg:flex-col
|
||||||
|
lg:sticky
|
||||||
|
lg:top-20
|
||||||
|
h-[calc(100vh-90px)]
|
||||||
|
overflow-hidden
|
||||||
|
rounded-3xl
|
||||||
|
border
|
||||||
|
border-slate-200
|
||||||
|
bg-white/80
|
||||||
|
backdrop-blur-xl
|
||||||
|
shadow-[0_8px_30px_rgba(0,0,0,0.06)]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="border-b border-slate-100 px-4 py-4">
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">
|
||||||
|
Recents
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* History List */}
|
||||||
|
<div className="flex-1 overflow-y-auto p-3">
|
||||||
|
{historyQuestions.length === 0 ? (
|
||||||
|
<div className="rounded-xl bg-slate-50 p-4 text-sm text-slate-500">
|
||||||
|
No recent prompts found
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{historyQuestions.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.response_id}
|
||||||
|
onClick={() => handleHistoryClick(item)}
|
||||||
|
className="
|
||||||
|
w-full
|
||||||
|
rounded-xl
|
||||||
|
px-3
|
||||||
|
py-3
|
||||||
|
text-left
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
hover:bg-indigo-50
|
||||||
|
hover:text-indigo-700
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div className="truncate text-sm font-medium">
|
||||||
|
{item.prompt}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 text-xs text-slate-400">
|
||||||
|
{new Date(item.created_at).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profile Section */}
|
||||||
|
<div className="relative border-t border-slate-100 p-3 bg-gradient-to-r from-slate-50 to-white">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
||||||
|
className="
|
||||||
|
flex
|
||||||
|
w-full
|
||||||
|
items-center
|
||||||
|
gap-3
|
||||||
|
rounded-2xl
|
||||||
|
border
|
||||||
|
border-slate-100
|
||||||
|
bg-white
|
||||||
|
p-3
|
||||||
|
shadow-sm
|
||||||
|
transition-all
|
||||||
|
duration-200
|
||||||
|
hover:border-indigo-200
|
||||||
|
hover:bg-indigo-50/40
|
||||||
|
hover:shadow-md
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{/* Avatar */}
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
flex
|
||||||
|
h-10
|
||||||
|
w-10
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
rounded-full
|
||||||
|
bg-gradient-to-br
|
||||||
|
from-indigo-500
|
||||||
|
to-cyan-500
|
||||||
|
text-sm
|
||||||
|
font-semibold
|
||||||
|
text-white
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span className="block text-sm text-foreground">
|
A
|
||||||
{item.question}
|
</div>
|
||||||
|
|
||||||
|
{/* User Info */}
|
||||||
|
<div className="flex flex-1 flex-col text-left">
|
||||||
|
<span className="text-sm font-semibold text-slate-800">
|
||||||
|
Logged In
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<p className="text-sm text-slate-500">
|
<span className="text-xs text-slate-500">
|
||||||
{item.category}
|
Yoda Workspace
|
||||||
</p>
|
</span>
|
||||||
</button>
|
</div>
|
||||||
))
|
|
||||||
)}
|
{/* Arrow */}
|
||||||
</div>
|
<svg
|
||||||
</aside>)}
|
className={`h-4 w-4 text-slate-400 transition-transform ${showProfileMenu ? "rotate-180" : ""
|
||||||
|
}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M19 9l-7 7-7-7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Dropdown */}
|
||||||
|
{showProfileMenu && (
|
||||||
|
<div
|
||||||
|
className="
|
||||||
|
absolute
|
||||||
|
bottom-20
|
||||||
|
left-3
|
||||||
|
right-3
|
||||||
|
overflow-hidden
|
||||||
|
rounded-2xl
|
||||||
|
border
|
||||||
|
border-slate-200
|
||||||
|
bg-white
|
||||||
|
shadow-xl
|
||||||
|
z-50
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="
|
||||||
|
w-full
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
text-left
|
||||||
|
text-sm
|
||||||
|
font-medium
|
||||||
|
text-red-600
|
||||||
|
transition-colors
|
||||||
|
hover:bg-red-50
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
)}
|
||||||
<div className="flex-1 min-w-0 flex flex-col gap-4">
|
<div className="flex-1 min-w-0 flex flex-col gap-4">
|
||||||
{messages.length === 0 ? (
|
{messages.length === 0 ? (
|
||||||
|
|
||||||
<div className="flex min-h-[65vh] flex-col items-center justify-center text-center">
|
<div className="flex min-h-[65vh] flex-col items-center justify-center text-center">
|
||||||
<h2 className="mb-3 text-4xl font-extrabold tracking-tight text-slate-900">
|
<h2 className="mb-3 text-4xl font-extrabold tracking-tight text-slate-900">
|
||||||
Welcome to GenBI
|
Welcome to Yoda
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="mb-10 text-lg text-slate-500">
|
<p className="mb-10 text-lg text-slate-500">
|
||||||
@@ -288,10 +501,9 @@ export default function ChatCanvas() {
|
|||||||
|
|
||||||
<div className="grid gap-3 md:grid-cols-2 max-w-2xl w-full">
|
<div className="grid gap-3 md:grid-cols-2 max-w-2xl w-full">
|
||||||
{[
|
{[
|
||||||
"Show sales by region",
|
"What was the total stock availability across different store types last month?",
|
||||||
"Compare revenue by month",
|
"what is the OSA percentage for the month of march 2026?",
|
||||||
"Top 10 products",
|
"Which products had the highest OSA percentage in March 2026?",
|
||||||
"Customer growth trends",
|
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item}
|
key={item}
|
||||||
@@ -313,11 +525,10 @@ export default function ChatCanvas() {
|
|||||||
className={cn("flex", isUser ? "justify-end" : "justify-start")}
|
className={cn("flex", isUser ? "justify-end" : "justify-start")}
|
||||||
key={message.id}
|
key={message.id}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
// "max-w-[1100px]",
|
|
||||||
"max-w-[900px] mx-auto",
|
"max-w-[900px] mx-auto",
|
||||||
isUser
|
isUser
|
||||||
? "rounded-3xl bg-gradient-to-r from-indigo-600 to-cyan-600 px-5 py-3 text-sm text-white shadow-lg"
|
? "rounded-3xl bg-gradient-to-r from-indigo-600 to-cyan-600 px-5 py-3 text-sm text-white shadow-lg"
|
||||||
@@ -328,15 +539,39 @@ export default function ChatCanvas() {
|
|||||||
typeof message.content === "string"
|
typeof message.content === "string"
|
||||||
? message.content
|
? message.content
|
||||||
: JSON.stringify(message.content, null, 2)
|
: JSON.stringify(message.content, null, 2)
|
||||||
) : (
|
) : message.error ? (
|
||||||
<ChartArtifact content={message.content}
|
<div className="rounded-3xl border border-amber-200 bg-amber-50 p-6">
|
||||||
/>
|
<div className="flex items-start gap-4">
|
||||||
)} */}
|
<AlertTriangle className="h-6 w-6 text-amber-600" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-amber-800">
|
||||||
|
Connection Problem
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="mt-2 text-sm text-amber-700">
|
||||||
|
{String(message.content)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ChartArtifact content={message.content} />
|
||||||
|
)} */}
|
||||||
{isUser ? (
|
{isUser ? (
|
||||||
typeof message.content === "string"
|
typeof message.content === "string"
|
||||||
? message.content
|
? message.content
|
||||||
: JSON.stringify(message.content, null, 2)
|
: JSON.stringify(message.content, null, 2)
|
||||||
|
) : message.clarification ? (
|
||||||
|
<div className="rounded-3xl border border-blue-200 bg-blue-50 p-6">
|
||||||
|
<h3 className="font-semibold text-blue-800">
|
||||||
|
{/* Clarification Needed */}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="mt-2 text-sm text-blue-700">
|
||||||
|
{String(message.content)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
) : message.error ? (
|
) : message.error ? (
|
||||||
<div className="rounded-3xl border border-amber-200 bg-amber-50 p-6">
|
<div className="rounded-3xl border border-amber-200 bg-amber-50 p-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
@@ -371,7 +606,7 @@ export default function ChatCanvas() {
|
|||||||
<span className="typing-dot" />
|
<span className="typing-dot" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-foreground/90">
|
<span className="text-sm font-medium text-foreground/90">
|
||||||
GenBI is typing...
|
Yoda is typing...
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -388,10 +623,34 @@ export default function ChatCanvas() {
|
|||||||
|
|
||||||
|
|
||||||
{/* RIGHT PANEL */}
|
{/* RIGHT PANEL */}
|
||||||
<aside className="hidden xl:block w-[380px] shrink-0">
|
{/* <aside className="hidden xl:block w-[380px] shrink-0 relative">
|
||||||
<div className="sticky top-24">
|
<div className="sticky bottom-4">
|
||||||
|
|
||||||
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
|
||||||
|
|
||||||
|
<div className="mt-5 rounded-3xl border border-slate-200 bg-white p-6 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
||||||
|
<h4 className="font-medium mb-3">
|
||||||
|
Quick Prompts
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{[
|
||||||
|
"What was the total stock availability across different store types last month?",
|
||||||
|
"what is the OSA percentage for the month of march 2026?",
|
||||||
|
"Which products had the highest OSA percentage in March 2026?",
|
||||||
|
].map((item) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
onClick={() => setInput(item)}
|
||||||
|
className="w-full rounded-lg border p-2 text-left text-sm hover:bg-muted"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
||||||
<h3 className="font-semibold text-lg">
|
<h3 className="font-semibold text-lg">
|
||||||
Ask GenBI
|
Ask GenBI
|
||||||
</h3>
|
</h3>
|
||||||
@@ -422,22 +681,29 @@ export default function ChatCanvas() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 rounded-3xl border border-slate-200 bg-white p-6 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
</div>
|
||||||
<h4 className="font-medium mb-3">
|
</aside> */}
|
||||||
|
|
||||||
|
{/* RIGHT PANEL */}
|
||||||
|
<aside className="hidden xl:block w-[380px] shrink-0">
|
||||||
|
<div className="fixed right-6 bottom-4 w-[380px] z-40 space-y-4">
|
||||||
|
|
||||||
|
{/* Quick Prompts */}
|
||||||
|
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
||||||
|
<h4 className="mb-3 font-medium">
|
||||||
Quick Prompts
|
Quick Prompts
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{[
|
{[
|
||||||
"Show sales by region",
|
"What was the total stock availability across different store types last month?",
|
||||||
"Compare revenue by month",
|
"what is the OSA percentage for the month of march 2026?",
|
||||||
"Top 10 products",
|
"Which products had the highest OSA percentage in March 2026?",
|
||||||
"Customer growth trends",
|
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item}
|
key={item}
|
||||||
onClick={() => setInput(item)}
|
onClick={() => setInput(item)}
|
||||||
className="w-full rounded-lg border p-2 text-left text-sm hover:bg-muted"
|
className="w-full rounded-lg border p-2 text-left text-sm transition hover:bg-slate-100"
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</button>
|
</button>
|
||||||
@@ -445,10 +711,43 @@ export default function ChatCanvas() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Ask GenBI */}
|
||||||
|
<div className="rounded-3xl border border-slate-200 bg-white p-6 shadow-[0_10px_40px_rgba(0,0,0,0.08)]">
|
||||||
|
<h3 className="text-lg font-semibold">
|
||||||
|
Ask Yoda
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="mt-1 mb-4 text-sm text-slate-500">
|
||||||
|
Ask questions about your business data
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form onSubmit={onSubmit} className="space-y-3">
|
||||||
|
<Input
|
||||||
|
value={input}
|
||||||
|
disabled={isSending}
|
||||||
|
onChange={(e) => {
|
||||||
|
setInput(e.target.value);
|
||||||
|
setError("");
|
||||||
|
}}
|
||||||
|
placeholder="Type your question..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="h-11 w-full rounded-xl bg-gradient-to-r from-indigo-600 to-cyan-600 text-white hover:opacity-90"
|
||||||
|
type="submit"
|
||||||
|
disabled={isSending || !input.trim()}
|
||||||
|
>
|
||||||
|
{isSending ? "Sending..." : "Send"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* <form className="fixed inset-x-0 bottom-0 z-20 px-3 pb-4" onSubmit={onSubmit}> */}
|
{/* <form className="fixed inset-x-0 bottom-0 z-20 px-3 pb-4" onSubmit={onSubmit}> */}
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+9
-3
@@ -47,6 +47,10 @@ export default function Login() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
|
console.log("Attempting login with:", {
|
||||||
|
username,
|
||||||
|
password: password ? "******" : "",
|
||||||
|
});
|
||||||
if (!validate()) return;
|
if (!validate()) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -57,8 +61,7 @@ export default function Login() {
|
|||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await api.post<LoginResponse>(
|
const response = await api.post<LoginResponse>("/loginUser",
|
||||||
"/loginUser",
|
|
||||||
body,
|
body,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -68,6 +71,8 @@ export default function Login() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log("Response:", response.data);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!response.data.success ||
|
!response.data.success ||
|
||||||
!response.data.token
|
!response.data.token
|
||||||
@@ -85,7 +90,8 @@ export default function Login() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
window.location.href = "/dashboard";
|
window.location.href = "/dashboard";
|
||||||
} catch (err) {
|
}
|
||||||
|
catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
if (isAxiosError(err)) {
|
if (isAxiosError(err)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user