u
This commit is contained in:
@@ -4,6 +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 InputBase from '@mui/material/InputBase';
|
||||
import TextInput from '../TextInput';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
@@ -18,8 +19,15 @@ import DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';
|
||||
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import AttachFileRoundedIcon from '@mui/icons-material/AttachFileRounded';
|
||||
import TuneRoundedIcon from '@mui/icons-material/TuneRounded';
|
||||
import ElderlyRoundedIcon from '@mui/icons-material/ElderlyRounded';
|
||||
import DirectionsWalkRoundedIcon from '@mui/icons-material/DirectionsWalkRounded';
|
||||
import DirectionsRunRoundedIcon from '@mui/icons-material/DirectionsRunRounded';
|
||||
import PsychologyRoundedIcon from '@mui/icons-material/PsychologyRounded';
|
||||
|
||||
import { setting as settingApi } from '../../api';
|
||||
import chatAutomat, { selectPendingAskUser } from '../../automats/chatAutomat';
|
||||
import userSettingsAutomat from '../../automats/userSettingsAutomat';
|
||||
import ChatFlowList from './ChatFlowList';
|
||||
@@ -62,6 +70,10 @@ export default class ChatDrawer extends PureComponent {
|
||||
attachmentError: null,
|
||||
drawerWidth: settings?.chatDrawerWidth || DEFAULT_DRAWER_WIDTH,
|
||||
isResizing: false,
|
||||
showSettingsBar: false,
|
||||
modelslugflag: 'floor',
|
||||
modelreasoning: 'medium',
|
||||
modelslug: 'openai/gpt-6-luna',
|
||||
};
|
||||
this.inputRef = React.createRef();
|
||||
this.fileInputRef = React.createRef();
|
||||
@@ -79,6 +91,7 @@ export default class ChatDrawer extends PureComponent {
|
||||
if (this.state.isOpen) {
|
||||
this.focusInput(150);
|
||||
}
|
||||
void this.loadModelSettings();
|
||||
this.unsub = chatAutomat.subscribe(this, (state) => {
|
||||
const wasOpen = this.state.isOpen;
|
||||
const willOpen = state.isOpen;
|
||||
@@ -338,6 +351,71 @@ export default class ChatDrawer extends PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
loadModelSettings = async () => {
|
||||
try {
|
||||
const data = await settingApi.list();
|
||||
const settings = data?.settings || [];
|
||||
const flagSetting = settings.find((s) => s.key === 'modelslugflag');
|
||||
const reasoningSetting = settings.find((s) => s.key === 'modelreasoning');
|
||||
const slugSetting = settings.find((s) => s.key === 'modelslug');
|
||||
this.setState({
|
||||
modelslugflag: flagSetting?.value ?? 'floor',
|
||||
modelreasoning: reasoningSetting?.value ?? 'medium',
|
||||
modelslug: slugSetting?.value ?? 'openai/gpt-6-luna',
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[ChatDrawer] Failed to load model settings:', err);
|
||||
}
|
||||
};
|
||||
|
||||
handleToggleSettingsBar = () => {
|
||||
this.setState((prev) => ({ showSettingsBar: !prev.showSettingsBar }));
|
||||
};
|
||||
|
||||
handleSetFlag = async (nextFlag) => {
|
||||
if (this.state.modelslugflag === nextFlag) return;
|
||||
const current = this.state.modelslugflag;
|
||||
this.setState({ modelslugflag: nextFlag });
|
||||
try {
|
||||
await settingApi.upsert('modelslugflag', { value: nextFlag });
|
||||
} catch (err) {
|
||||
console.error('[ChatDrawer] Failed to save modelslugflag:', err);
|
||||
this.setState({ modelslugflag: current });
|
||||
}
|
||||
};
|
||||
|
||||
handleSetReasoning = async (nextReasoning) => {
|
||||
if (this.state.modelreasoning === nextReasoning) return;
|
||||
const current = this.state.modelreasoning;
|
||||
this.setState({ modelreasoning: nextReasoning });
|
||||
try {
|
||||
await settingApi.upsert('modelreasoning', { value: nextReasoning });
|
||||
} catch (err) {
|
||||
console.error('[ChatDrawer] Failed to save modelreasoning:', err);
|
||||
this.setState({ modelreasoning: current });
|
||||
}
|
||||
};
|
||||
|
||||
handleModelslugChange = (e) => {
|
||||
this.setState({ modelslug: e.target.value });
|
||||
};
|
||||
|
||||
handleModelslugBlur = async () => {
|
||||
const { modelslug } = this.state;
|
||||
const trimmed = (modelslug || '').trim();
|
||||
try {
|
||||
await settingApi.upsert('modelslug', { value: trimmed });
|
||||
} catch (err) {
|
||||
console.error('[ChatDrawer] Failed to save modelslug:', err);
|
||||
}
|
||||
};
|
||||
|
||||
handleModelslugKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.target.blur();
|
||||
}
|
||||
};
|
||||
|
||||
getPendingAskUser = () => selectPendingAskUser(this.state);
|
||||
|
||||
renderHeader() {
|
||||
@@ -345,9 +423,12 @@ export default class ChatDrawer extends PureComponent {
|
||||
conversations,
|
||||
selectedConversationId,
|
||||
loadingConversations,
|
||||
showSettingsBar,
|
||||
modelslugflag,
|
||||
modelreasoning,
|
||||
modelslug,
|
||||
} = this.state;
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -360,15 +441,261 @@ export default class ChatDrawer extends PureComponent {
|
||||
gap: 1.5,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<AutoAwesomeRoundedIcon sx={{ fontSize: 22, color: 'primary.main' }} />
|
||||
<Typography variant="subtitle1" fontWeight={700}>
|
||||
KI-Assistent
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', minHeight: 34 }}>
|
||||
{showSettingsBar ? (
|
||||
/* Settings Bar Mode: Title goes away and reveals toggles */
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flex: 1, minWidth: 0, overflow: 'hidden' }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={this.handleToggleSettingsBar}
|
||||
aria-label="Einstellungen schließen"
|
||||
sx={{
|
||||
p: 0.5,
|
||||
color: 'primary.main',
|
||||
bgcolor: 'action.selected',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<TuneRoundedIcon sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
|
||||
<IconButton size="small" onClick={this.handleClose} aria-label="Chat schließen">
|
||||
{/* Group 1: Speed (floor, walk/standard "", run/nitro) */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.3,
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)'),
|
||||
p: 0.3,
|
||||
borderRadius: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetFlag('floor')}
|
||||
aria-label="Floor"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelslugflag === 'floor' ? 'primary.main' : 'transparent',
|
||||
color: modelslugflag === 'floor' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelslugflag === 'floor' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ElderlyRoundedIcon sx={{ fontSize: 17 }} />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetFlag('')}
|
||||
aria-label="Normal"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelslugflag === '' ? 'primary.main' : 'transparent',
|
||||
color: modelslugflag === '' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelslugflag === '' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DirectionsWalkRoundedIcon sx={{ fontSize: 17 }} />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetFlag('nitro')}
|
||||
aria-label="Nitro"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelslugflag === 'nitro' ? 'primary.main' : 'transparent',
|
||||
color: modelslugflag === 'nitro' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelslugflag === 'nitro' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DirectionsRunRoundedIcon sx={{ fontSize: 17 }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.3, my: 0.5 }} />
|
||||
|
||||
{/* Group 2: Reasoning (none, low, medium, max) */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.3,
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)'),
|
||||
p: 0.3,
|
||||
borderRadius: 2,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{/* none toggle (crossed out brain) */}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetReasoning('none')}
|
||||
aria-label="Reasoning None"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelreasoning === 'none' ? 'primary.main' : 'transparent',
|
||||
color: modelreasoning === 'none' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelreasoning === 'none' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<PsychologyRoundedIcon sx={{ fontSize: 16 }} />
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: '125%',
|
||||
height: '1.5px',
|
||||
bgcolor: modelreasoning === 'none' ? 'primary.contrastText' : 'error.main',
|
||||
transform: 'rotate(-45deg)',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</IconButton>
|
||||
|
||||
{/* low toggle (smallest brain) */}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetReasoning('low')}
|
||||
aria-label="Reasoning Low"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelreasoning === 'low' ? 'primary.main' : 'transparent',
|
||||
color: modelreasoning === 'low' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelreasoning === 'low' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PsychologyRoundedIcon sx={{ fontSize: 13 }} />
|
||||
</IconButton>
|
||||
|
||||
{/* medium toggle (medium brain) */}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetReasoning('medium')}
|
||||
aria-label="Reasoning Medium"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelreasoning === 'medium' ? 'primary.main' : 'transparent',
|
||||
color: modelreasoning === 'medium' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelreasoning === 'medium' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PsychologyRoundedIcon sx={{ fontSize: 17 }} />
|
||||
</IconButton>
|
||||
|
||||
{/* max toggle (big brain) */}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => this.handleSetReasoning('high')}
|
||||
aria-label="Reasoning Max"
|
||||
sx={{
|
||||
p: 0.3,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 1.5,
|
||||
bgcolor: modelreasoning === 'high' ? 'primary.main' : 'transparent',
|
||||
color: modelreasoning === 'high' ? 'primary.contrastText' : 'text.secondary',
|
||||
'&:hover': {
|
||||
bgcolor: modelreasoning === 'high' ? 'primary.dark' : 'action.selected',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PsychologyRoundedIcon sx={{ fontSize: 22 }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.3, my: 0.5 }} />
|
||||
|
||||
{/* modelslug input */}
|
||||
<InputBase
|
||||
value={modelslug || ''}
|
||||
onChange={this.handleModelslugChange}
|
||||
onBlur={this.handleModelslugBlur}
|
||||
onKeyDown={this.handleModelslugKeyDown}
|
||||
placeholder="Modellslug"
|
||||
inputProps={{
|
||||
spellCheck: false,
|
||||
autoCorrect: 'off',
|
||||
autoCapitalize: 'off',
|
||||
'aria-label': 'Modellslug',
|
||||
}}
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: 80,
|
||||
fontSize: '0.78rem',
|
||||
fontFamily: 'monospace',
|
||||
px: 0.8,
|
||||
py: 0.2,
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)'),
|
||||
borderRadius: 1.5,
|
||||
border: '1px solid transparent',
|
||||
'&:focus-within': {
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.1)' : '#fff'),
|
||||
},
|
||||
'& input': {
|
||||
p: 0,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
/* Normal Mode: settings icon on the left end, title visible */
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={this.handleToggleSettingsBar}
|
||||
aria-label="Modell-Einstellungen"
|
||||
sx={{
|
||||
p: 0.5,
|
||||
color: 'text.secondary',
|
||||
'&:hover': { color: 'primary.main', bgcolor: 'action.hover' },
|
||||
}}
|
||||
>
|
||||
<TuneRoundedIcon sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
<AutoAwesomeRoundedIcon sx={{ fontSize: 20, color: 'primary.main' }} />
|
||||
<Typography variant="subtitle1" fontWeight={700}>
|
||||
KI-Assistent
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<IconButton size="small" onClick={this.handleClose} aria-label="Chat schließen" sx={{ ml: 1, flexShrink: 0 }}>
|
||||
<CloseRoundedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user