+ {blocks.map((b, idx) => {
+ const last = idx === blocks.length - 1;
+ const tail = last ? cursor : null;
+ const k = `b${idx}`;
+ switch (b.type) {
+ case 'table':
+ return (
+
+
+
+
+ {b.headers.map((h, colIdx) => (
+ |
+ {parseInline(h, `${k}-th-${colIdx}`)}
+ |
+ ))}
+
+
+
+ {b.rows.map((row, rowIdx) => {
+ const isLastRow = last && rowIdx === b.rows.length - 1;
+ return (
+
+ {b.headers.map((_, colIdx) => {
+ const isLastCell = isLastRow && colIdx === b.headers.length - 1;
+ const cellValue = row[colIdx] ?? '';
+ return (
+ |
+ {parseInline(cellValue, `${k}-td-${rowIdx}-${colIdx}`)}
+ {isLastCell ? tail : null}
+ |
+ );
+ })}
+
+ );
+ })}
+
+
+
+ );
+ case 'code':
+ return (
+
+
+ {b.text}
+ {tail}
+
+
+ );
+ case 'heading': {
+ const Tag = `h${b.level}`;
+ return (
+
+ {parseInline(b.text, k)}
+ {tail}
+
+ );
+ }
+ case 'hr':
+ return
;
+ case 'quote':
+ return (
+
+ {parseInline(b.text, k)}
+ {tail}
+
+ );
+ case 'list': {
+ const Tag = b.ordered ? 'ol' : 'ul';
+ return (
+
+ {b.items.map((it, j) => (
+
+ {parseInline(it, `${k}-${j}`)}
+ {last && j === b.items.length - 1 ? tail : null}
+
+ ))}
+
+ );
+ }
+ default:
+ return (
+
+ {parseInline(b.text, k)}
+ {tail}
+
+ );
+ }
+ })}
+ {streaming && blocks.length === 0 ? cursor : null}
+
+ );
+}
+
+export default memo(HealingMarkdown);
diff --git a/src/components/chat/NewConversationDialog.jsx b/src/components/chat/NewConversationDialog.jsx
new file mode 100644
index 0000000..755efb3
--- /dev/null
+++ b/src/components/chat/NewConversationDialog.jsx
@@ -0,0 +1,171 @@
+import { PureComponent } from 'react';
+import Dialog from '@mui/material/Dialog';
+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 MenuItem from '@mui/material/MenuItem';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import CircularProgress from '@mui/material/CircularProgress';
+import AutoAwesomeRoundedIcon from '@mui/icons-material/AutoAwesomeRounded';
+
+import { PRESET_MODELS } from '../../automats/chatAutomat';
+
+export default class NewConversationDialog extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ provider: 'openrouter',
+ model: PRESET_MODELS[0].value,
+ customModel: '',
+ isCustom: false,
+ loading: false,
+ error: null,
+ };
+ }
+
+ handleModelChange = (e) => {
+ const val = e.target.value;
+ if (val === '__custom__') {
+ this.setState({ isCustom: true });
+ } else {
+ this.setState({ model: val, isCustom: false });
+ }
+ };
+
+ handleSubmit = async (e) => {
+ e?.preventDefault();
+ const { provider, model, customModel, isCustom } = this.state;
+ const finalModel = isCustom ? customModel.trim() : model.trim();
+ if (!finalModel) {
+ this.setState({ error: 'Bitte ein Modell angeben.' });
+ return;
+ }
+
+ this.setState({ loading: true, error: null });
+ try {
+ await this.props.onCreate?.({
+ provider: provider.trim() || 'openrouter',
+ model: finalModel,
+ });
+ this.props.onClose?.();
+ } catch (err) {
+ this.setState({
+ loading: false,
+ error: err?.message || 'Erstellung fehlgeschlagen.',
+ });
+ }
+ };
+
+ render() {
+ const { open, onClose } = this.props;
+ const { provider, model, customModel, isCustom, loading, error } = this.state;
+
+ return (
+