Commit 06918c59 authored by Mahmoud Aglan's avatar Mahmoud Aglan

again

parent 25a6e3a4
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -256,7 +256,7 @@ tar cf "$TARBALL" \
--exclude='.env.local' \
--exclude='.idea' \
--exclude='.vscode' \
--exclude='main.py' \
--exclude='./main.py' \
--exclude='create-project.ps1' \
--exclude='*.sh' \
.
......
import React from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import React, { useEffect, useState } from "react";
import { Routes, Route } from "react-router-dom";
import { useApp } from "./store";
import { getMe } from "./api";
import LoginPage from "./pages/LoginPage";
import ChatPage from "./pages/ChatPage";
import AdminPage from "./pages/AdminPage";
import { Flame } from "lucide-react";
export default function App() {
const { state } = useApp();
const loggedIn = !!state.token;
const { state, dispatch } = useApp();
const [authChecked, setAuthChecked] = useState(!state.token);
if (!loggedIn) {
useEffect(() => {
if (!state.token) {
setAuthChecked(true);
return;
}
if (state.user) {
setAuthChecked(true);
return;
}
(async () => {
try {
const user = await getMe(state.token);
dispatch({ type: "SET_USER", user });
} catch {
dispatch({ type: "LOGOUT" });
} finally {
setAuthChecked(true);
}
})();
}, [state.token, state.user, dispatch]);
if (!authChecked) {
return (
<div className="h-full flex items-center justify-center bg-anton-bg">
<div className="flex flex-col items-center gap-4 animate-fade-in">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-anton-accent to-red-600 flex items-center justify-center shadow-lg shadow-anton-accent/20">
<Flame size={32} className="text-white animate-pulse" />
</div>
<p className="text-anton-muted text-sm">Loading...</p>
</div>
</div>
);
}
if (!state.token) {
return <LoginPage />;
}
......
......@@ -38,10 +38,9 @@ export default function Sidebar({ onRefresh }) {
async function handleDelete(id) {
try {
// Abort any active stream for this chat before deleting
streamManager.abortStream(id);
await deleteChat(state.token, id);
dispatch({ type: "REMOVE_CHAT", chatId: id });
dispatch({ type: "DELETE_CHAT", chatId: id });
} catch { /* */ }
}
......@@ -89,7 +88,7 @@ export default function Sidebar({ onRefresh }) {
if (errors.length > 0) {
alert(
`Uploaded ${result.files.length - errors.length} of ${result.files.length} files.\n\nErrors:\n` +
errors.map((e) => `• ${e.filename}: ${e.error}`).join("\n")
errors.map((e) => `• ${e.filename}: ${e.error}`).join("\n")
);
}
loadKbs();
......@@ -106,7 +105,6 @@ export default function Sidebar({ onRefresh }) {
if (t === "knowledge" && !kbLoaded) loadKbs();
}
// ── Collapsed sidebar ──
if (!open) {
return (
<div className="w-12 bg-anton-surface border-r border-anton-border flex flex-col items-center py-3 gap-3 shrink-0">
......@@ -122,7 +120,6 @@ export default function Sidebar({ onRefresh }) {
>
<Plus size={18} />
</button>
{/* Streaming count badge when sidebar is collapsed */}
{streamingCount > 0 && (
<div className="w-6 h-6 rounded-full bg-anton-accent/20 flex items-center justify-center">
<span className="text-[10px] text-anton-accent font-bold animate-pulse">
......@@ -134,10 +131,8 @@ export default function Sidebar({ onRefresh }) {
);
}
// ── Full sidebar ──
return (
<div className="w-72 bg-anton-surface border-r border-anton-border flex flex-col shrink-0">
{/* Header */}
<div className="p-3 border-b border-anton-border flex items-center justify-between">
<div className="flex items-center gap-2">
<Flame size={20} className="text-anton-accent" />
......@@ -156,7 +151,6 @@ export default function Sidebar({ onRefresh }) {
</button>
</div>
{/* Tabs */}
<div className="flex border-b border-anton-border">
{[
{ key: "chats", label: "Chats", icon: MessageSquare },
......@@ -165,11 +159,10 @@ export default function Sidebar({ onRefresh }) {
<button
key={t.key}
onClick={() => switchTab(t.key)}
className={`flex-1 flex items-center justify-center gap-1.5 py-2.5 text-xs font-medium transition ${
tab === t.key
? "text-anton-accent border-b-2 border-anton-accent"
: "text-anton-muted hover:text-white"
}`}
className={`flex-1 flex items-center justify-center gap-1.5 py-2.5 text-xs font-medium transition ${tab === t.key
? "text-anton-accent border-b-2 border-anton-accent"
: "text-anton-muted hover:text-white"
}`}
>
<t.icon size={13} />
{t.label}
......@@ -177,7 +170,6 @@ export default function Sidebar({ onRefresh }) {
))}
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-2 space-y-1">
{tab === "chats" && (
<>
......@@ -193,11 +185,10 @@ export default function Sidebar({ onRefresh }) {
return (
<div
key={c.id}
className={`group flex items-center rounded-lg cursor-pointer transition ${
state.activeChatId === c.id
? "bg-anton-accent/10 text-anton-accent"
: "text-anton-text hover:bg-anton-card"
}`}
className={`group flex items-center rounded-lg cursor-pointer transition ${state.activeChatId === c.id
? "bg-anton-accent/10 text-anton-accent"
: "text-anton-text hover:bg-anton-card"
}`}
>
{renamingId === c.id ? (
<div className="flex items-center gap-1 flex-1 p-1">
......@@ -221,7 +212,6 @@ export default function Sidebar({ onRefresh }) {
onClick={() => dispatch({ type: "SET_ACTIVE_CHAT", chatId: c.id })}
className="flex-1 flex items-center gap-2 text-left px-3 py-2 text-sm truncate min-w-0"
>
{/* Streaming indicator dot */}
{chatStreaming && (
<span className="w-2 h-2 bg-anton-accent rounded-full animate-pulse shrink-0" />
)}
......@@ -298,9 +288,8 @@ export default function Sidebar({ onRefresh }) {
</div>
</div>
<label
className={`flex items-center gap-1.5 px-2 py-1.5 rounded border border-dashed border-anton-border text-xs text-anton-muted hover:text-anton-accent hover:border-anton-accent transition cursor-pointer ${
uploading ? "opacity-50 pointer-events-none" : ""
}`}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded border border-dashed border-anton-border text-xs text-anton-muted hover:text-anton-accent hover:border-anton-accent transition cursor-pointer ${uploading ? "opacity-50 pointer-events-none" : ""
}`}
>
<Upload size={12} />
{uploading
......@@ -333,7 +322,6 @@ export default function Sidebar({ onRefresh }) {
)}
</div>
{/* Footer */}
<div className="p-3 border-t border-anton-border space-y-2">
{state.user?.role === "superadmin" && (
<button
......
......@@ -5,27 +5,35 @@ const AppContext = createContext();
const initialState = {
token: localStorage.getItem("token") || null,
user: null,
user: JSON.parse(localStorage.getItem("user") || "null"),
chats: [],
activeChatId: null,
sidebarOpen: true,
chatMessages: {}, // chatId -> [messages]
activeStreams: {}, // chatId -> true (which chats are currently streaming)
chatMessages: {},
activeStreams: {},
};
function reducer(state, action) {
switch (action.type) {
case "LOGIN": {
localStorage.setItem("token", action.token);
localStorage.setItem("user", JSON.stringify(action.user));
return { ...state, token: action.token, user: action.user };
}
case "SET_TOKEN":
if (action.token) localStorage.setItem("token", action.token);
else localStorage.removeItem("token");
return { ...state, token: action.token };
case "SET_USER":
localStorage.setItem("user", JSON.stringify(action.user));
return { ...state, user: action.user };
case "LOGOUT":
localStorage.removeItem("token");
return { ...initialState, token: null };
localStorage.removeItem("user");
return { ...initialState, token: null, user: null };
case "SET_CHATS":
return { ...state, chats: action.chats };
......@@ -44,19 +52,21 @@ function reducer(state, action) {
return { ...state, chats: updated };
}
case "REMOVE_CHAT":
case "DELETE_CHAT": {
const filtered = state.chats.filter((c) => c.id !== action.chatId);
const chatId = action.chatId;
const filtered = state.chats.filter((c) => c.id !== chatId);
const newMessages = { ...state.chatMessages };
delete newMessages[action.chatId];
delete newMessages[chatId];
const newStreams = { ...state.activeStreams };
delete newStreams[action.chatId];
delete newStreams[chatId];
return {
...state,
chats: filtered,
chatMessages: newMessages,
activeStreams: newStreams,
activeChatId:
state.activeChatId === action.chatId
state.activeChatId === chatId
? filtered[0]?.id || null
: state.activeChatId,
};
......@@ -68,7 +78,6 @@ function reducer(state, action) {
case "TOGGLE_SIDEBAR":
return { ...state, sidebarOpen: !state.sidebarOpen };
// ── Per-chat message management ──────────────
case "SET_MESSAGES":
return {
...state,
......@@ -90,8 +99,6 @@ function reducer(state, action) {
},
};
// ── Background streaming flags ───────────────
// NOW PER-CHAT — no longer blocks other chats
case "SET_STREAMING": {
if (action.streaming) {
return {
......@@ -112,7 +119,6 @@ function reducer(state, action) {
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
// Give the background stream manager access to dispatch
useEffect(() => {
setDispatch(dispatch);
}, [dispatch]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment