latest changes

This commit is contained in:
2026-06-03 17:21:38 +05:30
parent 7e29b6acb7
commit b709add3b6
4 changed files with 508 additions and 184 deletions
+1
View File
@@ -8,6 +8,7 @@ const api = axios.create({
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
console.log("Attaching token to request:", token);
config.headers = config.headers || {};
+50 -32
View File
@@ -28,7 +28,10 @@ import {
FileText,
ImageIcon,
RefreshCcw,
Sparkles,
Sparkles,
CheckCircle2,
BarChart3,
Database,
} from "lucide-react";
import ErrorBoundary from "@/components/ErrorBoundary";
@@ -785,40 +788,55 @@ function VegaLiteArtifact({
<div className="flex flex-col gap-4">
{/* TOP TABS */}
<div className="flex items-center gap-2">
<button
onClick={() => setActiveTab("answer")}
className={`rounded-xl px-4 py-2 text-sm font-medium transition ${activeTab === "answer"
? "bg-cyan-500 text-white"
: "bg-white/10 text-slate-300 hover:bg-white/20"
}`}
>
Answer
</button>
<div className="flex items-center gap-2">
<button
onClick={() => setActiveTab("answer")}
className={`flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium transition ${
activeTab === "answer"
? "bg-cyan-500 text-white"
: "bg-white border border-slate-200 text-black hover:bg-slate-50"
}`}
>
<CheckCircle2
size={16}
className={activeTab === "answer" ? "text-white" : "text-green-600"}
/>
Answer
</button>
<button
onClick={() => setActiveTab("chart")}
className={`rounded-xl px-4 py-2 text-sm font-medium transition ${activeTab === "chart"
? "bg-cyan-500 text-white"
: "bg-white/10 text-slate-300 hover:bg-white/20"
}`}
>
Chart
</button>
<button
onClick={() => setActiveTab("chart")}
className={`flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium transition ${
activeTab === "chart"
? "bg-cyan-500 text-white"
: "bg-white border border-slate-200 text-black hover:bg-slate-50"
}`}
>
<BarChart3
size={16}
className={activeTab === "chart" ? "text-white" : "text-blue-600"}
/>
Chart
</button>
<button
onClick={() => setActiveTab("sql")}
className={`rounded-xl px-4 py-2 text-sm font-medium transition ${activeTab === "sql"
? "bg-cyan-500 text-white"
: "bg-white/10 text-slate-300 hover:bg-white/20"
}`}
>
SQL
</button>
</div>
<button
onClick={() => setActiveTab("sql")}
className={`flex items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium transition ${
activeTab === "sql"
? "bg-cyan-500 text-white"
: "bg-white border border-slate-200 text-black hover:bg-slate-50"
}`}
>
<Database
size={16}
className={activeTab === "sql" ? "text-white" : "text-orange-600"}
/>
SQL
</button>
</div>
{/* EXPORT BUTTONS */}
<div className="flex flex-wrap gap-3">
{/* <div className="flex flex-wrap gap-3">
<Button
onClick={downloadPDF}
className="rounded-xl bg-red-500 hover:bg-red-600"
@@ -850,7 +868,7 @@ function VegaLiteArtifact({
<ImageIcon className="mr-2 h-4 w-4" />
PNG
</Button>
</div>
</div> */}
</div>
+448 -149
View File
@@ -13,6 +13,7 @@ type ChatMessage = {
role: "user" | "assistant";
content: unknown;
error?: boolean;
clarification?: boolean;
};
function createId() {
@@ -34,11 +35,11 @@ function createMessage(role: ChatMessage["role"], content: unknown): ChatMessage
export default function ChatCanvas() {
const [input, setInput] = useState("");
const [messages, setMessages] = useState<ChatMessage[]>([]);
// const [selectedHistoryId, setSelectedHistoryId] = useState<string | null>(null);
const [error, setError] = useState("");
const [isSending, setIsSending] = useState(false);
const [showHistory, setShowHistory] = useState(true);
const [historyQuestions, setHistoryQuestions] = useState<any[]>([]);
const [showProfileMenu, setShowProfileMenu] = useState(false);
const bottomRef = useRef<HTMLDivElement>(null);
@@ -52,28 +53,101 @@ export default function ChatCanvas() {
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 () => {
try {
const res = await api.get("/suggestions");
const res = await api.get("/fetchUserHistory");
console.log("History Response =>", res.data);
if (res.data.success) {
setHistoryQuestions(res.data.questions);
setHistoryQuestions(res.data.data);
}
} 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) => {
// if (typeof message.content === "string") {
// setInput(message.content);
// setSelectedHistoryId(message.id);
// setError("");
// const response = await api.post(
// "/fetchHistory",
// {
// response_id: item.response_id,
// },
// {
// 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) => {
event.preventDefault();
@@ -96,10 +170,29 @@ export default function ChatCanvas() {
JSON.stringify(response.data, null, 2)
);
setMessages((current) => [
...current,
createMessage("assistant", response.data),
]);
// setMessages((current) => [
// ...current,
// 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) {
console.error(err);
@@ -146,140 +239,260 @@ export default function ChatCanvas() {
};
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">
{/* <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> */}
<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"> */}
<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>
<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">
<button
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"
>
{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"
<div className="flex items-center gap-3">
<button
onClick={() => setShowHistory(!showHistory)}
className="rounded-lg border px-3 py-1.5 text-sm hover:bg-slate-100"
>
Logout
</Button>
</button>
<h1 className="text-xl font-semibold">
Yoda
</h1>
</div>
</div>
</header>
{/* <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">
{showHistory && (<aside
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>
{showHistory && (
{/* <div className="space-y-2">
{history.length === 0 ? (
<div className="rounded-3xl border border-border/80 bg-muted/70 p-4 text-sm text-muted-foreground">
Your latest prompts will appear here as you chat.
</div>
) : (
history
.slice(-10)
.reverse()
.map((message) => (
<button
key={message.id}
type="button"
onClick={() => handleSelectHistory(message)}
className={cn(
"w-full rounded-3xl border px-3 py-3 text-left text-sm transition",
selectedHistoryId === message.id
? "border-primary bg-primary/10"
: "border-border/70 bg-muted/80 hover:bg-muted/90",
)}
>
<span className="block text-sm text-foreground">
{typeof message.content === "string"
? message.content
: JSON.stringify(message.content, null, 2)}
</span>
</button>
))
)}
</div> */}
<div className="space-y-2">
{historyQuestions.length === 0 ? (
<div className="rounded-3xl border border-border/80 bg-muted/70 p-4 text-sm text-muted-foreground">
No recent prompts found.
</div>
) : (
historyQuestions.map((item, index) => (
<button
key={index}
type="button"
onClick={() => setInput(item.question)}
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"
// <aside
// 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">
// {historyQuestions.length === 0 ? (
// <div className="rounded-3xl border border-border/80 bg-muted/70 p-4 text-sm text-muted-foreground">
// No recent prompts found.
// </div>
// ) : (
// historyQuestions.map((item, index) => (
// <button
// key={item.response_id}
// type="button"
// onClick={() => handleHistoryClick(item)}
// 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">
// {item.prompt}
// </span>
// <p className="text-xs text-slate-500 mt-1">
// {new Date(item.created_at).toLocaleString()}
// </p>
// </button>
// ))
// )}
// </div>
// <div className="border-t p-4">
// <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">
{item.question}
A
</div>
{/* User Info */}
<div className="flex flex-1 flex-col text-left">
<span className="text-sm font-semibold text-slate-800">
Logged In
</span>
<p className="text-sm text-slate-500">
{item.category}
</p>
</button>
))
)}
</div>
</aside>)}
<span className="text-xs text-slate-500">
Yoda Workspace
</span>
</div>
{/* Arrow */}
<svg
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">
{messages.length === 0 ? (
<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">
Welcome to GenBI
Welcome to Yoda
</h2>
<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">
{[
"Show sales by region",
"Compare revenue by month",
"Top 10 products",
"Customer growth trends",
"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}
@@ -313,11 +525,10 @@ export default function ChatCanvas() {
className={cn("flex", isUser ? "justify-end" : "justify-start")}
key={message.id}
>
<div
className={cn(
// "max-w-[1100px]",
"max-w-[900px] mx-auto",
isUser
? "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"
? message.content
: JSON.stringify(message.content, null, 2)
) : (
<ChartArtifact content={message.content}
/>
)} */}
) : message.error ? (
<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 ? (
typeof message.content === "string"
? message.content
: 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 ? (
<div className="rounded-3xl border border-amber-200 bg-amber-50 p-6">
<div className="flex items-start gap-4">
@@ -371,7 +606,7 @@ export default function ChatCanvas() {
<span className="typing-dot" />
</div>
<span className="text-sm font-medium text-foreground/90">
GenBI is typing...
Yoda is typing...
</span>
</div>
</div>
@@ -388,10 +623,34 @@ export default function ChatCanvas() {
{/* RIGHT PANEL */}
<aside className="hidden xl:block w-[380px] shrink-0">
<div className="sticky top-24">
{/* <aside className="hidden xl:block w-[380px] shrink-0 relative">
<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">
Ask GenBI
</h3>
@@ -422,22 +681,29 @@ export default function ChatCanvas() {
</form>
</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)]">
<h4 className="font-medium mb-3">
</div>
</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
</h4>
<div className="space-y-2">
{[
"Show sales by region",
"Compare revenue by month",
"Top 10 products",
"Customer growth trends",
"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"
className="w-full rounded-lg border p-2 text-left text-sm transition hover:bg-slate-100"
>
{item}
</button>
@@ -445,10 +711,43 @@ export default function ChatCanvas() {
</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>
</aside>
</section>
{/* <form className="fixed inset-x-0 bottom-0 z-20 px-3 pb-4" onSubmit={onSubmit}> */}
</main>
+9 -3
View File
@@ -47,6 +47,10 @@ export default function Login() {
};
const login = async () => {
console.log("Attempting login with:", {
username,
password: password ? "******" : "",
});
if (!validate()) return;
setIsLoading(true);
@@ -57,8 +61,7 @@ export default function Login() {
password,
});
const response = await api.post<LoginResponse>(
"/loginUser",
const response = await api.post<LoginResponse>("/loginUser",
body,
{
headers: {
@@ -68,6 +71,8 @@ export default function Login() {
}
);
console.log("Response:", response.data);
if (
!response.data.success ||
!response.data.token
@@ -85,7 +90,8 @@ export default function Login() {
);
window.location.href = "/dashboard";
} catch (err) {
}
catch (err) {
console.error(err);
if (isAxiosError(err)) {