This commit is contained in:
seb
2026-10-05 01:01:03 +02:00
parent 81b59fd309
commit b766769634
59 changed files with 903 additions and 280 deletions

View File

@@ -2,9 +2,14 @@ import React, { useState } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import TextInput from '../TextInput';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import SendRoundedIcon from '@mui/icons-material/SendRounded';
import {
ASK_PROMPT_BG_PAIR,
ASK_PROMPT_BORDER_PAIR,
byMode,
} from '../../colors';
export default function AskUserPrompt({ askData, onAnswer, disabled = false }) {
const { toolCallId, question, options = [] } = askData;
@@ -30,8 +35,8 @@ export default function AskUserPrompt({ askData, onAnswer, disabled = false }) {
my: 1,
p: 2,
borderRadius: 3,
bgcolor: theme.palette.mode === 'dark' ? 'rgba(59, 91, 219, 0.12)' : '#eef2ff',
border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(59, 91, 219, 0.3)' : '#c7d2fe'}`,
bgcolor: byMode(theme, ASK_PROMPT_BG_PAIR),
border: `1px solid ${byMode(theme, ASK_PROMPT_BORDER_PAIR)}`,
display: 'flex',
flexDirection: 'column',
gap: 1.5,
@@ -97,28 +102,13 @@ export default function AskUserPrompt({ askData, onAnswer, disabled = false }) {
onSubmit={handleCustomSubmit}
sx={{ display: 'flex', gap: 1, mt: 0.5, alignItems: 'center' }}
>
<TextField
size="small"
<TextInput
fullWidth
autoFocus
placeholder="Antwort eingeben…"
value={customAnswer}
onChange={(e) => setCustomAnswer(e.target.value)}
disabled={disabled}
slotProps={{
htmlInput: {
spellCheck: false,
autoCorrect: 'off',
autoCapitalize: 'off',
},
}}
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2,
bgcolor: 'background.paper',
fontSize: '0.82rem',
},
}}
/>
<Button
type="submit"

View File

@@ -4,7 +4,7 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import TextInput from '../TextInput';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import FormControl from '@mui/material/FormControl';
@@ -20,6 +20,10 @@ import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
import userSettingsAutomat from '../../automats/userSettingsAutomat';
import ChatFlowList from './ChatFlowList';
import AskUserPrompt from './AskUserPrompt';
import {
BLUE_GLOW,
SHADOW_DRAWER,
} from '../../colors';
const DEFAULT_DRAWER_WIDTH = 460;
const MIN_DRAWER_WIDTH = 320;
@@ -489,11 +493,10 @@ export default class ChatDrawer extends PureComponent {
)}
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1 }}>
<TextField
<TextInput
inputRef={this.inputRef}
multiline
maxRows={4}
size="small"
fullWidth
value={inputText}
onChange={(e) => this.setState({ inputText: e.target.value })}
@@ -502,22 +505,8 @@ export default class ChatDrawer extends PureComponent {
placeholder={
pendingAsk
? 'Bitte Frage oben beantworten…'
: 'Nachricht eingeben… (Enter)'
: 'Nachricht eingeben…'
}
slotProps={{
htmlInput: {
spellCheck: false,
autoCorrect: 'off',
autoCapitalize: 'off',
},
}}
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2.5,
bgcolor: 'background.default',
fontSize: '0.875rem',
},
}}
/>
<IconButton
@@ -576,7 +565,7 @@ export default class ChatDrawer extends PureComponent {
maxWidth: '100vw',
display: 'flex',
flexDirection: 'column',
boxShadow: '-8px 0 32px rgba(0, 0, 0, 0.12)',
boxShadow: SHADOW_DRAWER,
position: 'fixed',
overflow: 'visible',
transition: isResizing ? 'none !important' : undefined,
@@ -592,7 +581,7 @@ export default class ChatDrawer extends PureComponent {
maxWidth: '100vw',
display: 'flex',
flexDirection: 'column',
boxShadow: '-8px 0 32px rgba(0, 0, 0, 0.12)',
boxShadow: SHADOW_DRAWER,
position: 'fixed',
overflow: 'visible',
transition: isResizing ? 'none !important' : undefined,
@@ -625,7 +614,7 @@ export default class ChatDrawer extends PureComponent {
height: 56,
width: 4,
opacity: 1,
boxShadow: '0 0 10px rgba(59, 91, 219, 0.6)',
boxShadow: `0 0 10px ${BLUE_GLOW}`,
},
}}
>
@@ -638,7 +627,7 @@ export default class ChatDrawer extends PureComponent {
bgcolor: isResizing ? 'primary.main' : 'divider',
opacity: isResizing ? 1 : 0.6,
transition: 'background-color 0.15s, height 0.15s, width 0.15s, opacity 0.15s, box-shadow 0.15s',
boxShadow: isResizing ? '0 0 10px rgba(59, 91, 219, 0.6)' : 'none',
boxShadow: isResizing ? `0 0 10px ${BLUE_GLOW}` : 'none',
}}
/>
</Box>

View File

@@ -10,6 +10,15 @@ import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded';
import chatAutomat, { selectPendingAskUser, selectHasPendingAsk } from '../../automats/chatAutomat';
import {
CHAT_BUSY,
CHAT_FAB_BUSY,
CHAT_FAB_PENDING, CHAT_FAB_SHADOW_BUSY,
CHAT_FAB_SHADOW_IDLE,
CHAT_FAB_SHADOW_PENDING, RED,
RED_GLOW,
WHITE,
} from '../../colors';
export default class ChatFab extends PureComponent {
constructor(props) {
@@ -86,7 +95,7 @@ export default class ChatFab extends PureComponent {
right: -8,
bottom: -8,
borderRadius: '50%',
bgcolor: '#f59e0b',
bgcolor: CHAT_BUSY,
opacity: 0.5,
animation: 'fabAskPulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'@keyframes fabAskPulse': {
@@ -129,7 +138,7 @@ export default class ChatFab extends PureComponent {
position: 'absolute',
top: -6,
left: -6,
color: '#f59e0b',
color: CHAT_BUSY,
pointerEvents: 'none',
}}
/>
@@ -146,14 +155,14 @@ export default class ChatFab extends PureComponent {
sx={{
'& .MuiBadge-badge': hasPendingAsk
? {
bgcolor: '#ef4444',
color: '#ffffff',
bgcolor: RED,
color: WHITE,
fontWeight: 900,
fontSize: '0.85rem',
minWidth: 22,
height: 22,
borderRadius: '50%',
boxShadow: '0 2px 10px rgba(239, 68, 68, 0.6)',
boxShadow: `0 2px 10px ${RED_GLOW}`,
animation: 'badgePop 1.5s ease infinite',
'@keyframes badgePop': {
'0%, 100%': { transform: 'scale(1)' },
@@ -171,10 +180,10 @@ export default class ChatFab extends PureComponent {
width: 54,
height: 54,
boxShadow: hasPendingAsk
? '0 8px 24px rgba(245, 158, 11, 0.45)'
? CHAT_FAB_SHADOW_PENDING
: isBusy
? '0 8px 24px rgba(245, 158, 11, 0.35)'
: '0 8px 24px rgba(59, 91, 219, 0.35)',
? CHAT_FAB_SHADOW_BUSY
: CHAT_FAB_SHADOW_IDLE,
transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s',
'&:hover': {
transform: 'scale(1.06)',
@@ -183,9 +192,9 @@ export default class ChatFab extends PureComponent {
transform: 'scale(0.96)',
},
background: hasPendingAsk
? 'linear-gradient(135deg, #f59e0b 0%, #ea580c 100%)'
? CHAT_FAB_PENDING
: isBusy
? 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)'
? CHAT_FAB_BUSY
: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 100%)`,
})}
>

View File

@@ -2,6 +2,13 @@ import React, { PureComponent, createRef } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import HealingMarkdown from './HealingMarkdown';
import {
SHADOW_BUBBLE_ASSISTANT,
SHADOW_BUBBLE_USER,
SURFACE_RAISED,
SURFACE_SUBTLE_PAIR,
byMode,
} from '../../colors';
function formatMessageTime(isoString) {
if (!isoString) return '';
@@ -109,7 +116,7 @@ function MessageRow({ msg, showTime = true }) {
<Box sx={{ display: 'flex', justifyContent: 'flex-start', py: 0.35, px: 2 }}>
<Box
sx={(theme) => ({
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)',
bgcolor: byMode(theme, SURFACE_SUBTLE_PAIR),
color: 'text.secondary',
px: 1.25,
py: 0.35,
@@ -193,7 +200,7 @@ function MessageRow({ msg, showTime = true }) {
<Box sx={{ display: 'flex', justifyContent: 'flex-start', py: 0.35, px: 2 }}>
<Box
sx={(theme) => ({
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)',
bgcolor: byMode(theme, SURFACE_SUBTLE_PAIR),
color: 'text.secondary',
px: 1.25,
py: 0.35,
@@ -272,13 +279,13 @@ function MessageRow({ msg, showTime = true }) {
? theme.palette.primary.main
: theme.palette.mode === 'dark'
? 'background.paper'
: '#f4f6f8',
: SURFACE_RAISED,
color: isUser
? theme.palette.primary.contrastText
: theme.palette.text.primary,
boxShadow: isUser
? '0 2px 8px rgba(59, 91, 219, 0.25)'
: '0 1px 4px rgba(0, 0, 0, 0.05)',
? SHADOW_BUBBLE_USER
: SHADOW_BUBBLE_ASSISTANT,
border: isUser ? 'none' : `1px solid ${theme.palette.divider}`,
wordBreak: 'break-word',
whiteSpace: isUser ? 'pre-wrap' : 'normal',

View File

@@ -1,5 +1,12 @@
import React, { memo, useState } from 'react';
import Box from '@mui/material/Box';
import {
CODE_BLOCK_PAIR,
CODE_INLINE_PAIR,
SURFACE_SUBTLE_PAIR,
TABLE_HEADER_PAIR,
byMode,
} from '../../colors';
/**
* Healing markdown renderer.
@@ -336,7 +343,7 @@ const rootSx = (theme) => ({
fontSize: '0.82em',
px: 0.5,
borderRadius: 0.75,
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)',
bbox: byMode(theme, CODE_INLINE_PAIR),
},
'& pre': {
m: 0,
@@ -344,7 +351,7 @@ const rootSx = (theme) => ({
p: 1.25,
borderRadius: 1.5,
overflowX: 'auto',
bgcolor: theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.35)' : 'rgba(0,0,0,0.06)',
bgcolor: byMode(theme, CODE_BLOCK_PAIR),
},
'& pre code': { p: 0, bgcolor: 'transparent', whiteSpace: 'pre' },
'& hr': { border: 0, borderTop: `1px solid ${theme.palette.divider}`, my: 1.5 },
@@ -357,7 +364,7 @@ const rootSx = (theme) => ({
objectFit: 'contain',
borderRadius: 1.5,
border: `1px solid ${theme.palette.divider}`,
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)',
bbox: byMode(theme, SURFACE_SUBTLE_PAIR),
},
'& .md-image-fallback': {
display: 'inline-block',
@@ -388,7 +395,7 @@ const rootSx = (theme) => ({
'&:last-child': { borderRight: 'none' },
},
'& th': {
bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.04)',
bbox: byMode(theme, TABLE_HEADER_PAIR),
fontWeight: 600,
},
'& tr:last-child td': {

View File

@@ -4,7 +4,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import TextInput from '../TextInput';
import MenuItem from '@mui/material/MenuItem';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
@@ -12,6 +12,9 @@ import CircularProgress from '@mui/material/CircularProgress';
import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
import { PRESET_MODELS } from '../../automats/chatAutomat';
import {
SHADOW_DIALOG,
} from '../../colors';
export default class NewConversationDialog extends PureComponent {
constructor(props) {
@@ -73,7 +76,7 @@ export default class NewConversationDialog extends PureComponent {
sx: {
borderRadius: 3,
p: 1,
boxShadow: '0 20px 40px rgba(0,0,0,0.2)',
boxShadow: SHADOW_DIALOG,
},
}}
>
@@ -103,23 +106,21 @@ export default class NewConversationDialog extends PureComponent {
Wähle das Sprachmodell für die neue Konversation aus.
</Typography>
<TextField
<TextInput
label="Provider"
value={provider}
onChange={(e) => this.setState({ provider: e.target.value })}
fullWidth
size="small"
disabled={loading}
helperText="Standard: openrouter"
/>
<TextField
<TextInput
select
label="Modell Vorlage"
value={isCustom ? '__custom__' : model}
onChange={this.handleModelChange}
fullWidth
size="small"
disabled={loading}
>
{PRESET_MODELS.map((item) => (
@@ -128,16 +129,15 @@ export default class NewConversationDialog extends PureComponent {
</MenuItem>
))}
<MenuItem value="__custom__">Anderes Modell eingeben…</MenuItem>
</TextField>
</TextInput>
{isCustom && (
<TextField
<TextInput
label="Modell-Bezeichnung"
placeholder="z. B. anthropic/claude-3.5-haiku"
value={customModel}
onChange={(e) => this.setState({ customModel: e.target.value })}
fullWidth
size="small"
autoFocus
disabled={loading}
/>