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
17ddd732
Commit
17ddd732
authored
Mar 29, 2026
by
AGLANPC\aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ghvntumyutj mtyj mtyjthy nv
parent
f8727239
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
2260 additions
and
3683 deletions
+2260
-3683
FULL_CODEBASE.txt
FULL_CODEBASE.txt
+1975
-2872
gitlab_routes.py
backend/routes/gitlab_routes.py
+5
-11
api.js
frontend/src/api.js
+71
-113
ChatView.jsx
frontend/src/components/ChatView.jsx
+165
-171
CodeBlock.jsx
frontend/src/components/CodeBlock.jsx
+27
-110
MessageBubble.jsx
frontend/src/components/MessageBubble.jsx
+16
-47
RepoFilePanel.jsx
frontend/src/components/RepoFilePanel.jsx
+0
-358
GitLabPage.jsx
frontend/src/pages/GitLabPage.jsx
+1
-1
No files found.
FULL_CODEBASE.txt
View file @
17ddd732
This diff is collapsed.
Click to expand it.
backend/routes/gitlab_routes.py
View file @
17ddd732
...
...
@@ -109,18 +109,12 @@ def update_settings(body: SettingsBody, admin: User = Depends(require_superadmin
@
router
.
post
(
"/test-connection"
)
async
def
test_connection
(
body
:
SettingsBody
,
admin
:
User
=
Depends
(
require_superadmin
),
db
:
Session
=
Depends
(
get_db
)):
url
=
body
.
gitlab_url
.
rstrip
(
"/"
)
if
not
body
.
gitlab_token
or
body
.
gitlab_token
==
"UNCHANGED"
:
s
=
db
.
query
(
GitLabSettings
)
.
first
()
token
=
s
.
gitlab_token
if
s
else
""
else
:
token
=
body
.
gitlab_token
if
not
url
or
not
token
:
raise
HTTPException
(
400
,
"GitLab URL and token not provided"
)
async
def
test_connection
(
admin
:
User
=
Depends
(
require_superadmin
),
db
:
Session
=
Depends
(
get_db
)):
s
=
db
.
query
(
GitLabSettings
)
.
first
()
if
not
s
or
not
s
.
gitlab_url
or
not
s
.
gitlab_token
:
raise
HTTPException
(
400
,
"GitLab URL and token not configured"
)
try
:
result
=
await
gitlab_service
.
test_connection
(
url
,
token
)
result
=
await
gitlab_service
.
test_connection
(
s
.
gitlab_url
,
s
.
gitlab_
token
)
return
result
except
gitlab_service
.
GitLabError
as
e
:
raise
HTTPException
(
e
.
status_code
,
f
"Connection failed: {e.detail}"
)
...
...
frontend/src/api.js
View file @
17ddd732
This diff is collapsed.
Click to expand it.
frontend/src/components/ChatView.jsx
View file @
17ddd732
This diff is collapsed.
Click to expand it.
frontend/src/components/CodeBlock.jsx
View file @
17ddd732
import
React
,
{
useState
}
from
"react"
;
import
{
Prism
as
SyntaxHighlighter
}
from
"react-syntax-highlighter"
;
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"
;
import
{
oneDark
}
from
"react-syntax-highlighter/dist/esm/styles/prism"
;
import
{
Copy
,
Check
,
Download
,
GitCommitVertical
}
from
"lucide-react"
;
const
COMMIT_STATES
=
{
idle
:
"idle"
,
checking
:
"checking"
,
committing
:
"committing"
,
success
:
"success"
,
error
:
"error"
};
export
default
function
CodeBlock
({
language
,
filename
,
code
,
linkedRepo
,
token
})
{
export
default
function
CodeBlock
({
language
,
filename
,
code
,
linkedRepo
,
onCommit
})
{
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
);
...
...
@@ -29,119 +22,43 @@ export default function CodeBlock({ language, filename, code, linkedRepo, token
URL
.
revokeObjectURL
(
url
);
}
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"
);
}
function
handleCommit
()
{
if
(
!
onCommit
||
!
filename
)
return
;
onCommit
(
filename
,
code
,
"update"
);
}
const
canCommit
=
linkedRepo
&&
filename
&&
token
;
return
(
<
div
className=
"
rounded-lg overflow-hidden border border-anton-border my-3
"
>
<
div
className=
"
my-3 rounded-xl overflow-hidden border border-anton-border bg-[#1a1b26]
"
>
{
/* Header */
}
<
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
>
)
}
{
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
}
/>
<
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
</
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
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
}
/>
}
</
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=
{
vscDarkPlus
}
customStyle=
{
{
margin
:
0
,
padding
:
"1rem"
,
fontSize
:
"0.8rem"
,
background
:
"#1a1a2e"
,
maxHeight
:
"500px"
}
}
showLineNumbers
lineNumberStyle=
{
{
minWidth
:
"2.5em"
,
paddingRight
:
"1em"
,
color
:
"#555"
}
}
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
>
{
code
}
</
SyntaxHighlighter
>
...
...
frontend/src/components/MessageBubble.jsx
View file @
17ddd732
...
...
@@ -8,22 +8,16 @@ 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
})
{
const
MessageBubble
=
React
.
memo
(
function
MessageBubble
({
message
,
isStreaming
,
isThinking
,
token
,
linkedRepo
,
onCommit
})
{
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
;
...
...
@@ -40,16 +34,14 @@ 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
>
...
...
@@ -57,7 +49,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"
)
{
...
...
@@ -65,18 +57,13 @@ 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
>
);
}
...
...
@@ -95,8 +82,7 @@ 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
>
)
:
(
...
...
@@ -107,28 +93,14 @@ 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
}
token=
{
token
}
/>
);
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
}
/>;
},
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
>
...
...
@@ -136,13 +108,10 @@ 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
()
}
↑ tokens
</
span
>
<
span
className=
"text-[11px] text-anton-muted"
>
{
input_tokens
?.
toLocaleString
()
}
↓ /
{
output_tokens
?.
toLocaleString
()
}
↑
</
span
>
)
}
</
div
>
)
}
...
...
frontend/src/components/RepoFilePanel.jsx
deleted
100644 → 0
View file @
f8727239
This diff is collapsed.
Click to expand it.
frontend/src/pages/GitLabPage.jsx
View file @
17ddd732
...
...
@@ -70,7 +70,7 @@ export default function GitLabPage() {
async
function
handleTest
()
{
setTesting
(
true
);
setTestResult
(
null
);
try
{
const
r
=
await
gitlabTestConnection
(
t
,
{
gitlab_url
:
url
,
gitlab_token
:
token
||
"UNCHANGED"
}
);
const
r
=
await
gitlabTestConnection
(
t
);
setTestResult
({
ok
:
true
,
msg
:
`Connected as
${
r
.
name
}
(@
${
r
.
username
}
)`
});
}
catch
(
e
)
{
setTestResult
({
ok
:
false
,
msg
:
e
.
message
});
}
setTesting
(
false
);
...
...
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