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
1c05548e
Commit
1c05548e
authored
Mar 30, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update frontend/src/components/MessageBubble.jsx via Son of Anton
parent
c324caf4
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
45 additions
and
2 deletions
+45
-2
MessageBubble.jsx
frontend/src/components/MessageBubble.jsx
+45
-2
No files found.
frontend/src/components/MessageBubble.jsx
View file @
1c05548e
...
...
@@ -2,8 +2,9 @@ import React, { useState, useMemo, useCallback } from "react";
import
ReactMarkdown
from
"react-markdown"
;
import
remarkGfm
from
"remark-gfm"
;
import
CodeBlock
from
"./CodeBlock"
;
import
UIPreview
,
{
buildPreviewHTML
,
isPreviewable
}
from
"./UIPreview"
;
import
{
getAttachmentUrl
,
extractCodeBlocks
,
commitFromChat
,
exportPptx
,
exportDocx
}
from
"../api"
;
import
{
User
,
Flame
,
ChevronDown
,
ChevronRight
,
Brain
,
Copy
,
Check
,
Image
,
Film
,
FileText
,
ExternalLink
,
GitCommitVertical
,
Loader2
,
Presentation
,
FileOutput
}
from
"lucide-react"
;
import
{
User
,
Flame
,
ChevronDown
,
ChevronRight
,
Brain
,
Copy
,
Check
,
Image
,
Film
,
FileText
,
ExternalLink
,
GitCommitVertical
,
Loader2
,
Presentation
,
FileOutput
,
Eye
}
from
"lucide-react"
;
const
FILE_TYPE_ICONS
=
{
image
:
Image
,
video
:
Film
,
document
:
FileText
,
text
:
FileText
};
...
...
@@ -16,6 +17,7 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
const
[
batchCommitting
,
setBatchCommitting
]
=
useState
(
false
);
const
[
batchDone
,
setBatchDone
]
=
useState
(
false
);
const
[
exportingType
,
setExportingType
]
=
useState
(
""
);
const
[
showUIPreview
,
setShowUIPreview
]
=
useState
(
false
);
const
handleCopy
=
useCallback
(()
=>
{
navigator
.
clipboard
.
writeText
(
content
||
""
);
setCopied
(
true
);
setTimeout
(()
=>
setCopied
(
false
),
2000
);
},
[
content
]);
...
...
@@ -24,6 +26,19 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
return
extractCodeBlocks
(
content
).
filter
(
b
=>
b
.
filename
);
},
[
content
,
isUser
,
linkedRepo
]);
// Check if this message has previewable UI code
const
codeBlocks
=
useMemo
(()
=>
{
if
(
isUser
||
!
content
)
return
[];
return
extractCodeBlocks
(
content
);
},
[
content
,
isUser
]);
const
previewable
=
useMemo
(()
=>
isPreviewable
(
codeBlocks
),
[
codeBlocks
]);
const
previewHTML
=
useMemo
(()
=>
{
if
(
!
previewable
)
return
null
;
return
buildPreviewHTML
(
codeBlocks
);
},
[
previewable
,
codeBlocks
]);
async
function
handleBatchCommit
()
{
if
(
!
committableBlocks
.
length
||
!
linkedRepo
||
!
chatId
)
return
;
const
msg
=
prompt
(
`Commit
${
committableBlocks
.
length
}
file(s) to
${
linkedRepo
.
name
}
/
${
linkedRepo
.
default_branch
}
:`
,
`Update
${
committableBlocks
.
length
}
files via Son of Anton`
);
...
...
@@ -88,6 +103,25 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
)
}
</
div
>
{
/* Preview UI Card — shown when message has previewable code */
}
{
!
isUser
&&
!
isStreaming
&&
previewable
&&
previewHTML
&&
(
<
button
onClick=
{
()
=>
setShowUIPreview
(
true
)
}
className=
"mt-2 w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-blue-500/30 bg-blue-500/5 hover:bg-blue-500/10 hover:border-blue-500/50 transition group"
>
<
div
className=
"w-10 h-10 rounded-lg bg-blue-500/20 flex items-center justify-center shrink-0 group-hover:bg-blue-500/30 transition"
>
<
Eye
size=
{
18
}
className=
"text-blue-400"
/>
</
div
>
<
div
className=
"text-left min-w-0"
>
<
p
className=
"text-sm text-white font-medium"
>
Preview UI Design
</
p
>
<
p
className=
"text-[10px] text-blue-400/70"
>
{
codeBlocks
.
length
}
code block
{
codeBlocks
.
length
>
1
?
"s"
:
""
}
• Click to open live preview
</
p
>
</
div
>
<
Eye
size=
{
16
}
className=
"text-blue-400/50 group-hover:text-blue-400 transition ml-auto shrink-0"
/>
</
button
>
)
}
{
!
isUser
&&
!
isStreaming
&&
content
&&
(
<
div
className=
"flex items-center gap-2 sm:gap-3 mt-1.5 px-1 flex-wrap"
>
<
button
onClick=
{
handleCopy
}
className=
"flex items-center gap-1 text-[10px] text-anton-muted hover:text-white transition"
>
{
copied
?
<
Check
size=
{
11
}
className=
"text-anton-success"
/>
:
<
Copy
size=
{
11
}
/>
}
{
copied
?
"Copied"
:
"Copy"
}
</
button
>
...
...
@@ -109,10 +143,19 @@ const MessageBubble = React.memo(function MessageBubble({ message, isStreaming,
)
}
</
div
>
{
isUser
&&
(<
div
className=
"shrink-0 mt-1"
><
div
className=
"w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-anton-card border border-anton-border flex items-center justify-center"
><
User
size=
{
14
}
className=
"text-anton-muted"
/></
div
></
div
>)
}
{
/* UI Preview Modal */
}
{
showUIPreview
&&
previewHTML
&&
(
<
UIPreview
html=
{
previewHTML
}
title=
{
`UI Preview`
}
onClose=
{
()
=>
setShowUIPreview
(
false
)
}
/>
)
}
</
div
>
);
});
function
_stripPrefixes
(
text
)
{
if
(
!
text
)
return
""
;
return
text
.
replace
(
/^
\[(?:
Image|Video|Document|File
)
:
\s[^\]]
*
\]\n?
/gm
,
""
).
trim
();
}
function
_stripPrefixes
(
text
)
{
if
(
!
text
)
return
""
;
return
text
.
replace
(
/^
\[(?:
Image|Video|Document|File
)
:
\s[^\]]
*
\]\n?
/gm
,
""
).
replace
(
/^
\[
UI DESIGN MODE
\][\s\S]
*
?\n\n
/m
,
""
).
trim
();
}
export
default
MessageBubble
;
\ No newline at end of file
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