Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
Son Of Anton
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Son Of Anton
Commits
06918c59
Commit
06918c59
authored
Mar 19, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
again
parent
25a6e3a4
Changes
5
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
9818 additions
and
1665 deletions
+9818
-1665
FULL_CODEBASE.txt
FULL_CODEBASE.txt
+9746
-1623
fix-and-deploy.sh
fix-and-deploy.sh
+1
-1
App.jsx
frontend/src/App.jsx
+41
-5
Sidebar.jsx
frontend/src/components/Sidebar.jsx
+12
-24
store.jsx
frontend/src/store.jsx
+18
-12
No files found.
FULL_CODEBASE.txt
View file @
06918c59
This diff is collapsed.
Click to expand it.
fix-and-deploy.sh
View file @
06918c59
...
@@ -256,7 +256,7 @@ tar cf "$TARBALL" \
...
@@ -256,7 +256,7 @@ tar cf "$TARBALL" \
--exclude
=
'.env.local'
\
--exclude
=
'.env.local'
\
--exclude
=
'.idea'
\
--exclude
=
'.idea'
\
--exclude
=
'.vscode'
\
--exclude
=
'.vscode'
\
--exclude
=
'main.py'
\
--exclude
=
'
./
main.py'
\
--exclude
=
'create-project.ps1'
\
--exclude
=
'create-project.ps1'
\
--exclude
=
'*.sh'
\
--exclude
=
'*.sh'
\
.
.
...
...
frontend/src/App.jsx
View file @
06918c59
import
React
from
"react"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
Routes
,
Route
,
Navigate
}
from
"react-router-dom"
;
import
{
Routes
,
Route
}
from
"react-router-dom"
;
import
{
useApp
}
from
"./store"
;
import
{
useApp
}
from
"./store"
;
import
{
getMe
}
from
"./api"
;
import
LoginPage
from
"./pages/LoginPage"
;
import
LoginPage
from
"./pages/LoginPage"
;
import
ChatPage
from
"./pages/ChatPage"
;
import
ChatPage
from
"./pages/ChatPage"
;
import
AdminPage
from
"./pages/AdminPage"
;
import
AdminPage
from
"./pages/AdminPage"
;
import
{
Flame
}
from
"lucide-react"
;
export
default
function
App
()
{
export
default
function
App
()
{
const
{
state
}
=
useApp
();
const
{
state
,
dispatch
}
=
useApp
();
const
loggedIn
=
!!
state
.
token
;
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
/>;
return
<
LoginPage
/>;
}
}
...
...
frontend/src/components/Sidebar.jsx
View file @
06918c59
...
@@ -38,10 +38,9 @@ export default function Sidebar({ onRefresh }) {
...
@@ -38,10 +38,9 @@ export default function Sidebar({ onRefresh }) {
async
function
handleDelete
(
id
)
{
async
function
handleDelete
(
id
)
{
try
{
try
{
// Abort any active stream for this chat before deleting
streamManager
.
abortStream
(
id
);
streamManager
.
abortStream
(
id
);
await
deleteChat
(
state
.
token
,
id
);
await
deleteChat
(
state
.
token
,
id
);
dispatch
({
type
:
"
REMOV
E_CHAT"
,
chatId
:
id
});
dispatch
({
type
:
"
DELET
E_CHAT"
,
chatId
:
id
});
}
catch
{
/* */
}
}
catch
{
/* */
}
}
}
...
@@ -106,7 +105,6 @@ export default function Sidebar({ onRefresh }) {
...
@@ -106,7 +105,6 @@ export default function Sidebar({ onRefresh }) {
if
(
t
===
"knowledge"
&&
!
kbLoaded
)
loadKbs
();
if
(
t
===
"knowledge"
&&
!
kbLoaded
)
loadKbs
();
}
}
// ── Collapsed sidebar ──
if
(
!
open
)
{
if
(
!
open
)
{
return
(
return
(
<
div
className=
"w-12 bg-anton-surface border-r border-anton-border flex flex-col items-center py-3 gap-3 shrink-0"
>
<
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 }) {
...
@@ -122,7 +120,6 @@ export default function Sidebar({ onRefresh }) {
>
>
<
Plus
size=
{
18
}
/>
<
Plus
size=
{
18
}
/>
</
button
>
</
button
>
{
/* Streaming count badge when sidebar is collapsed */
}
{
streamingCount
>
0
&&
(
{
streamingCount
>
0
&&
(
<
div
className=
"w-6 h-6 rounded-full bg-anton-accent/20 flex items-center justify-center"
>
<
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"
>
<
span
className=
"text-[10px] text-anton-accent font-bold animate-pulse"
>
...
@@ -134,10 +131,8 @@ export default function Sidebar({ onRefresh }) {
...
@@ -134,10 +131,8 @@ export default function Sidebar({ onRefresh }) {
);
);
}
}
// ── Full sidebar ──
return
(
return
(
<
div
className=
"w-72 bg-anton-surface border-r border-anton-border flex flex-col shrink-0"
>
<
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=
"p-3 border-b border-anton-border flex items-center justify-between"
>
<
div
className=
"flex items-center gap-2"
>
<
div
className=
"flex items-center gap-2"
>
<
Flame
size=
{
20
}
className=
"text-anton-accent"
/>
<
Flame
size=
{
20
}
className=
"text-anton-accent"
/>
...
@@ -156,7 +151,6 @@ export default function Sidebar({ onRefresh }) {
...
@@ -156,7 +151,6 @@ export default function Sidebar({ onRefresh }) {
</
button
>
</
button
>
</
div
>
</
div
>
{
/* Tabs */
}
<
div
className=
"flex border-b border-anton-border"
>
<
div
className=
"flex border-b border-anton-border"
>
{
[
{
[
{
key
:
"chats"
,
label
:
"Chats"
,
icon
:
MessageSquare
},
{
key
:
"chats"
,
label
:
"Chats"
,
icon
:
MessageSquare
},
...
@@ -165,8 +159,7 @@ export default function Sidebar({ onRefresh }) {
...
@@ -165,8 +159,7 @@ export default function Sidebar({ onRefresh }) {
<
button
<
button
key=
{
t
.
key
}
key=
{
t
.
key
}
onClick=
{
()
=>
switchTab
(
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 ${
className=
{
`flex-1 flex items-center justify-center gap-1.5 py-2.5 text-xs font-medium transition ${tab === t.key
tab === t.key
? "text-anton-accent border-b-2 border-anton-accent"
? "text-anton-accent border-b-2 border-anton-accent"
: "text-anton-muted hover:text-white"
: "text-anton-muted hover:text-white"
}`
}
}`
}
...
@@ -177,7 +170,6 @@ export default function Sidebar({ onRefresh }) {
...
@@ -177,7 +170,6 @@ export default function Sidebar({ onRefresh }) {
))
}
))
}
</
div
>
</
div
>
{
/* Content */
}
<
div
className=
"flex-1 overflow-y-auto p-2 space-y-1"
>
<
div
className=
"flex-1 overflow-y-auto p-2 space-y-1"
>
{
tab
===
"chats"
&&
(
{
tab
===
"chats"
&&
(
<>
<>
...
@@ -193,8 +185,7 @@ export default function Sidebar({ onRefresh }) {
...
@@ -193,8 +185,7 @@ export default function Sidebar({ onRefresh }) {
return
(
return
(
<
div
<
div
key=
{
c
.
id
}
key=
{
c
.
id
}
className=
{
`group flex items-center rounded-lg cursor-pointer transition ${
className=
{
`group flex items-center rounded-lg cursor-pointer transition ${state.activeChatId === c.id
state.activeChatId === c.id
? "bg-anton-accent/10 text-anton-accent"
? "bg-anton-accent/10 text-anton-accent"
: "text-anton-text hover:bg-anton-card"
: "text-anton-text hover:bg-anton-card"
}`
}
}`
}
...
@@ -221,7 +212,6 @@ export default function Sidebar({ onRefresh }) {
...
@@ -221,7 +212,6 @@ export default function Sidebar({ onRefresh }) {
onClick=
{
()
=>
dispatch
({
type
:
"SET_ACTIVE_CHAT"
,
chatId
:
c
.
id
})
}
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"
className=
"flex-1 flex items-center gap-2 text-left px-3 py-2 text-sm truncate min-w-0"
>
>
{
/* Streaming indicator dot */
}
{
chatStreaming
&&
(
{
chatStreaming
&&
(
<
span
className=
"w-2 h-2 bg-anton-accent rounded-full animate-pulse shrink-0"
/>
<
span
className=
"w-2 h-2 bg-anton-accent rounded-full animate-pulse shrink-0"
/>
)
}
)
}
...
@@ -298,8 +288,7 @@ export default function Sidebar({ onRefresh }) {
...
@@ -298,8 +288,7 @@ export default function Sidebar({ onRefresh }) {
</
div
>
</
div
>
</
div
>
</
div
>
<
label
<
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 ${
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" : ""
uploading ? "opacity-50 pointer-events-none" : ""
}`
}
}`
}
>
>
<
Upload
size=
{
12
}
/>
<
Upload
size=
{
12
}
/>
...
@@ -333,7 +322,6 @@ export default function Sidebar({ onRefresh }) {
...
@@ -333,7 +322,6 @@ export default function Sidebar({ onRefresh }) {
)
}
)
}
</
div
>
</
div
>
{
/* Footer */
}
<
div
className=
"p-3 border-t border-anton-border space-y-2"
>
<
div
className=
"p-3 border-t border-anton-border space-y-2"
>
{
state
.
user
?.
role
===
"superadmin"
&&
(
{
state
.
user
?.
role
===
"superadmin"
&&
(
<
button
<
button
...
...
frontend/src/store.jsx
View file @
06918c59
...
@@ -5,27 +5,35 @@ const AppContext = createContext();
...
@@ -5,27 +5,35 @@ const AppContext = createContext();
const
initialState
=
{
const
initialState
=
{
token
:
localStorage
.
getItem
(
"token"
)
||
null
,
token
:
localStorage
.
getItem
(
"token"
)
||
null
,
user
:
null
,
user
:
JSON
.
parse
(
localStorage
.
getItem
(
"user"
)
||
"null"
)
,
chats
:
[],
chats
:
[],
activeChatId
:
null
,
activeChatId
:
null
,
sidebarOpen
:
true
,
sidebarOpen
:
true
,
chatMessages
:
{},
// chatId -> [messages]
chatMessages
:
{},
activeStreams
:
{},
// chatId -> true (which chats are currently streaming)
activeStreams
:
{},
};
};
function
reducer
(
state
,
action
)
{
function
reducer
(
state
,
action
)
{
switch
(
action
.
type
)
{
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"
:
case
"SET_TOKEN"
:
if
(
action
.
token
)
localStorage
.
setItem
(
"token"
,
action
.
token
);
if
(
action
.
token
)
localStorage
.
setItem
(
"token"
,
action
.
token
);
else
localStorage
.
removeItem
(
"token"
);
else
localStorage
.
removeItem
(
"token"
);
return
{
...
state
,
token
:
action
.
token
};
return
{
...
state
,
token
:
action
.
token
};
case
"SET_USER"
:
case
"SET_USER"
:
localStorage
.
setItem
(
"user"
,
JSON
.
stringify
(
action
.
user
));
return
{
...
state
,
user
:
action
.
user
};
return
{
...
state
,
user
:
action
.
user
};
case
"LOGOUT"
:
case
"LOGOUT"
:
localStorage
.
removeItem
(
"token"
);
localStorage
.
removeItem
(
"token"
);
return
{
...
initialState
,
token
:
null
};
localStorage
.
removeItem
(
"user"
);
return
{
...
initialState
,
token
:
null
,
user
:
null
};
case
"SET_CHATS"
:
case
"SET_CHATS"
:
return
{
...
state
,
chats
:
action
.
chats
};
return
{
...
state
,
chats
:
action
.
chats
};
...
@@ -44,19 +52,21 @@ function reducer(state, action) {
...
@@ -44,19 +52,21 @@ function reducer(state, action) {
return
{
...
state
,
chats
:
updated
};
return
{
...
state
,
chats
:
updated
};
}
}
case
"REMOVE_CHAT"
:
case
"DELETE_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
};
const
newMessages
=
{
...
state
.
chatMessages
};
delete
newMessages
[
action
.
chatId
];
delete
newMessages
[
chatId
];
const
newStreams
=
{
...
state
.
activeStreams
};
const
newStreams
=
{
...
state
.
activeStreams
};
delete
newStreams
[
action
.
chatId
];
delete
newStreams
[
chatId
];
return
{
return
{
...
state
,
...
state
,
chats
:
filtered
,
chats
:
filtered
,
chatMessages
:
newMessages
,
chatMessages
:
newMessages
,
activeStreams
:
newStreams
,
activeStreams
:
newStreams
,
activeChatId
:
activeChatId
:
state
.
activeChatId
===
action
.
chatId
state
.
activeChatId
===
chatId
?
filtered
[
0
]?.
id
||
null
?
filtered
[
0
]?.
id
||
null
:
state
.
activeChatId
,
:
state
.
activeChatId
,
};
};
...
@@ -68,7 +78,6 @@ function reducer(state, action) {
...
@@ -68,7 +78,6 @@ function reducer(state, action) {
case
"TOGGLE_SIDEBAR"
:
case
"TOGGLE_SIDEBAR"
:
return
{
...
state
,
sidebarOpen
:
!
state
.
sidebarOpen
};
return
{
...
state
,
sidebarOpen
:
!
state
.
sidebarOpen
};
// ── Per-chat message management ──────────────
case
"SET_MESSAGES"
:
case
"SET_MESSAGES"
:
return
{
return
{
...
state
,
...
state
,
...
@@ -90,8 +99,6 @@ function reducer(state, action) {
...
@@ -90,8 +99,6 @@ function reducer(state, action) {
},
},
};
};
// ── Background streaming flags ───────────────
// NOW PER-CHAT — no longer blocks other chats
case
"SET_STREAMING"
:
{
case
"SET_STREAMING"
:
{
if
(
action
.
streaming
)
{
if
(
action
.
streaming
)
{
return
{
return
{
...
@@ -112,7 +119,6 @@ function reducer(state, action) {
...
@@ -112,7 +119,6 @@ function reducer(state, action) {
export
function
AppProvider
({
children
})
{
export
function
AppProvider
({
children
})
{
const
[
state
,
dispatch
]
=
useReducer
(
reducer
,
initialState
);
const
[
state
,
dispatch
]
=
useReducer
(
reducer
,
initialState
);
// Give the background stream manager access to dispatch
useEffect
(()
=>
{
useEffect
(()
=>
{
setDispatch
(
dispatch
);
setDispatch
(
dispatch
);
},
[
dispatch
]);
},
[
dispatch
]);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment