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
01c1d3c3
Commit
01c1d3c3
authored
Mar 24, 2026
by
Kantz
Browse files
verschiebung des upload buttons
parent
9f1fcd5a
Changes
5
Hide whitespace changes
Inline
Side-by-side
math-tutor/frontend/src/components/Chat/ChatWindow.tsx
View file @
01c1d3c3
...
@@ -11,6 +11,7 @@ type ChatWindowProps = {
...
@@ -11,6 +11,7 @@ type ChatWindowProps = {
onSend
:
()
=>
void
;
onSend
:
()
=>
void
;
onHistoryNavigate
?:
(
direction
:
"
older
"
|
"
newer
"
)
=>
boolean
;
onHistoryNavigate
?:
(
direction
:
"
older
"
|
"
newer
"
)
=>
boolean
;
onToggleCanvas
?:
()
=>
void
;
onToggleCanvas
?:
()
=>
void
;
onUploadSolution
?:
(
file
:
File
)
=>
void
|
Promise
<
void
>
;
onInspectDoc
?:
(
doc
:
RetrievedDoc
)
=>
void
;
onInspectDoc
?:
(
doc
:
RetrievedDoc
)
=>
void
;
docIndex
?:
Record
<
string
,
RetrievedDoc
>
;
docIndex
?:
Record
<
string
,
RetrievedDoc
>
;
docSlugIndex
?:
Record
<
string
,
RetrievedDoc
>
;
docSlugIndex
?:
Record
<
string
,
RetrievedDoc
>
;
...
@@ -23,6 +24,7 @@ export default function ChatWindow({
...
@@ -23,6 +24,7 @@ export default function ChatWindow({
onSend
,
onSend
,
onHistoryNavigate
,
onHistoryNavigate
,
onToggleCanvas
,
onToggleCanvas
,
onUploadSolution
,
onInspectDoc
,
onInspectDoc
,
docIndex
,
docIndex
,
docSlugIndex
,
docSlugIndex
,
...
@@ -42,6 +44,7 @@ export default function ChatWindow({
...
@@ -42,6 +44,7 @@ export default function ChatWindow({
onSend
=
{
onSend
}
onSend
=
{
onSend
}
onHistoryNavigate
=
{
onHistoryNavigate
}
onHistoryNavigate
=
{
onHistoryNavigate
}
onToggleCanvas
=
{
onToggleCanvas
}
onToggleCanvas
=
{
onToggleCanvas
}
onUploadSolution
=
{
onUploadSolution
}
/>
/>
</
div
>
</
div
>
);
);
...
...
math-tutor/frontend/src/components/Chat/MessageInput.tsx
View file @
01c1d3c3
import
{
EditPencil
,
Send
}
from
"
iconoir-react
"
;
import
{
useRef
}
from
"
react
"
;
import
{
EditPencil
,
Send
,
Upload
}
from
"
iconoir-react
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
t
}
from
"
../../i18n
"
;
type
MessageInputProps
=
{
type
MessageInputProps
=
{
...
@@ -7,6 +8,7 @@ type MessageInputProps = {
...
@@ -7,6 +8,7 @@ type MessageInputProps = {
onSend
:
()
=>
void
;
onSend
:
()
=>
void
;
onHistoryNavigate
?:
(
direction
:
"
older
"
|
"
newer
"
)
=>
boolean
;
onHistoryNavigate
?:
(
direction
:
"
older
"
|
"
newer
"
)
=>
boolean
;
onToggleCanvas
?:
()
=>
void
;
onToggleCanvas
?:
()
=>
void
;
onUploadSolution
?:
(
file
:
File
)
=>
void
|
Promise
<
void
>
;
};
};
export
default
function
MessageInput
({
export
default
function
MessageInput
({
...
@@ -15,8 +17,25 @@ export default function MessageInput({
...
@@ -15,8 +17,25 @@ export default function MessageInput({
onSend
,
onSend
,
onHistoryNavigate
,
onHistoryNavigate
,
onToggleCanvas
,
onToggleCanvas
,
onUploadSolution
,
}:
MessageInputProps
)
{
}:
MessageInputProps
)
{
const
canSend
=
value
.
trim
().
length
>
0
;
const
canSend
=
value
.
trim
().
length
>
0
;
const
uploadInputRef
=
useRef
<
HTMLInputElement
|
null
>
(
null
);
const
handleUploadClick
=
()
=>
{
uploadInputRef
.
current
?.
click
();
};
const
handleUploadChange
=
async
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
file
=
event
.
target
.
files
?.[
0
];
event
.
target
.
value
=
""
;
if
(
!
file
)
{
return
;
}
await
onUploadSolution
?.(
file
);
};
return
(
return
(
<
div
className
=
"composer"
>
<
div
className
=
"composer"
>
...
@@ -30,6 +49,22 @@ export default function MessageInput({
...
@@ -30,6 +49,22 @@ export default function MessageInput({
>
>
<
EditPencil
width
=
{
18
}
height
=
{
18
}
aria-hidden
=
"true"
/>
<
EditPencil
width
=
{
18
}
height
=
{
18
}
aria-hidden
=
"true"
/>
</
button
>
</
button
>
<
button
className
=
"btn"
type
=
"button"
onClick
=
{
handleUploadClick
}
aria-label
=
{
t
(
"
uploadSolution
"
)
}
title
=
{
t
(
"
uploadSolution
"
)
}
>
<
Upload
width
=
{
18
}
height
=
{
18
}
aria-hidden
=
"true"
/>
</
button
>
<
input
ref
=
{
uploadInputRef
}
type
=
"file"
accept
=
"image/*"
className
=
"canvas-upload-input"
onChange
=
{
handleUploadChange
}
/>
<
button
<
button
className
=
"btn primary"
className
=
"btn primary"
type
=
"button"
type
=
"button"
...
...
math-tutor/frontend/src/i18n.ts
View file @
01c1d3c3
...
@@ -12,6 +12,7 @@
...
@@ -12,6 +12,7 @@
pen
:
"
Pen
"
,
pen
:
"
Pen
"
,
clear
:
"
Clear
"
,
clear
:
"
Clear
"
,
saveAndConvert
:
"
Save + Convert
"
,
saveAndConvert
:
"
Save + Convert
"
,
uploadSolution
:
"
Upload solution image
"
,
hide
:
"
Hide
"
,
hide
:
"
Hide
"
,
show
:
"
Show
"
,
show
:
"
Show
"
,
newChat
:
"
New Chat
"
,
newChat
:
"
New Chat
"
,
...
@@ -55,6 +56,10 @@
...
@@ -55,6 +56,10 @@
failedLoadSavedChats
:
"
Could not load saved chats.
"
,
failedLoadSavedChats
:
"
Could not load saved chats.
"
,
failedLoadSelectedChat
:
"
Could not load the selected chat.
"
,
failedLoadSelectedChat
:
"
Could not load the selected chat.
"
,
savingDrawingConverting
:
"
Saving drawing and converting to LaTeX...
"
,
savingDrawingConverting
:
"
Saving drawing and converting to LaTeX...
"
,
uploadingSolutionConverting
:
"
Uploading image and converting to LaTeX...
"
,
uploadFailed
:
"
Uploading the image failed.
"
,
uploadReadFailed
:
"
Could not read the selected image.
"
,
uploadInvalidFileType
:
"
Please select an image file.
"
,
saveFailedStatus
:
"
Saving failed (status: {status}).
"
,
saveFailedStatus
:
"
Saving failed (status: {status}).
"
,
canvasSaveFailed
:
"
Saving the canvas failed.
"
,
canvasSaveFailed
:
"
Saving the canvas failed.
"
,
canvasSaved
:
"
Canvas saved.
"
,
canvasSaved
:
"
Canvas saved.
"
,
...
@@ -91,6 +96,7 @@
...
@@ -91,6 +96,7 @@
pen
:
"
Stift
"
,
pen
:
"
Stift
"
,
clear
:
"
Leeren
"
,
clear
:
"
Leeren
"
,
saveAndConvert
:
"
Speichern + Konvertieren
"
,
saveAndConvert
:
"
Speichern + Konvertieren
"
,
uploadSolution
:
"
Lösungsbild hochladen
"
,
hide
:
"
Verstecken
"
,
hide
:
"
Verstecken
"
,
show
:
"
Anzeigen
"
,
show
:
"
Anzeigen
"
,
newChat
:
"
neuer Chat
"
,
newChat
:
"
neuer Chat
"
,
...
@@ -138,6 +144,11 @@
...
@@ -138,6 +144,11 @@
failedLoadSelectedChat
:
"
Der ausgewählte Chat konnte nicht geladen werden.
"
,
failedLoadSelectedChat
:
"
Der ausgewählte Chat konnte nicht geladen werden.
"
,
savingDrawingConverting
:
savingDrawingConverting
:
"
Zeichnung wird gespeichert und in LaTeX konvertiert...
"
,
"
Zeichnung wird gespeichert und in LaTeX konvertiert...
"
,
uploadingSolutionConverting
:
"
Bild wird hochgeladen und in LaTeX konvertiert...
"
,
uploadFailed
:
"
Das Hochladen des Bildes ist fehlgeschlagen.
"
,
uploadReadFailed
:
"
Das ausgewählte Bild konnte nicht gelesen werden.
"
,
uploadInvalidFileType
:
"
Bitte wähle eine Bilddatei aus.
"
,
saveFailedStatus
:
"
Speichern fehlgeschlagen (Status: {status}). Probiere es nochmal oder lade die Seite neu.
"
,
saveFailedStatus
:
"
Speichern fehlgeschlagen (Status: {status}). Probiere es nochmal oder lade die Seite neu.
"
,
canvasSaveFailed
:
"
Speichern des Canvas fehlgeschlagen. Probiere es nochmal oder lade die Seite neu.
"
,
canvasSaveFailed
:
"
Speichern des Canvas fehlgeschlagen. Probiere es nochmal oder lade die Seite neu.
"
,
canvasSaved
:
"
Canvas gespeichert.
"
,
canvasSaved
:
"
Canvas gespeichert.
"
,
...
...
math-tutor/frontend/src/pages/ChatPage.tsx
View file @
01c1d3c3
...
@@ -737,10 +737,13 @@ export default function ChatPage() {
...
@@ -737,10 +737,13 @@ export default function ChatPage() {
navigate
(
isTaskCoupledOrchestrator
(
next
)
?
"
/select-task
"
:
"
/chat
"
);
navigate
(
isTaskCoupledOrchestrator
(
next
)
?
"
/select-task
"
:
"
/chat
"
);
};
};
const
handleCanvasSave
=
async
(
dataUrl
:
string
)
=>
{
const
handleCanvasSave
=
async
(
dataUrl
:
string
,
statusMessage
=
t
(
"
savingDrawingConverting
"
)
)
=>
{
setCanvasStatus
({
setCanvasStatus
({
kind
:
"
info
"
,
kind
:
"
info
"
,
message
:
t
(
"
savingDrawingConverting
"
)
,
message
:
statusMessage
,
});
});
try
{
try
{
...
@@ -787,6 +790,61 @@ export default function ChatPage() {
...
@@ -787,6 +790,61 @@ export default function ChatPage() {
}
}
};
};
const
fileToPngDataUrl
=
async
(
file
:
File
):
Promise
<
string
>
=>
{
if
(
!
file
.
type
.
startsWith
(
"
image/
"
))
{
throw
new
Error
(
t
(
"
uploadInvalidFileType
"
));
}
const
fileDataUrl
=
await
new
Promise
<
string
>
((
resolve
,
reject
)
=>
{
const
reader
=
new
FileReader
();
reader
.
onload
=
()
=>
{
if
(
typeof
reader
.
result
===
"
string
"
)
{
resolve
(
reader
.
result
);
return
;
}
reject
(
new
Error
(
t
(
"
uploadReadFailed
"
)));
};
reader
.
onerror
=
()
=>
reject
(
new
Error
(
t
(
"
uploadReadFailed
"
)));
reader
.
readAsDataURL
(
file
);
});
if
(
file
.
type
===
"
image/png
"
)
{
return
fileDataUrl
;
}
const
image
=
await
new
Promise
<
HTMLImageElement
>
((
resolve
,
reject
)
=>
{
const
img
=
new
Image
();
img
.
onload
=
()
=>
resolve
(
img
);
img
.
onerror
=
()
=>
reject
(
new
Error
(
t
(
"
uploadReadFailed
"
)));
img
.
src
=
fileDataUrl
;
});
const
canvas
=
document
.
createElement
(
"
canvas
"
);
canvas
.
width
=
image
.
naturalWidth
||
image
.
width
;
canvas
.
height
=
image
.
naturalHeight
||
image
.
height
;
const
context
=
canvas
.
getContext
(
"
2d
"
);
if
(
!
context
)
{
throw
new
Error
(
t
(
"
uploadReadFailed
"
));
}
context
.
drawImage
(
image
,
0
,
0
);
return
canvas
.
toDataURL
(
"
image/png
"
);
};
const
handleCanvasUpload
=
async
(
file
:
File
)
=>
{
if
(
!
file
)
{
return
;
}
try
{
const
dataUrl
=
await
fileToPngDataUrl
(
file
);
await
handleCanvasSave
(
dataUrl
,
t
(
"
uploadingSolutionConverting
"
));
}
catch
(
error
)
{
const
message
=
error
instanceof
Error
?
error
.
message
:
t
(
"
uploadFailed
"
);
setCanvasStatus
({
kind
:
"
error
"
,
message
});
void
error
;
}
};
const
handleCiteDoc
=
(
docId
:
string
)
=>
{
const
handleCiteDoc
=
(
docId
:
string
)
=>
{
setDraft
((
prev
)
=>
(
prev
?
`
${
prev
}
[
${
docId
}
]`
:
`[
${
docId
}
]`
));
setDraft
((
prev
)
=>
(
prev
?
`
${
prev
}
[
${
docId
}
]`
:
`[
${
docId
}
]`
));
};
};
...
@@ -891,6 +949,7 @@ export default function ChatPage() {
...
@@ -891,6 +949,7 @@ export default function ChatPage() {
onSend
=
{
handleSend
}
onSend
=
{
handleSend
}
onHistoryNavigate
=
{
handleHistoryNavigate
}
onHistoryNavigate
=
{
handleHistoryNavigate
}
onToggleCanvas
=
{
handleToggleCanvas
}
onToggleCanvas
=
{
handleToggleCanvas
}
onUploadSolution
=
{
handleCanvasUpload
}
onInspectDoc
=
{
handleInspectDoc
}
onInspectDoc
=
{
handleInspectDoc
}
docIndex
=
{
docIndexes
.
bySourceKey
}
docIndex
=
{
docIndexes
.
bySourceKey
}
docSlugIndex
=
{
docIndexes
.
bySlug
}
docSlugIndex
=
{
docIndexes
.
bySlug
}
...
...
math-tutor/frontend/src/styles/theme.css
View file @
01c1d3c3
...
@@ -614,6 +614,18 @@ body {
...
@@ -614,6 +614,18 @@ body {
margin-left
:
auto
;
margin-left
:
auto
;
}
}
.canvas-upload-input
{
position
:
absolute
;
width
:
1px
;
height
:
1px
;
padding
:
0
;
margin
:
-1px
;
overflow
:
hidden
;
clip
:
rect
(
0
,
0
,
0
,
0
);
white-space
:
nowrap
;
border
:
0
;
}
.canvas-hidden
{
.canvas-hidden
{
padding
:
12px
;
padding
:
12px
;
border-radius
:
12px
;
border-radius
:
12px
;
...
@@ -776,6 +788,10 @@ body {
...
@@ -776,6 +788,10 @@ body {
.canvas-actions-header
{
.canvas-actions-header
{
margin-left
:
0
;
margin-left
:
0
;
}
}
.canvas-actions-header
.btn
{
flex
:
1
1
120px
;
}
}
}
.app-loading
{
.app-loading
{
...
...
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