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
6c805864
Commit
6c805864
authored
Mar 13, 2026
by
Kantz
Browse files
Merge branch 'dev' into 'main'
Dev See merge request kantz/tutor_react!13
parents
5a28f888
358ac39b
Changes
16
Hide whitespace changes
Inline
Side-by-side
math-tutor/backend/app/LLM_services/math_intent_LLM.py
View file @
6c805864
...
...
@@ -43,14 +43,14 @@ ODER
"""
def
solve_with_tools
(
user_text
:
str
)
->
str
:
return
"none"
messages
=
[
{
"role"
:
"system"
,
"content"
:
SYSTEM
},
{
"role"
:
"user"
,
"content"
:
user_text
+
"
\n
Antworte nur mit 'Keine Lösung' falls es in dieser Nachricht KEINE Berechung gibt."
},
]
result
,
tool_outputs
=
llm_client
.
chat
_with_tools
(
result
=
llm_client
.
chat
(
messages
=
messages
,
tools
=
[
sympy_solve
],
use_ollama
=
True
,
)
return
llm_client
.
get_message_content
(
result
)
math-tutor/backend/app/LLM_services/qa_LLM.py
View file @
6c805864
...
...
@@ -4,7 +4,7 @@ QA_SYSTEM_PROMPT ="""
Du bist ein Mathe-Tutor. Antworte auf Deutsch, klar, korrekt und sehr kurz.
Nutze ausschließlich den bereitgestellten Kontext.
Wenn d
ie Frage dort nicht beantwortet wird
, antworte nur mit:
Wenn d
ort nichts zu dem Thema steht
, antworte nur mit:
„Dazu steht nichts im Material.“
verwende die $ für mathematische Formeln, z.B. $a^2 + b^2 = c^2$
...
...
math-tutor/backend/app/api/tasks.py
View file @
6c805864
...
...
@@ -22,6 +22,7 @@ class TaskFile(BaseModel):
title
:
str
intro
:
str
tasks
:
List
[
TaskItem
]
subsections
:
List
[
str
]
=
Field
(
default_factory
=
list
)
class
TasksResponse
(
BaseModel
):
...
...
math-tutor/backend/app/deterministic_services/context_stores/context_store_base.py
View file @
6c805864
...
...
@@ -73,7 +73,7 @@ def save_sheet(sheet: dict[str, Any]) -> None:
chat_id
=
sheet
.
get
(
"chat_id"
,
"unknown"
)
latest_path
=
os
.
path
.
join
(
_LOG_DIR
,
f
"
{
chat_id
}
_latest.json"
)
payload
=
json
.
dumps
(
sheet
,
ensure_ascii
=
Tru
e
,
indent
=
2
)
payload
=
json
.
dumps
(
sheet
,
ensure_ascii
=
Fals
e
,
indent
=
2
)
with
open
(
latest_path
,
"w"
,
encoding
=
"utf-8"
)
as
file_handle
:
file_handle
.
write
(
payload
)
...
...
math-tutor/backend/app/deterministic_services/orchestrators/orchestrator_task.py
View file @
6c805864
...
...
@@ -54,10 +54,10 @@ def _ensure_context_task_fields(state: base.ChatState, query_text: str) -> tuple
return
selected_file_id
,
selected_task_id
def
_retrieve_context_for_task
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
None
:
def
_retrieve_context_for_task
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
int
:
refs
=
task_catalog
.
get_selected_task_subsection_refs
(
state
.
sheet
)
if
not
refs
:
return
return
0
def
_retrieve
()
->
dict
:
sources
=
retrieval_store
.
retrieve_for_subsections
(
...
...
@@ -70,7 +70,7 @@ def _retrieve_context_for_task(state: base.ChatState, query_text: str) -> None:
"source_count"
:
len
(
sources
),
}
base
.
log_timed_call
(
result
=
base
.
log_timed_call
(
state
.
tool_log
,
"retrieve_context_with_task_subsections"
,
{
...
...
@@ -79,13 +79,20 @@ def _retrieve_context_for_task(state: base.ChatState, query_text: str) -> None:
},
_retrieve
,
)
return
int
(
result
.
get
(
"source_count"
,
0
))
# beim ersten Aufruf
def
_on_bootstrap
(
state
:
base
.
ChatState
,
query_text
:
str
)
->
None
:
base
.
bootstrap_retrieval
(
state
.
sheet
,
query_text
,
state
.
tool_log
)
_ensure_context_task_fields
(
state
,
query_text
)
_retrieve_context_for_task
(
state
,
query_text
)
source_count
=
_retrieve_context_for_task
(
state
,
query_text
)
print
(
f
"_retrieve_context_for_task source_count=
{
source_count
}
"
)
if
source_count
>
0
:
_ensure_context_task_fields
(
state
,
query_text
)
task_text
=
context_store
.
context_store_new
.
get_task
(
state
.
sheet
).
strip
()
retrieval_query
=
query_text
if
task_text
:
retrieval_query
=
f
"Aufgabe:
\n
{
task_text
}
\n\n
{
query_text
}
"
base
.
bootstrap_retrieval
(
state
.
sheet
,
retrieval_query
,
state
.
tool_log
)
# Sonstiges bei jedem Aufruf
...
...
math-tutor/backend/app/deterministic_services/session_store.py
View file @
6c805864
...
...
@@ -52,7 +52,7 @@ def archive_chat(
}
os
.
makedirs
(
_LOG_DIR
,
exist_ok
=
True
)
payload
=
json
.
dumps
(
record
,
ensure_ascii
=
Tru
e
)
payload
=
json
.
dumps
(
record
,
ensure_ascii
=
Fals
e
)
with
_LOCK
:
with
open
(
_LOG_PATH
,
"a"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
payload
+
"
\n
"
)
...
...
math-tutor/backend/app/deterministic_services/task_catalog.py
View file @
6c805864
...
...
@@ -259,6 +259,7 @@ def build_task_catalog(task_files: list[dict[str, Any]] | None = None) -> list[d
file_id
=
str
(
task_file
.
get
(
"_file_id"
,
""
))
title
=
str
(
task_file
.
get
(
"title"
,
""
)).
strip
()
intro
=
str
(
task_file
.
get
(
"intro"
,
""
)).
strip
()
subsections
=
task_file
.
get
(
"subsections"
,
[])
tasks
:
list
[
dict
[
str
,
str
]]
=
[]
for
item
in
task_file
.
get
(
"tasks"
,
[]):
if
not
isinstance
(
item
,
dict
):
...
...
@@ -280,6 +281,7 @@ def build_task_catalog(task_files: list[dict[str, Any]] | None = None) -> list[d
"title"
:
title
,
"intro"
:
intro
,
"tasks"
:
tasks
,
"subsections"
:
subsections
,
}
)
return
response
math-tutor/backend/app/deterministic_services/tool_logging.py
View file @
6c805864
...
...
@@ -42,5 +42,5 @@ def write_tool_log(entries: list[dict], created_at: str | None = None, chat_id:
except
(
OSError
,
json
.
JSONDecodeError
):
pass
with
open
(
path
,
"w"
,
encoding
=
"utf-8"
)
as
f
:
json
.
dump
(
payload
,
f
,
ensure_ascii
=
Tru
e
,
indent
=
2
)
json
.
dump
(
payload
,
f
,
ensure_ascii
=
Fals
e
,
indent
=
2
)
return
path
math-tutor/frontend/SuMINT-Logo.png
0 → 100644
View file @
6c805864
166 KB
math-tutor/frontend/src/api/taskApi.ts
View file @
6c805864
...
...
@@ -9,6 +9,7 @@ export type TaskFile = {
title
:
string
;
intro
:
string
;
tasks
:
TaskItem
[];
subsections
?:
string
[];
};
export
type
TasksResponse
=
{
...
...
math-tutor/frontend/src/components/System/BackendHealthGate.tsx
View file @
6c805864
...
...
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState, type PropsWithChildren } fro
import
{
fetchHealth
,
type
HealthResponse
}
from
"
../../api/healthApi
"
;
import
{
t
}
from
"
../../i18n
"
;
import
"
../../styles/theme.css
"
;
import
sumintLogo
from
"
../../../SuMINT-Logo.png
"
;
type
GateState
=
"
checking
"
|
"
healthy
"
|
"
unhealthy
"
;
...
...
@@ -93,7 +94,7 @@ export default function BackendHealthGate({ children }: PropsWithChildren) {
<
div
className
=
"backend-health-shell"
>
<
header
className
=
"app-header backend-health-header"
>
<
div
className
=
"brand"
>
<
span
className
=
"brand-
mark"
>
SUM
</
span
>
<
img
className
=
"brand-
logo"
src
=
{
sumintLogo
}
alt
=
"SuMINT Logo"
/
>
<
div
className
=
"brand-text"
>
<
div
className
=
"brand-title"
>
Math Tutor
</
div
>
<
div
className
=
"brand-subtitle"
>
{
t
(
"
prototypeWorkspace
"
)
}
</
div
>
...
...
math-tutor/frontend/src/i18n.ts
View file @
6c805864
...
...
@@ -87,8 +87,8 @@
pen
:
"
Stift
"
,
clear
:
"
Leeren
"
,
saveAndConvert
:
"
Speichern + Konvertieren
"
,
hide
:
"
v
erstecken
"
,
show
:
"
a
nzeigen
"
,
hide
:
"
V
erstecken
"
,
show
:
"
A
nzeigen
"
,
newChat
:
"
neuer Chat
"
,
loading
:
"
Lade...
"
,
orchestratorMode
:
"
Modus
"
,
...
...
math-tutor/frontend/src/pages/ChatPage.tsx
View file @
6c805864
...
...
@@ -12,6 +12,7 @@ import type { OrchestratorName } from "../api/orchestratorApi";
import
{
selectTask
}
from
"
../api/taskApi
"
;
import
{
t
}
from
"
../i18n
"
;
import
{
createSessionId
,
useTutorSession
}
from
"
../state/tutorSession
"
;
import
sumintLogo
from
"
../../SuMINT-Logo.png
"
;
const
initialMessages
:
ChatMessage
[]
=
[];
...
...
@@ -793,33 +794,33 @@ export default function ChatPage() {
return
(
<
div
className
=
"app-shell"
>
<
header
className
=
"app-header"
>
<
header
className
=
"app-header
app-header-chat
"
>
{
showChatsButton
?
(
<
button
type
=
"button"
className
=
"btn sidebar-toggle"
onClick
=
{
handleOpenSidebar
}
>
{
t
(
"
chats
"
)
}
</
button
>
)
:
null
}
<
div
className
=
"brand"
>
<
span
className
=
"brand-
mark"
>
SUM
</
span
>
<
div
className
=
"brand-text"
>
<
div
className
=
"brand-title"
>
Math Tutor
</
div
>
<
div
className
=
"brand-subtitle"
>
{
t
(
"
prototypeWorkspace
"
)
}
</
div
>
)
:
null
}
<
div
className
=
"brand"
>
<
img
className
=
"brand-
logo"
src
=
{
sumintLogo
}
alt
=
"SuMINT Logo"
/
>
<
div
className
=
"brand-text"
>
<
div
className
=
"brand-title"
>
Math Tutor
</
div
>
<
div
className
=
"brand-subtitle"
>
{
t
(
"
prototypeWorkspace
"
)
}
</
div
>
</
div
>
</
div
>
<
div
className
=
"header-meta"
>
<
OrchestratorSelect
value
=
{
selectedOrchestrator
}
options
=
{
availableOrchestrators
}
onChange
=
{
handleSwitchOrchestrator
}
disabled
=
{
!
isOrchestratorSelectable
}
/>
</
div
>
</
header
>
{
orchestratorError
?
<
div
className
=
"chat-archive-error"
>
{
orchestratorError
}
</
div
>
:
null
}
{
deepLinkError
?
<
div
className
=
"chat-archive-error"
>
{
deepLinkError
}
</
div
>
:
null
}
<
main
className
=
"app-main"
>
<
section
className
=
"chat-column"
>
<
div
className
=
"chat-mode-row"
>
<
OrchestratorSelect
value
=
{
selectedOrchestrator
}
options
=
{
availableOrchestrators
}
onChange
=
{
handleSwitchOrchestrator
}
disabled
=
{
!
isOrchestratorSelectable
}
/>
</
div
>
<
ChatWindow
messages
=
{
messages
}
draft
=
{
draft
}
...
...
math-tutor/frontend/src/pages/TaskSelectionPage.tsx
View file @
6c805864
...
...
@@ -6,6 +6,7 @@ import { selectTask } from "../api/taskApi";
import
{
t
}
from
"
../i18n
"
;
import
{
useTutorSession
}
from
"
../state/tutorSession
"
;
import
"
../styles/theme.css
"
;
import
sumintLogo
from
"
../../SuMINT-Logo.png
"
;
export
default
function
TaskSelectionPage
()
{
const
navigate
=
useNavigate
();
...
...
@@ -41,6 +42,16 @@ export default function TaskSelectionPage() {
unlockTask
();
},
[
isTaskModeEnabled
,
isTasksInitialized
,
navigate
,
unlockTask
]);
useEffect
(()
=>
{
if
(
!
taskFileOptions
.
length
)
{
return
;
}
if
(
selectedTaskRef
&&
taskFileOptions
.
some
((
option
)
=>
option
.
value
===
selectedTaskRef
.
fileId
))
{
return
;
}
setTaskFile
(
taskFileOptions
[
0
].
value
);
},
[
isTasksInitialized
,
selectedTaskRef
,
setTaskFile
,
taskFileOptions
]);
useEffect
(()
=>
{
if
(
!
selectedTask
?.
fullText
||
!
taskDisplayRef
.
current
)
{
return
;
...
...
@@ -86,88 +97,90 @@ export default function TaskSelectionPage() {
<
div
className
=
"app-shell"
>
<
header
className
=
"app-header"
>
<
div
className
=
"brand"
>
<
span
className
=
"brand-
mark"
>
SUM
</
span
>
<
img
className
=
"brand-
logo"
src
=
{
sumintLogo
}
alt
=
"SuMINT Logo"
/
>
<
div
className
=
"brand-text"
>
<
div
className
=
"brand-title"
>
Math Tutor
</
div
>
<
div
className
=
"brand-subtitle"
>
{
t
(
"
taskSelectionSubtitle
"
)
}
</
div
>
</
div
>
</
div
>
<
div
className
=
"header-meta"
>
<
OrchestratorSelect
value
=
{
selectedOrchestrator
}
options
=
{
availableOrchestrators
}
onChange
=
{
handleSwitchOrchestrator
}
disabled
=
{
!
isOrchestratorSelectable
}
/>
</
div
>
</
header
>
{
orchestratorError
?
<
div
className
=
"chat-archive-error"
>
{
orchestratorError
}
</
div
>
:
null
}
<
main
className
=
"task-select-main"
>
<
section
className
=
"task-select-card"
>
<
div
className
=
"task-select-header"
>
<
h2
className
=
"task-select-title"
>
{
t
(
"
taskSelectionTitle
"
)
}
</
h2
>
<
div
className
=
"task-select-wrap"
>
<
div
className
=
"task-mode-row"
>
<
OrchestratorSelect
value
=
{
selectedOrchestrator
}
options
=
{
availableOrchestrators
}
onChange
=
{
handleSwitchOrchestrator
}
disabled
=
{
!
isOrchestratorSelectable
}
/>
</
div
>
<
section
className
=
"task-select-card"
>
<
div
className
=
"task-select-header"
>
<
h2
className
=
"task-select-title"
>
{
t
(
"
taskSelectionTitle
"
)
}
</
h2
>
</
div
>
<
div
className
=
"task-select-controls"
>
<
label
className
=
"task-select-label"
htmlFor
=
"task-file-select"
>
{
t
(
"
taskFile
"
)
}
</
label
>
<
select
id
=
"task-file-select"
className
=
"task-select"
value
=
{
selectedTaskRef
?.
fileId
||
""
}
onChange
=
{
(
event
)
=>
setTaskFile
(
event
.
target
.
value
)
}
disabled
=
{
!
taskFileOptions
.
length
}
>
{
taskFileOptions
.
length
?
(
taskFileOptions
.
map
((
option
)
=>
(
<
option
key
=
{
option
.
value
}
value
=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
)
:
(
<
option
value
=
""
>
{
t
(
"
noTasksAvailable
"
)
}
</
option
>
)
}
</
select
>
<
div
className
=
"task-select-controls"
>
<
label
className
=
"task-select-label"
htmlFor
=
"task-file-select"
>
{
t
(
"
taskFile
"
)
}
</
label
>
<
select
id
=
"task-file-select"
className
=
"task-select"
value
=
{
selectedTaskRef
?.
fileId
||
""
}
onChange
=
{
(
event
)
=>
setTaskFile
(
event
.
target
.
value
)
}
disabled
=
{
!
taskFileOptions
.
length
}
>
{
taskFileOptions
.
length
?
(
taskFileOptions
.
map
((
option
)
=>
(
<
option
key
=
{
option
.
value
}
value
=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
)
:
(
<
option
value
=
""
>
{
t
(
"
noTasksAvailable
"
)
}
</
option
>
)
}
</
select
>
<
label
className
=
"task-select-label"
htmlFor
=
"task-id-select"
>
{
t
(
"
taskId
"
)
}
</
label
>
<
select
id
=
"task-id-select"
className
=
"task-select"
value
=
{
selectedTaskRef
?.
taskId
||
""
}
onChange
=
{
(
event
)
=>
setTaskId
(
event
.
target
.
value
)
}
disabled
=
{
!
taskOptions
.
length
}
>
{
taskOptions
.
length
?
(
taskOptions
.
map
((
option
)
=>
(
<
option
key
=
{
option
.
value
}
value
=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
)
:
(
<
option
value
=
""
>
{
t
(
"
noTasksAvailable
"
)
}
</
option
>
)
}
</
select
>
</
div
>
<
label
className
=
"task-select-label"
htmlFor
=
"task-id-select"
>
{
t
(
"
taskId
"
)
}
</
label
>
<
select
id
=
"task-id-select"
className
=
"task-select"
value
=
{
selectedTaskRef
?.
taskId
||
""
}
onChange
=
{
(
event
)
=>
setTaskId
(
event
.
target
.
value
)
}
disabled
=
{
!
taskOptions
.
length
}
>
{
taskOptions
.
length
?
(
taskOptions
.
map
((
option
)
=>
(
<
option
key
=
{
option
.
value
}
value
=
{
option
.
value
}
>
{
option
.
label
}
</
option
>
))
)
:
(
<
option
value
=
""
>
{
t
(
"
noTasksAvailable
"
)
}
</
option
>
)
}
</
select
>
</
div
>
<
div
className
=
"task-panel-content"
ref
=
{
taskDisplayRef
}
>
{
selectedTask
?.
fullText
||
t
(
"
noTaskSelected
"
)
}
</
div
>
<
div
className
=
"task-panel-content"
ref
=
{
taskDisplayRef
}
>
{
selectedTask
?.
fullText
||
t
(
"
noTaskSelected
"
)
}
</
div
>
{
tasksError
?
<
div
className
=
"task-panel-error"
>
{
tasksError
}
</
div
>
:
null
}
{
tasksError
?
<
div
className
=
"task-panel-error"
>
{
tasksError
}
</
div
>
:
null
}
<
button
type
=
"button"
className
=
"btn primary task-solve-btn"
onClick
=
{
handleSolveWithTutor
}
disabled
=
{
!
selectedTaskRef
}
>
{
t
(
"
solveWithTutor
"
)
}
</
button
>
</
section
>
<
button
type
=
"button"
className
=
"btn primary task-solve-btn"
onClick
=
{
handleSolveWithTutor
}
disabled
=
{
!
selectedTaskRef
}
>
{
t
(
"
solveWithTutor
"
)
}
</
button
>
</
section
>
</
div
>
</
main
>
</
div
>
);
...
...
math-tutor/frontend/src/state/tutorSession.tsx
View file @
6c805864
...
...
@@ -65,6 +65,9 @@ export const createSessionId = () =>
export
const
getDefaultTaskId
=
(
tasks
:
Array
<
{
task_id
:
string
}
>
):
string
=>
tasks
.
find
((
task
)
=>
task
.
task_id
===
"
01
"
)?.
task_id
||
tasks
[
0
]?.
task_id
||
""
;
const
isSelectableTaskFile
=
(
file
:
TaskFile
):
boolean
=>
Array
.
isArray
(
file
.
subsections
)
&&
file
.
subsections
.
length
>
0
;
export
function
TutorSessionProvider
({
children
}:
PropsWithChildren
)
{
const
[
chatSessionId
,
setChatSessionId
]
=
useState
<
string
>
(()
=>
createSessionId
());
const
[
selectedOrchestrator
,
setSelectedOrchestratorState
]
=
...
...
@@ -107,13 +110,18 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
[
selectedTaskRef
?.
fileId
,
taskFiles
]
);
const
selectableTaskFiles
=
useMemo
(
()
=>
taskFiles
.
filter
((
file
)
=>
isSelectableTaskFile
(
file
)),
[
taskFiles
]
);
const
taskFileOptions
=
useMemo
(
()
=>
t
askFiles
.
map
((
file
)
=>
({
selectableT
askFiles
.
map
((
file
)
=>
({
value
:
file
.
file_id
,
label
:
file
.
title
||
file
.
file_id
,
})),
[
t
askFiles
]
[
selectableT
askFiles
]
);
const
taskOptions
=
useMemo
(
...
...
@@ -149,17 +157,18 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
try
{
const
payload
=
await
fetchTasks
();
const
files
=
payload
.
task_files
||
[];
const
selectableFiles
=
files
.
filter
((
file
)
=>
isSelectableTaskFile
(
file
));
setTaskFiles
(
files
);
setSelectedTaskRef
((
prev
)
=>
{
if
(
prev
)
{
const
file
=
f
iles
.
find
((
item
)
=>
item
.
file_id
===
prev
.
fileId
);
const
file
=
selectableF
iles
.
find
((
item
)
=>
item
.
file_id
===
prev
.
fileId
);
if
(
file
&&
file
.
tasks
.
some
((
task
)
=>
task
.
task_id
===
prev
.
taskId
))
{
return
prev
;
}
}
const
firstFile
=
f
iles
[
0
];
const
firstFile
=
selectableF
iles
[
0
];
if
(
!
firstFile
)
{
return
null
;
}
...
...
@@ -188,7 +197,7 @@ export function TutorSessionProvider({ children }: PropsWithChildren) {
if
(
!
fileId
)
{
return
;
}
const
file
=
taskFiles
.
find
((
item
)
=>
item
.
file_id
===
fileId
);
const
file
=
taskFiles
.
find
((
item
)
=>
item
.
file_id
===
fileId
&&
isSelectableTaskFile
(
item
)
);
if
(
!
file
)
{
return
;
}
...
...
math-tutor/frontend/src/styles/theme.css
View file @
6c805864
...
...
@@ -29,22 +29,22 @@ body {
margin-bottom
:
24px
;
}
.app-header-chat
{
justify-content
:
flex-start
;
}
.brand
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
}
.brand-mark
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
width
:
44px
;
height
:
44px
;
border-radius
:
12px
;
background
:
#1d1b16
;
color
:
#fef9f0
;
font-weight
:
700
;
.brand-logo
{
display
:
block
;
width
:
auto
;
height
:
100px
;
max-width
:
300px
;
object-fit
:
contain
;
}
.brand-title
{
...
...
@@ -190,6 +190,14 @@ body {
gap
:
16px
;
min-height
:
0
;
max-height
:
85vh
;
position
:
relative
;
}
.chat-mode-row
{
position
:
absolute
;
top
:
-52px
;
right
:
0
;
z-index
:
2
;
}
.chat-window
{
...
...
@@ -700,6 +708,10 @@ body {
align-items
:
flex-start
;
}
.app-header-chat
{
justify-content
:
flex-start
;
}
.header-meta
{
width
:
100%
;
}
...
...
@@ -715,6 +727,10 @@ body {
min-height
:
360px
;
}
.chat-mode-row
{
position
:
static
;
}
.composer-row
{
width
:
100%
;
}
...
...
@@ -803,8 +819,20 @@ body {
place-items
:
center
;
}
.task-select-
card
{
.task-select-
wrap
{
width
:
min
(
820px
,
100%
);
position
:
relative
;
}
.task-mode-row
{
position
:
absolute
;
top
:
-52px
;
right
:
0
;
z-index
:
2
;
}
.task-select-card
{
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
...
...
@@ -874,6 +902,11 @@ body {
grid-template-columns
:
1
fr
;
}
.task-mode-row
{
position
:
static
;
margin-bottom
:
12px
;
}
.task-solve-btn
{
width
:
100%
;
}
...
...
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