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
b02ad490
Commit
b02ad490
authored
Mar 31, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
new fix
parent
3102b2dd
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
8398 additions
and
6335 deletions
+8398
-6335
FULL_CODEBASE.txt
FULL_CODEBASE.txt
+8015
-6268
chat_routes.py
backend/routes/chat_routes.py
+116
-20
gitlab_routes.py
backend/routes/gitlab_routes.py
+77
-6
MessageBubble.jsx
frontend/src/components/MessageBubble.jsx
+190
-41
No files found.
FULL_CODEBASE.txt
View file @
b02ad490
This diff is collapsed.
Click to expand it.
backend/routes/chat_routes.py
View file @
b02ad490
...
...
@@ -66,19 +66,16 @@ def list_chats(user: User = Depends(get_current_user), db: Session = Depends(get
@
router
.
post
(
""
)
def
create_chat
(
body
:
CreateChatBody
,
user
:
User
=
Depends
(
get_current_user
),
db
:
Session
=
Depends
(
get_db
)):
perms
=
get_user_permissions
(
user
.
id
,
db
)
# Enforce max chats
max_chats
=
perms
.
get
(
"max_chats"
,
0
)
if
max_chats
>
0
:
current_count
=
count_user_chats
(
user
.
id
,
db
)
if
current_count
>=
max_chats
:
raise
HTTPException
(
403
,
f
"Chat limit reached ({max_chats}). Delete old chats or contact admin."
)
# Validate KB permission
if
body
.
knowledge_base_id
:
if
not
perms
.
get
(
"can_use_knowledge_base"
):
raise
HTTPException
(
403
,
"Knowledge base access not enabled for your account."
)
# Validate GitLab permission
if
body
.
linked_repo_id
:
if
not
perms
.
get
(
"can_use_gitlab"
):
raise
HTTPException
(
403
,
"GitLab access not enabled for your account."
)
...
...
@@ -166,19 +163,16 @@ async def reconnect_stream(chat_id: str, user: User = Depends(get_current_user))
async
def
send_message
(
chat_id
:
str
,
body
:
SendMessageBody
,
user
:
User
=
Depends
(
get_current_user
),
db
:
Session
=
Depends
(
get_db
)):
perms
=
get_user_permissions
(
user
.
id
,
db
)
# Enforce daily message limit
max_per_day
=
perms
.
get
(
"max_messages_per_day"
,
0
)
if
max_per_day
>
0
:
today_count
=
count_user_messages_today
(
user
.
id
,
db
)
if
today_count
>=
max_per_day
:
raise
HTTPException
(
429
,
f
"Daily message limit reached ({max_per_day}). Try again tomorrow."
)
# Enforce web search permission
web_search
=
body
.
web_search
if
web_search
and
not
perms
.
get
(
"can_use_web_search"
):
web_search
=
False
# Enforce attachment permission
if
body
.
attachment_ids
and
not
perms
.
get
(
"can_use_attachments"
):
raise
HTTPException
(
403
,
"File attachments not enabled for your account."
)
...
...
@@ -187,7 +181,6 @@ async def send_message(chat_id: str, body: SendMessageBody, user: User = Depends
if
len
(
body
.
attachment_ids
)
>
max_att
:
raise
HTTPException
(
400
,
f
"Too many attachments. Max {max_att} per message."
)
# Enforce model & limits
model
=
check_model_allowed
(
user
.
id
,
body
.
model
or
"eu.anthropic.claude-opus-4-6-v1"
,
db
)
max_tokens
=
min
(
body
.
max_tokens
,
perms
.
get
(
"max_tokens_cap"
,
4096
))
reasoning_budget
=
min
(
body
.
reasoning_budget
,
perms
.
get
(
"max_reasoning_budget"
,
0
))
...
...
@@ -206,21 +199,100 @@ async def send_message(chat_id: str, body: SendMessageBody, user: User = Depends
@
router
.
post
(
"/{chat_id}/commit"
)
async
def
commit_from_chat
(
chat_id
:
str
,
body
:
CommitFromChatBody
,
user
:
User
=
Depends
(
get_current_user
),
db
:
Session
=
Depends
(
get_db
)):
async
def
commit_from_chat
(
chat_id
:
str
,
body
:
CommitFromChatBody
,
user
:
User
=
Depends
(
get_current_user
),
db
:
Session
=
Depends
(
get_db
),
):
"""
Commit files from chat to linked GitLab repo.
Auto-detects whether each file should be 'create' or 'update'
by checking the repo tree, so it never fails on wrong action type.
"""
check_feature
(
user
.
id
,
"use_gitlab"
,
db
)
chat
=
db
.
query
(
Chat
)
.
filter
(
Chat
.
id
==
chat_id
,
Chat
.
user_id
==
user
.
id
)
.
first
()
if
not
chat
:
raise
HTTPException
(
404
,
"Chat not found"
)
if
not
chat
.
linked_repo_id
:
raise
HTTPException
(
400
,
"No repository linked"
)
if
not
chat
:
raise
HTTPException
(
404
,
"Chat not found"
)
if
not
chat
.
linked_repo_id
:
raise
HTTPException
(
400
,
"No repository linked"
)
repo
=
db
.
query
(
LinkedRepo
)
.
filter
(
LinkedRepo
.
id
==
chat
.
linked_repo_id
)
.
first
()
if
not
repo
:
raise
HTTPException
(
404
,
"Linked repository not found"
)
if
not
repo
:
raise
HTTPException
(
404
,
"Linked repository not found"
)
settings
=
db
.
query
(
GitLabSettings
)
.
first
()
if
not
settings
or
not
settings
.
is_active
:
raise
HTTPException
(
400
,
"GitLab not configured"
)
actions
=
[{
"action"
:
f
.
get
(
"action"
,
"update"
),
"file_path"
:
f
[
"file_path"
],
"content"
:
f
[
"content"
]}
for
f
in
body
.
files
]
if
not
actions
:
raise
HTTPException
(
400
,
"No files to commit"
)
if
not
settings
or
not
settings
.
is_active
:
raise
HTTPException
(
400
,
"GitLab not configured"
)
# ── Fetch repo tree to know which files already exist ──
existing_paths
=
set
()
try
:
tree
=
await
gitlab_service
.
get_tree
(
settings
.
gitlab_url
,
settings
.
gitlab_token
,
repo
.
gitlab_project_id
,
ref
=
body
.
branch
,
recursive
=
True
,
)
existing_paths
=
{
item
[
"path"
]
for
item
in
tree
if
item
[
"type"
]
==
"blob"
}
except
Exception
:
# If tree fetch fails (empty repo, network issue, etc.),
# we'll try all as "create" since we can't know what exists
pass
# ── Build actions with auto-detected create/update ──
actions
=
[]
for
f
in
body
.
files
:
file_path
=
f
.
get
(
"file_path"
,
""
)
content
=
f
.
get
(
"content"
,
""
)
requested_action
=
f
.
get
(
"action"
,
"auto"
)
if
not
file_path
or
not
content
:
continue
file_exists
=
file_path
in
existing_paths
# Smart action resolution
if
requested_action
in
(
"auto"
,
"upsert"
):
# Auto-detect: update if exists, create if not
actual_action
=
"update"
if
file_exists
else
"create"
elif
requested_action
==
"update"
and
not
file_exists
:
# User said update but file doesn't exist → create instead
actual_action
=
"create"
elif
requested_action
==
"create"
and
file_exists
:
# User said create but file already exists → update instead
actual_action
=
"update"
else
:
actual_action
=
requested_action
actions
.
append
({
"action"
:
actual_action
,
"file_path"
:
file_path
,
"content"
:
content
,
})
if
not
actions
:
raise
HTTPException
(
400
,
"No valid files to commit"
)
try
:
result
=
await
gitlab_service
.
commit_files
(
settings
.
gitlab_url
,
settings
.
gitlab_token
,
repo
.
gitlab_project_id
,
body
.
branch
,
body
.
commit_message
,
actions
)
result
=
await
gitlab_service
.
commit_files
(
settings
.
gitlab_url
,
settings
.
gitlab_token
,
repo
.
gitlab_project_id
,
body
.
branch
,
body
.
commit_message
,
actions
,
)
gen_manager
.
invalidate_repo_cache
(
repo
.
id
)
return
{
"ok"
:
True
,
"commit"
:
result
,
"files_committed"
:
len
(
actions
)}
return
{
"ok"
:
True
,
"commit"
:
result
,
"files_committed"
:
len
(
actions
),
}
except
gitlab_service
.
GitLabError
as
e
:
raise
HTTPException
(
e
.
status_code
,
f
"Commit failed: {e.detail}"
)
...
...
@@ -238,17 +310,41 @@ def _sse(data):
def
_chat_dict
(
c
,
db
=
None
):
d
=
{
"id"
:
c
.
id
,
"title"
:
c
.
title
,
"model"
:
c
.
model
,
"knowledge_base_id"
:
c
.
knowledge_base_id
,
"linked_repo_id"
:
c
.
linked_repo_id
,
"max_tokens"
:
c
.
max_tokens
or
4096
,
"reasoning_budget"
:
c
.
reasoning_budget
or
0
,
"created_at"
:
str
(
c
.
created_at
),
"updated_at"
:
str
(
c
.
updated_at
)}
d
=
{
"id"
:
c
.
id
,
"title"
:
c
.
title
,
"model"
:
c
.
model
,
"knowledge_base_id"
:
c
.
knowledge_base_id
,
"linked_repo_id"
:
c
.
linked_repo_id
,
"max_tokens"
:
c
.
max_tokens
or
4096
,
"reasoning_budget"
:
c
.
reasoning_budget
or
0
,
"created_at"
:
str
(
c
.
created_at
),
"updated_at"
:
str
(
c
.
updated_at
),
}
if
db
and
c
.
linked_repo_id
:
repo
=
db
.
query
(
LinkedRepo
)
.
filter
(
LinkedRepo
.
id
==
c
.
linked_repo_id
)
.
first
()
if
repo
:
d
[
"linked_repo"
]
=
{
"id"
:
repo
.
id
,
"name"
:
repo
.
name
,
"path_with_namespace"
:
repo
.
path_with_namespace
,
"default_branch"
:
repo
.
default_branch
,
"web_url"
:
repo
.
web_url
,
"gitlab_project_id"
:
repo
.
gitlab_project_id
,
"map_status"
:
repo
.
map_status
}
d
[
"linked_repo"
]
=
{
"id"
:
repo
.
id
,
"name"
:
repo
.
name
,
"path_with_namespace"
:
repo
.
path_with_namespace
,
"default_branch"
:
repo
.
default_branch
,
"web_url"
:
repo
.
web_url
,
"gitlab_project_id"
:
repo
.
gitlab_project_id
,
"map_status"
:
repo
.
map_status
,
}
return
d
def
_msg_dict
(
m
):
return
{
"id"
:
m
.
id
,
"role"
:
m
.
role
,
"content"
:
m
.
content
,
"thinking_content"
:
m
.
thinking_content
,
"input_tokens"
:
m
.
input_tokens
,
"output_tokens"
:
m
.
output_tokens
,
"created_at"
:
str
(
m
.
created_at
)}
return
{
"id"
:
m
.
id
,
"role"
:
m
.
role
,
"content"
:
m
.
content
,
"thinking_content"
:
m
.
thinking_content
,
"input_tokens"
:
m
.
input_tokens
,
"output_tokens"
:
m
.
output_tokens
,
"created_at"
:
str
(
m
.
created_at
),
}
def
_att_brief
(
a
):
return
{
"id"
:
a
.
id
,
"original_filename"
:
a
.
original_filename
,
"mime_type"
:
a
.
mime_type
,
"file_type"
:
a
.
file_type
,
"file_size"
:
a
.
file_size
}
\ No newline at end of file
return
{
"id"
:
a
.
id
,
"original_filename"
:
a
.
original_filename
,
"mime_type"
:
a
.
mime_type
,
"file_type"
:
a
.
file_type
,
"file_size"
:
a
.
file_size
,
}
\ No newline at end of file
backend/routes/gitlab_routes.py
View file @
b02ad490
"""
GitLab CE integration routes — superadmin only.
Son of Anton v4.
0
.0
Son of Anton v4.
2
.0
"""
import
asyncio
...
...
@@ -46,7 +46,7 @@ class SingleCommitBody(BaseModel):
file_path
:
str
content
:
str
commit_message
:
str
action
:
str
=
"
update
"
action
:
str
=
"
auto
"
class
BranchBody
(
BaseModel
):
branch_name
:
str
...
...
@@ -188,7 +188,6 @@ async def link_repo(body: LinkRepoBody, admin: User = Depends(require_superadmin
db
.
commit
()
db
.
refresh
(
repo
)
# Start background analysis
asyncio
.
create_task
(
_analyze_repo_background
(
repo
.
id
,
s
.
gitlab_url
,
s
.
gitlab_token
,
project
[
"id"
],
project
.
get
(
"default_branch"
,
"main"
),
...
...
@@ -347,12 +346,56 @@ async def create_branch(repo_id: str, body: BranchBody, admin: User = Depends(re
@
router
.
post
(
"/repos/{repo_id}/commit"
)
async
def
commit_code
(
repo_id
:
str
,
body
:
CommitBody
,
admin
:
User
=
Depends
(
require_superadmin
),
db
:
Session
=
Depends
(
get_db
)):
"""
Commit multiple files. Auto-detects create vs update per file.
"""
s
=
_get_settings
(
db
)
repo
=
_get_repo
(
repo_id
,
db
)
# Fetch tree to know which files exist
existing_paths
=
set
()
try
:
tree
=
await
gitlab_service
.
get_tree
(
s
.
gitlab_url
,
s
.
gitlab_token
,
repo
.
gitlab_project_id
,
ref
=
body
.
branch
,
recursive
=
True
,
)
existing_paths
=
{
item
[
"path"
]
for
item
in
tree
if
item
[
"type"
]
==
"blob"
}
except
Exception
:
pass
resolved_actions
=
[]
for
a
in
body
.
actions
:
file_path
=
a
.
get
(
"file_path"
,
""
)
content
=
a
.
get
(
"content"
,
""
)
requested
=
a
.
get
(
"action"
,
"auto"
)
if
not
file_path
:
continue
file_exists
=
file_path
in
existing_paths
if
requested
in
(
"auto"
,
"upsert"
):
actual
=
"update"
if
file_exists
else
"create"
elif
requested
==
"update"
and
not
file_exists
:
actual
=
"create"
elif
requested
==
"create"
and
file_exists
:
actual
=
"update"
else
:
actual
=
requested
resolved_actions
.
append
({
"action"
:
actual
,
"file_path"
:
file_path
,
"content"
:
content
,
})
if
not
resolved_actions
:
raise
HTTPException
(
400
,
"No valid files to commit"
)
try
:
result
=
await
gitlab_service
.
commit_files
(
s
.
gitlab_url
,
s
.
gitlab_token
,
repo
.
gitlab_project_id
,
body
.
branch
,
body
.
commit_message
,
body
.
actions
,
body
.
branch
,
body
.
commit_message
,
resolved_
actions
,
)
return
result
except
gitlab_service
.
GitLabError
as
e
:
...
...
@@ -360,13 +403,41 @@ async def commit_code(repo_id: str, body: CommitBody, admin: User = Depends(requ
@
router
.
post
(
"/repos/{repo_id}/commit-single"
)
async
def
commit_single
(
repo_id
:
str
,
body
:
SingleCommitBody
,
admin
:
User
=
Depends
(
require_superadmin
),
db
:
Session
=
Depends
(
get_db
)):
async
def
commit_single
(
repo_id
:
str
,
body
:
SingleCommitBody
,
admin
:
User
=
Depends
(
require_superadmin
),
db
:
Session
=
Depends
(
get_db
),
):
"""
Commit a single file. Auto-detects create vs update.
"""
s
=
_get_settings
(
db
)
repo
=
_get_repo
(
repo_id
,
db
)
# Auto-detect whether file exists
action
=
body
.
action
if
action
in
(
"update"
,
"create"
,
"auto"
):
try
:
await
gitlab_service
.
get_file_content
(
s
.
gitlab_url
,
s
.
gitlab_token
,
repo
.
gitlab_project_id
,
body
.
file_path
,
ref
=
body
.
branch
,
)
file_exists
=
True
except
gitlab_service
.
GitLabError
:
file_exists
=
False
if
action
==
"auto"
:
action
=
"update"
if
file_exists
else
"create"
elif
action
==
"update"
and
not
file_exists
:
action
=
"create"
elif
action
==
"create"
and
file_exists
:
action
=
"update"
try
:
result
=
await
gitlab_service
.
commit_single_file
(
s
.
gitlab_url
,
s
.
gitlab_token
,
repo
.
gitlab_project_id
,
body
.
branch
,
body
.
file_path
,
body
.
content
,
body
.
commit_message
,
body
.
action
,
body
.
branch
,
body
.
file_path
,
body
.
content
,
body
.
commit_message
,
action
,
)
return
result
except
gitlab_service
.
GitLabError
as
e
:
...
...
frontend/src/components/MessageBubble.jsx
View file @
b02ad490
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