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
02876ff2
Commit
02876ff2
authored
Mar 29, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dgdfg jdfjgyjt
parent
2dd589eb
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
3671 additions
and
2254 deletions
+3671
-2254
FULL_CODEBASE.txt
FULL_CODEBASE.txt
+2872
-1975
api.js
frontend/src/api.js
+113
-71
ChatView.jsx
frontend/src/components/ChatView.jsx
+171
-165
CodeBlock.jsx
frontend/src/components/CodeBlock.jsx
+110
-27
MessageBubble.jsx
frontend/src/components/MessageBubble.jsx
+47
-16
RepoFilePanel.jsx
frontend/src/components/RepoFilePanel.jsx
+358
-0
No files found.
FULL_CODEBASE.txt
View file @
02876ff2
This diff is collapsed.
Click to expand it.
frontend/src/api.js
View file @
02876ff2
This diff is collapsed.
Click to expand it.
frontend/src/components/ChatView.jsx
View file @
02876ff2
This diff is collapsed.
Click to expand it.
frontend/src/components/CodeBlock.jsx
View file @
02876ff2
import
React
,
{
useState
}
from
"react"
;
import
{
Prism
as
SyntaxHighlighter
}
from
"react-syntax-highlighter"
;
import
{
oneDark
}
from
"react-syntax-highlighter/dist/esm/styles/prism"
;
import
{
Copy
,
Check
,
Download
,
GitCommitVertical
}
from
"lucide-react"
;
import
{
vscDarkPlus
}
from
"react-syntax-highlighter/dist/esm/styles/prism"
;
import
{
Copy
,
Check
,
Download
,
GitBranch
,
Loader2
,
CheckCircle2
,
XCircle
}
from
"lucide-react"
;
import
{
gitlabFileExists
,
gitlabCommitSingle
}
from
"../api"
;
export
default
function
CodeBlock
({
language
,
filename
,
code
,
linkedRepo
,
onCommit
})
{
const
COMMIT_STATES
=
{
idle
:
"idle"
,
checking
:
"checking"
,
committing
:
"committing"
,
success
:
"success"
,
error
:
"error"
};
export
default
function
CodeBlock
({
language
,
filename
,
code
,
linkedRepo
,
token
})
{
const
[
copied
,
setCopied
]
=
useState
(
false
);
const
[
commitState
,
setCommitState
]
=
useState
(
COMMIT_STATES
.
idle
);
const
[
commitMsg
,
setCommitMsg
]
=
useState
(
""
);
const
[
commitError
,
setCommitError
]
=
useState
(
""
);
const
[
showCommitForm
,
setShowCommitForm
]
=
useState
(
false
);
function
handleCopy
()
{
navigator
.
clipboard
.
writeText
(
code
);
...
...
@@ -22,43 +29,119 @@ export default function CodeBlock({ language, filename, code, linkedRepo, onComm
URL
.
revokeObjectURL
(
url
);
}
function
handleCommit
()
{
if
(
!
onCommit
||
!
filename
)
return
;
onCommit
(
filename
,
code
,
"update"
);
function
toggleCommitForm
()
{
if
(
!
showCommitForm
)
{
setCommitMsg
(
`Update
${
filename
||
"file"
}
via Son of Anton`
);
setCommitError
(
""
);
setCommitState
(
COMMIT_STATES
.
idle
);
}
setShowCommitForm
(
!
showCommitForm
);
}
async
function
handleCommit
()
{
if
(
!
linkedRepo
||
!
token
||
!
filename
)
return
;
setCommitState
(
COMMIT_STATES
.
checking
);
setCommitError
(
""
);
try
{
const
exists
=
await
gitlabFileExists
(
token
,
linkedRepo
.
id
,
filename
,
linkedRepo
.
default_branch
);
const
action
=
exists
?
"update"
:
"create"
;
setCommitState
(
COMMIT_STATES
.
committing
);
await
gitlabCommitSingle
(
token
,
linkedRepo
.
id
,
{
branch
:
linkedRepo
.
default_branch
,
file_path
:
filename
,
content
:
code
,
commit_message
:
commitMsg
||
`
${
action
===
"create"
?
"Create"
:
"Update"
}
${
filename
}
via Son of Anton`
,
action
,
});
setCommitState
(
COMMIT_STATES
.
success
);
setTimeout
(()
=>
{
setCommitState
(
COMMIT_STATES
.
idle
);
setShowCommitForm
(
false
);
},
2500
);
}
catch
(
err
)
{
setCommitState
(
COMMIT_STATES
.
error
);
setCommitError
(
err
.
message
||
"Commit failed"
);
}
}
const
canCommit
=
linkedRepo
&&
filename
&&
token
;
return
(
<
div
className=
"
my-3 rounded-xl overflow-hidden border border-anton-border bg-[#1a1b26]
"
>
<
div
className=
"
rounded-lg overflow-hidden border border-anton-border my-3
"
>
{
/* Header */
}
<
div
className=
"flex items-center justify-between px-3 py-1.5 bg-anton-surface border-b border-anton-border"
>
<
div
className=
"flex items-center gap-2 min-w-0"
>
{
language
&&
<
span
className=
"text-[10px] text-anton-accent font-mono uppercase"
>
{
language
}
</
span
>
}
{
filename
&&
<
span
className=
"text-[10px] text-anton-muted truncate"
>
{
filename
}
</
span
>
}
</
div
>
<
div
className=
"flex items-center gap-0.5 shrink-0"
>
{
linkedRepo
&&
filename
&&
(
<
button
onClick=
{
handleCommit
}
title=
{
`Commit to ${linkedRepo.name}`
}
className=
"flex items-center gap-1 px-2 py-1 text-[10px] text-orange-400 hover:bg-orange-400/10 rounded transition"
>
<
GitCommitVertical
size=
{
11
}
/>
Commit
<
div
className=
"flex items-center justify-between bg-[#1e1e2e] px-3 py-2 gap-2"
>
<
span
className=
"text-xs text-anton-muted font-mono truncate min-w-0"
>
{
filename
||
language
||
"code"
}
</
span
>
<
div
className=
"flex items-center gap-1 shrink-0"
>
{
canCommit
&&
(
<
button
onClick=
{
toggleCommitForm
}
title=
"Commit to repo"
className=
{
`flex items-center gap-1 px-2 py-1 rounded text-[11px] font-medium transition ${showCommitForm
? "bg-green-500/20 text-green-400"
: "text-anton-muted hover:text-green-400 hover:bg-green-500/10"
}`
}
>
<
GitBranch
size=
{
12
}
/>
<
span
className=
"hidden sm:inline"
>
Push
</
span
>
</
button
>
)
}
<
button
onClick=
{
handleDownload
}
className=
"p-1.5 text-anton-muted hover:text-white transition"
title=
"Download"
>
<
Download
size=
{
12
}
/>
</
button
>
<
button
onClick=
{
handleCopy
}
className=
"p-1.5 text-anton-muted hover:text-white transition"
title=
"Copy"
>
{
copied
?
<
Check
size=
{
12
}
className=
"text-green-400"
/>
:
<
Copy
size=
{
12
}
/>
}
{
filename
&&
(
<
button
onClick=
{
handleDownload
}
title=
"Download file"
className=
"p-1.5 rounded text-anton-muted hover:text-white hover:bg-white/5 transition"
>
<
Download
size=
{
13
}
/>
</
button
>
)
}
<
button
onClick=
{
handleCopy
}
title=
"Copy code"
className=
"p-1.5 rounded text-anton-muted hover:text-white hover:bg-white/5 transition"
>
{
copied
?
<
Check
size=
{
13
}
className=
"text-green-400"
/>
:
<
Copy
size=
{
13
}
/>
}
</
button
>
</
div
>
</
div
>
{
/* Commit Form */
}
{
showCommitForm
&&
canCommit
&&
(
<
div
className=
"bg-[#16162a] border-b border-anton-border px-3 py-2.5 space-y-2 animate-fade-in"
>
<
div
className=
"flex items-center gap-2 text-[11px]"
>
<
span
className=
"text-anton-muted"
>
Repo:
</
span
>
<
span
className=
"text-green-400 font-mono"
>
{
linkedRepo
.
name
}
</
span
>
<
span
className=
"text-anton-muted"
>
→
</
span
>
<
span
className=
"text-blue-400 font-mono"
>
{
linkedRepo
.
default_branch
}
</
span
>
</
div
>
<
input
type=
"text"
value=
{
commitMsg
}
onChange=
{
(
e
)
=>
setCommitMsg
(
e
.
target
.
value
)
}
placeholder=
"Commit message..."
className=
"w-full bg-anton-bg border border-anton-border rounded px-2.5 py-1.5 text-xs text-white placeholder-anton-muted focus:outline-none focus:border-green-500/50"
/>
<
div
className=
"flex items-center gap-2"
>
<
button
onClick=
{
handleCommit
}
disabled=
{
commitState
===
COMMIT_STATES
.
checking
||
commitState
===
COMMIT_STATES
.
committing
}
className=
"flex items-center gap-1.5 px-3 py-1.5 rounded bg-green-600 hover:bg-green-500 text-white text-xs font-medium transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{
commitState
===
COMMIT_STATES
.
checking
&&
<><
Loader2
size=
{
12
}
className=
"animate-spin"
/>
Checking...
</>
}
{
commitState
===
COMMIT_STATES
.
committing
&&
<><
Loader2
size=
{
12
}
className=
"animate-spin"
/>
Committing...
</>
}
{
commitState
===
COMMIT_STATES
.
success
&&
<><
CheckCircle2
size=
{
12
}
/>
Committed!
</>
}
{
commitState
===
COMMIT_STATES
.
error
&&
<><
XCircle
size=
{
12
}
/>
Retry
</>
}
{
commitState
===
COMMIT_STATES
.
idle
&&
<><
GitBranch
size=
{
12
}
/>
Commit
</>
}
</
button
>
<
button
onClick=
{
toggleCommitForm
}
className=
"px-3 py-1.5 rounded text-xs text-anton-muted hover:text-white transition"
>
Cancel
</
button
>
{
commitState
===
COMMIT_STATES
.
success
&&
(
<
span
className=
"text-[11px] text-green-400"
>
✓ Pushed to
{
linkedRepo
.
default_branch
}
</
span
>
)
}
</
div
>
{
commitError
&&
(
<
div
className=
"text-[11px] text-red-400 bg-red-500/10 rounded px-2 py-1"
>
{
commitError
}
</
div
>
)
}
</
div
>
)
}
{
/* Code */
}
<
SyntaxHighlighter
language=
{
language
||
"text"
}
style=
{
oneDark
}
customStyle=
{
{
margin
:
0
,
padding
:
"12px 16px"
,
fontSize
:
"12px"
,
lineHeight
:
"1.5"
,
background
:
"transparent"
}
}
showLineNumbers=
{
code
.
split
(
"
\n
"
).
length
>
3
}
lineNumberStyle=
{
{
color
:
"#555"
,
fontSize
:
"10px"
,
paddingRight
:
"12px"
}
}
wrapLongLines
style=
{
vscDarkPlus
}
customStyle=
{
{
margin
:
0
,
padding
:
"1rem"
,
fontSize
:
"0.8rem"
,
background
:
"#1a1a2e"
,
maxHeight
:
"500px"
}
}
showLineNumbers
lineNumberStyle=
{
{
minWidth
:
"2.5em"
,
paddingRight
:
"1em"
,
color
:
"#555"
}
}
>
{
code
}
</
SyntaxHighlighter
>
...
...
frontend/src/components/MessageBubble.jsx
View file @
02876ff2
...
...
@@ -8,16 +8,22 @@ import {
Image
,
Film
,
FileText
,
ExternalLink
,
}
from
"lucide-react"
;
const
FILE_TYPE_ICONS
=
{
image
:
Image
,
video
:
Film
,
document
:
FileText
,
text
:
FileText
};
const
FILE_TYPE_ICONS
=
{
image
:
Image
,
video
:
Film
,
document
:
FileText
,
text
:
FileText
,
};
const
MessageBubble
=
React
.
memo
(
function
MessageBubble
({
message
,
isStreaming
,
isThinking
,
token
,
linkedRepo
,
onCommit
})
{
const
MessageBubble
=
React
.
memo
(
function
MessageBubble
({
message
,
isStreaming
,
isThinking
,
token
,
linkedRepo
})
{
const
{
role
,
content
,
thinking_content
,
input_tokens
,
output_tokens
,
attachments
}
=
message
;
const
isUser
=
role
===
"user"
;
const
[
showThinking
,
setShowThinking
]
=
useState
(
false
);
const
[
copied
,
setCopied
]
=
useState
(
false
);
const
[
expandedImage
,
setExpandedImage
]
=
useState
(
null
);
function
handleCopy
()
{
navigator
.
clipboard
.
writeText
(
content
||
""
);
setCopied
(
true
);
setTimeout
(()
=>
setCopied
(
false
),
2000
);
}
function
handleCopy
()
{
navigator
.
clipboard
.
writeText
(
content
||
""
);
setCopied
(
true
);
setTimeout
(()
=>
setCopied
(
false
),
2000
);
}
const
hasAttachments
=
attachments
&&
attachments
.
length
>
0
;
...
...
@@ -34,14 +40,16 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
<
div
className=
{
`max-w-[80%] ${isUser ? "order-first" : ""}`
}
>
{
thinking_content
&&
(
<
div
className=
"mb-2"
>
<
button
onClick=
{
()
=>
setShowThinking
(
!
showThinking
)
}
className=
"flex items-center gap-1.5 text-xs text-purple-400 hover:text-purple-300 transition mb-1"
>
<
button
onClick=
{
()
=>
setShowThinking
(
!
showThinking
)
}
className=
"flex items-center gap-1.5 text-xs text-purple-400 hover:text-purple-300 transition mb-1"
>
<
Brain
size=
{
12
}
/>
{
showThinking
?
<
ChevronDown
size=
{
12
}
/>
:
<
ChevronRight
size=
{
12
}
/>
}
{
isThinking
?
<
span
className=
"thinking-pulse"
>
Reasoning…
</
span
>
:
<
span
>
View reasoning
</
span
>
}
</
button
>
{
(
showThinking
||
isThinking
)
&&
(
<
div
className=
"bg-purple-500/5 border border-purple-500/20 rounded-lg p-3 text-xs text-purple-300/80 font-mono whitespace-pre-wrap max-h-60 overflow-y-auto"
>
{
thinking_content
}{
isThinking
&&
<
span
className=
"inline-block w-1.5 h-4 bg-purple-400 ml-0.5 animate-pulse"
/>
}
{
thinking_content
}
{
isThinking
&&
<
span
className=
"inline-block w-1.5 h-4 bg-purple-400 ml-0.5 animate-pulse"
/>
}
</
div
>
)
}
</
div
>
...
...
@@ -49,7 +57,7 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
{
hasAttachments
&&
(
<
div
className=
"mb-2 flex flex-wrap gap-2"
>
{
attachments
.
map
(
att
=>
{
{
attachments
.
map
(
(
att
)
=>
{
const
Icon
=
FILE_TYPE_ICONS
[
att
.
file_type
]
||
FileText
;
const
url
=
getAttachmentUrl
(
att
.
id
);
if
(
att
.
file_type
===
"image"
)
{
...
...
@@ -57,13 +65,18 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
<
div
key=
{
att
.
id
}
className=
"relative group"
>
<
img
src=
{
`${url}?token=${token}`
}
alt=
{
att
.
original_filename
}
className=
"max-w-[240px] max-h-[200px] rounded-lg border border-anton-border object-cover cursor-pointer hover:opacity-90 transition"
onClick=
{
()
=>
setExpandedImage
(
expandedImage
===
att
.
id
?
null
:
att
.
id
)
}
onError=
{
e
=>
{
e
.
target
.
style
.
display
=
"none"
;
}
}
/>
onClick=
{
()
=>
setExpandedImage
(
expandedImage
===
att
.
id
?
null
:
att
.
id
)
}
onError=
{
(
e
)
=>
{
e
.
target
.
style
.
display
=
"none"
;
}
}
/>
{
expandedImage
===
att
.
id
&&
(
<
div
className=
"fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-8 cursor-pointer"
onClick=
{
()
=>
setExpandedImage
(
null
)
}
>
<
img
src=
{
`${url}?token=${token}`
}
alt=
{
att
.
original_filename
}
className=
"max-w-full max-h-full object-contain rounded-lg"
/>
<
div
className=
"fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-8 cursor-pointer"
onClick=
{
()
=>
setExpandedImage
(
null
)
}
>
<
img
src=
{
`${url}?token=${token}`
}
alt=
{
att
.
original_filename
}
className=
"max-w-full max-h-full object-contain rounded-lg"
/>
</
div
>
)
}
<
div
className=
"absolute bottom-1 left-1 bg-black/60 text-[9px] text-white px-1.5 py-0.5 rounded"
>
{
att
.
original_filename
}
</
div
>
<
div
className=
"absolute bottom-1 left-1 bg-black/60 text-[9px] text-white px-1.5 py-0.5 rounded"
>
{
att
.
original_filename
}
</
div
>
</
div
>
);
}
...
...
@@ -82,7 +95,8 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
</
div
>
)
}
<
div
className=
{
`rounded-2xl px-4 py-3 ${isUser ? "bg-anton-accent text-white rounded-br-md" : "bg-anton-card border border-anton-border rounded-bl-md"}`
}
>
<
div
className=
{
`rounded-2xl px-4 py-3 ${isUser ? "bg-anton-accent text-white rounded-br-md" : "bg-anton-card border border-anton-border rounded-bl-md"
}`
}
>
{
isUser
?
(
<
div
className=
"text-sm whitespace-pre-wrap"
>
{
_stripPrefixes
(
content
)
}
</
div
>
)
:
(
...
...
@@ -93,14 +107,28 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
const
rawLang
=
match
?.[
1
]
||
""
;
if
(
inline
)
return
<
code
className=
{
className
}
{
...
props
}
>
{
children
}
</
code
>;
let
lang
=
rawLang
,
filename
=
null
;
if
(
rawLang
.
includes
(
":"
))
{
const
idx
=
rawLang
.
indexOf
(
":"
);
lang
=
rawLang
.
slice
(
0
,
idx
);
filename
=
rawLang
.
slice
(
idx
+
1
);
}
return
<
CodeBlock
language=
{
lang
}
filename=
{
filename
}
code=
{
String
(
children
).
replace
(
/
\n
$/
,
""
)
}
linkedRepo=
{
linkedRepo
}
onCommit=
{
onCommit
}
/>;
if
(
rawLang
.
includes
(
":"
))
{
const
idx
=
rawLang
.
indexOf
(
":"
);
lang
=
rawLang
.
slice
(
0
,
idx
);
filename
=
rawLang
.
slice
(
idx
+
1
);
}
return
(
<
CodeBlock
language=
{
lang
}
filename=
{
filename
}
code=
{
String
(
children
).
replace
(
/
\n
$/
,
""
)
}
linkedRepo=
{
linkedRepo
}
token=
{
token
}
/>
);
},
pre
({
children
})
{
return
<>
{
children
}
</>;
},
}
}
>
{
content
||
""
}
</
ReactMarkdown
>
{
isStreaming
&&
!
isThinking
&&
<
span
className=
"inline-block w-1.5 h-4 bg-anton-accent ml-0.5 animate-pulse"
/>
}
{
isStreaming
&&
!
isThinking
&&
(
<
span
className=
"inline-block w-1.5 h-4 bg-anton-accent ml-0.5 animate-pulse"
/>
)
}
</
div
>
)
}
</
div
>
...
...
@@ -108,10 +136,13 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
{
!
isUser
&&
!
isStreaming
&&
content
&&
(
<
div
className=
"flex items-center gap-3 mt-1.5 px-1"
>
<
button
onClick=
{
handleCopy
}
className=
"flex items-center gap-1 text-[11px] text-anton-muted hover:text-white transition"
>
{
copied
?
<
Check
size=
{
11
}
className=
"text-anton-success"
/>
:
<
Copy
size=
{
11
}
/>
}
{
copied
?
"Copied"
:
"Copy"
}
{
copied
?
<
Check
size=
{
11
}
className=
"text-anton-success"
/>
:
<
Copy
size=
{
11
}
/>
}
{
copied
?
"Copied"
:
"Copy"
}
</
button
>
{
(
input_tokens
>
0
||
output_tokens
>
0
)
&&
(
<
span
className=
"text-[11px] text-anton-muted"
>
{
input_tokens
?.
toLocaleString
()
}
↓ /
{
output_tokens
?.
toLocaleString
()
}
↑
</
span
>
<
span
className=
"text-[11px] text-anton-muted"
>
{
input_tokens
?.
toLocaleString
()
}
↓ /
{
output_tokens
?.
toLocaleString
()
}
↑ tokens
</
span
>
)
}
</
div
>
)
}
...
...
frontend/src/components/RepoFilePanel.jsx
0 → 100644
View file @
02876ff2
This diff is collapsed.
Click to expand it.
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