Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
math_tutor_dev
public_math_tutor
Commits
8cedace3
Commit
8cedace3
authored
Mar 13, 2026
by
Kantz
Browse files
thinkging hinter einem Button
parent
a242bdc4
Changes
3
Hide whitespace changes
Inline
Side-by-side
math-tutor/frontend/src/components/Chat/MessageBubble.tsx
View file @
8cedace3
...
...
@@ -29,9 +29,103 @@ export default function MessageBubble({
}:
MessageBubbleProps
)
{
const
bubbleRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
const
[
isRawView
,
setIsRawView
]
=
useState
(
false
);
const
renderedText
=
useMemo
(
()
=>
escapeAsterisksInsideMath
(
text
.
replaceAll
(
"
</think>
"
,
"
\n
---
\n
"
)),
[
text
]
const
[
isThinkingExpanded
,
setIsThinkingExpanded
]
=
useState
(
false
);
const
{
thinkingText
,
answerText
}
=
useMemo
(()
=>
{
const
parts
=
text
.
split
(
"
</think>
"
);
if
(
role
!==
"
assistant
"
||
parts
.
length
<
2
)
{
return
{
thinkingText
:
""
,
answerText
:
escapeAsterisksInsideMath
(
text
),
};
}
const
rawThinking
=
parts
.
slice
(
0
,
-
1
).
join
(
"
</think>
"
).
trim
();
const
rawAnswer
=
parts
[
parts
.
length
-
1
].
trim
();
return
{
thinkingText
:
escapeAsterisksInsideMath
(
rawThinking
),
answerText
:
escapeAsterisksInsideMath
(
rawAnswer
),
};
},
[
role
,
text
]);
const
hasThinking
=
Boolean
(
thinkingText
);
useEffect
(()
=>
{
setIsThinkingExpanded
(
false
);
},
[
text
]);
const
renderMarkdown
=
(
value
:
string
)
=>
(
<
ReactMarkdown
urlTransform
=
{
(
url
)
=>
{
if
(
url
.
startsWith
(
"
doc://
"
))
{
return
url
;
}
return
defaultUrlTransform
(
url
);
}
}
components
=
{
{
a
:
({
href
,
children
,
...
props
})
=>
{
const
target
=
href
||
""
;
if
(
!
target
.
startsWith
(
"
doc://
"
))
{
const
mdMatch
=
target
.
match
(
/
([^/]
+
)\.
md$/i
);
if
(
!
mdMatch
)
{
return
(
<
a
href
=
{
href
}
target
=
"_blank"
rel
=
"noreferrer"
{
...
props
}
>
{
children
}
</
a
>
);
}
const
file
=
mdMatch
[
1
]
||
""
;
const
slugFromFile
=
file
.
toLowerCase
()
.
replace
(
/ä/g
,
"
ae
"
)
.
replace
(
/ö/g
,
"
oe
"
)
.
replace
(
/ü/g
,
"
ue
"
)
.
replace
(
/ß/g
,
"
ss
"
)
.
replace
(
/
[^
a-z0-9
]
+/g
,
"
-
"
)
.
replace
(
/^-+|-+$/g
,
""
);
const
slug
=
slugFromFile
.
split
(
"
-
"
).
pop
()
||
slugFromFile
;
const
doc
=
docSlugIndex
?
docSlugIndex
[
slug
]
:
undefined
;
return
(
<
a
href
=
{
href
}
onClick
=
{
(
event
)
=>
{
if
(
!
doc
||
!
onInspectDoc
)
{
return
;
}
event
.
preventDefault
();
onInspectDoc
(
doc
);
}
}
{
...
props
}
>
{
children
}
</
a
>
);
}
const
key
=
decodeURIComponent
(
target
.
slice
(
"
doc://
"
.
length
));
const
doc
=
docIndex
?
docIndex
[
key
]
:
undefined
;
return
(
<
a
href
=
{
href
}
onClick
=
{
(
event
)
=>
{
if
(
!
doc
||
!
onInspectDoc
)
{
return
;
}
event
.
preventDefault
();
onInspectDoc
(
doc
);
}
}
{
...
props
}
>
{
children
}
</
a
>
);
},
}
}
>
{
value
}
</
ReactMarkdown
>
);
useEffect
(()
=>
{
...
...
@@ -55,7 +149,7 @@ export default function MessageBubble({
script
.
addEventListener
(
"
load
"
,
typeset
);
return
()
=>
script
.
removeEventListener
(
"
load
"
,
typeset
);
}
},
[
t
ext
,
isRawView
]);
},
[
answerT
ext
,
isRawView
,
isThinkingExpanded
,
thinkingText
]);
return
(
<
div
className
=
{
`message-bubble
${
role
}
`
}
ref
=
{
bubbleRef
}
>
...
...
@@ -77,82 +171,24 @@ export default function MessageBubble({
{
isRawView
?
(
<
pre
className
=
"message-raw-text"
>
{
text
}
</
pre
>
)
:
(
<
ReactMarkdown
urlTransform
=
{
(
url
)
=>
{
if
(
url
.
startsWith
(
"
doc://
"
))
{
return
url
;
}
return
defaultUrlTransform
(
url
);
}
}
components
=
{
{
a
:
({
href
,
children
,
...
props
})
=>
{
const
target
=
href
||
""
;
if
(
!
target
.
startsWith
(
"
doc://
"
))
{
const
mdMatch
=
target
.
match
(
/
([^/]
+
)\.
md$/i
);
if
(
!
mdMatch
)
{
return
(
<
a
href
=
{
href
}
target
=
"_blank"
rel
=
"noreferrer"
{
...
props
}
>
{
children
}
</
a
>
);
}
const
file
=
mdMatch
[
1
]
||
""
;
const
slugFromFile
=
file
.
toLowerCase
()
.
replace
(
/ä/g
,
"
ae
"
)
.
replace
(
/ö/g
,
"
oe
"
)
.
replace
(
/ü/g
,
"
ue
"
)
.
replace
(
/ß/g
,
"
ss
"
)
.
replace
(
/
[^
a-z0-9
]
+/g
,
"
-
"
)
.
replace
(
/^-+|-+$/g
,
""
);
const
slug
=
slugFromFile
.
split
(
"
-
"
).
pop
()
||
slugFromFile
;
const
doc
=
docSlugIndex
?
docSlugIndex
[
slug
]
:
undefined
;
return
(
<
a
href
=
{
href
}
onClick
=
{
(
event
)
=>
{
if
(
!
doc
||
!
onInspectDoc
)
{
return
;
}
event
.
preventDefault
();
onInspectDoc
(
doc
);
}
}
{
...
props
}
>
{
children
}
</
a
>
);
}
const
key
=
decodeURIComponent
(
target
.
slice
(
"
doc://
"
.
length
));
const
doc
=
docIndex
?
docIndex
[
key
]
:
undefined
;
return
(
<
a
href
=
{
href
}
onClick
=
{
(
event
)
=>
{
if
(
!
doc
||
!
onInspectDoc
)
{
return
;
}
event
.
preventDefault
();
onInspectDoc
(
doc
);
}
}
{
...
props
}
>
{
children
}
</
a
>
);
},
}
}
>
{
renderedText
}
</
ReactMarkdown
>
<>
{
hasThinking
?
(
<
div
className
=
"message-thinking"
>
<
button
type
=
"button"
className
=
"message-thinking-toggle"
aria-expanded
=
{
isThinkingExpanded
}
onClick
=
{
()
=>
setIsThinkingExpanded
((
prev
)
=>
!
prev
)
}
>
{
isThinkingExpanded
?
t
(
"
hideThinking
"
)
:
t
(
"
showThinking
"
)
}
</
button
>
{
isThinkingExpanded
?
(
<
div
className
=
"message-thinking-content"
>
{
renderMarkdown
(
thinkingText
)
}
</
div
>
)
:
null
}
</
div
>
)
:
null
}
<
div
className
=
"message-answer"
>
{
renderMarkdown
(
answerText
)
}
</
div
>
</>
)
}
</
div
>
</
div
>
...
...
math-tutor/frontend/src/i18n.ts
View file @
8cedace3
...
...
@@ -40,6 +40,8 @@
roleAssistant
:
"
assistant
"
,
showSource
:
"
Show source
"
,
showRendered
:
"
Show rendered
"
,
showThinking
:
"
Show thinking
"
,
hideThinking
:
"
Hide thinking
"
,
noTasksAvailable
:
"
No tasks available
"
,
noTaskSelected
:
"
No task selected.
"
,
savedChats
:
"
Saved Chats
"
,
...
...
@@ -117,6 +119,8 @@
roleAssistant
:
"
assistent
"
,
showSource
:
"
Formeltext anzeigen
"
,
showRendered
:
"
Gerendert anzeigen
"
,
showThinking
:
"
Thinking anzeigen
"
,
hideThinking
:
"
Thinking ausblenden
"
,
noTasksAvailable
:
"
Keine Aufgaben verfügbar
"
,
noTaskSelected
:
"
Keine Aufgabe ausgewählt.
"
,
savedChats
:
"
Gespeicherte Chats
"
,
...
...
math-tutor/frontend/src/styles/theme.css
View file @
8cedace3
...
...
@@ -466,6 +466,34 @@ body {
overflow-wrap
:
anywhere
;
}
.message-thinking
{
margin-bottom
:
12px
;
padding-bottom
:
12px
;
border-bottom
:
1px
dashed
#d8d1c4
;
}
.message-thinking-toggle
{
border
:
1px
solid
#d8d1c4
;
background
:
#f6f0e4
;
color
:
#6f675d
;
border-radius
:
999px
;
padding
:
4px
10px
;
font-size
:
11px
;
cursor
:
pointer
;
}
.message-thinking-toggle
:hover
{
background
:
#efe6d4
;
}
.message-thinking-content
{
margin-top
:
10px
;
padding
:
10px
12px
;
border-radius
:
10px
;
background
:
#fffaf0
;
border
:
1px
solid
#e2ded5
;
}
.composer
{
display
:
flex
;
flex-direction
:
column
;
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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