Skip to content

Commit ee6c75a

Browse files
author
cw
committed
Merge branch 'feat/web-ui-complete' into beta
2 parents 8cb5a47 + 34e0780 commit ee6c75a

21 files changed

Lines changed: 331 additions & 954 deletions

web-ui/src/components/ChatPanel.tsx

Lines changed: 0 additions & 120 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
interface ConfirmDialogProps {
4+
open: boolean;
5+
title?: string;
6+
message: string;
7+
confirmLabel?: string;
8+
cancelLabel?: string;
9+
danger?: boolean;
10+
onConfirm: () => void;
11+
onCancel: () => void;
12+
}
13+
14+
export default function ConfirmDialog({
15+
open, title, message, confirmLabel = 'Confirm', cancelLabel = 'Cancel',
16+
danger = false, onConfirm, onCancel,
17+
}: ConfirmDialogProps) {
18+
const confirmRef = useRef<HTMLButtonElement>(null);
19+
20+
useEffect(() => {
21+
if (open) confirmRef.current?.focus();
22+
}, [open]);
23+
24+
useEffect(() => {
25+
if (!open) return;
26+
const onKey = (e: KeyboardEvent) => {
27+
if (e.key === 'Escape') onCancel();
28+
};
29+
window.addEventListener('keydown', onKey);
30+
return () => window.removeEventListener('keydown', onKey);
31+
}, [open, onCancel]);
32+
33+
if (!open) return null;
34+
35+
return (
36+
<div className="cd-overlay" onClick={onCancel}>
37+
<div className="cd-dialog" onClick={e => e.stopPropagation()}>
38+
{title && <h3 className="cd-title">{title}</h3>}
39+
<p className="cd-message">{message}</p>
40+
<div className="cd-actions">
41+
<button className="cd-btn cd-cancel" onClick={onCancel}>{cancelLabel}</button>
42+
<button
43+
ref={confirmRef}
44+
className={`cd-btn cd-confirm${danger ? ' danger' : ''}`}
45+
onClick={onConfirm}
46+
>
47+
{confirmLabel}
48+
</button>
49+
</div>
50+
</div>
51+
</div>
52+
);
53+
}

web-ui/src/components/Layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NavLink, Outlet } from 'react-router-dom';
2+
import ToastContainer from './Toast';
23
import '../styles/theme.css';
34

45
const navItems = [
@@ -51,6 +52,7 @@ export default function Layout() {
5152
<div className="page-content">
5253
<Outlet />
5354
</div>
55+
<ToastContainer />
5456
</div>
5557
);
5658
}

web-ui/src/components/ModelSelector.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.

web-ui/src/components/QuickActions.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

web-ui/src/components/StatsPanel.tsx

Lines changed: 0 additions & 50 deletions
This file was deleted.

web-ui/src/components/Toast.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useState, useEffect, useCallback } from 'react';
2+
3+
type ToastType = 'success' | 'error' | 'info';
4+
5+
interface ToastMessage {
6+
id: number;
7+
text: string;
8+
type: ToastType;
9+
}
10+
11+
let _addToast: ((text: string, type?: ToastType) => void) | null = null;
12+
13+
/** Call from anywhere to show a toast notification. */
14+
export function showToast(text: string, type: ToastType = 'info') {
15+
_addToast?.(text, type);
16+
}
17+
18+
let _nextId = 1;
19+
20+
export default function ToastContainer() {
21+
const [toasts, setToasts] = useState<ToastMessage[]>([]);
22+
23+
const addToast = useCallback((text: string, type: ToastType = 'info') => {
24+
const id = _nextId++;
25+
setToasts(prev => [...prev, { id, text, type }]);
26+
setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 3000);
27+
}, []);
28+
29+
useEffect(() => { _addToast = addToast; return () => { _addToast = null; }; }, [addToast]);
30+
31+
if (toasts.length === 0) return null;
32+
33+
const icons: Record<ToastType, string> = { success: 'check_circle', error: 'error', info: 'info' };
34+
35+
return (
36+
<div className="toast-container">
37+
{toasts.map(t => (
38+
<div key={t.id} className={`toast-item toast-${t.type}`}>
39+
<span className="material-icons toast-icon">{icons[t.type]}</span>
40+
<span className="toast-text">{t.text}</span>
41+
<button className="toast-close" onClick={() => setToasts(prev => prev.filter(x => x.id !== t.id))}>
42+
<span className="material-icons">close</span>
43+
</button>
44+
</div>
45+
))}
46+
</div>
47+
);
48+
}

web-ui/src/components/episode/ScenePipelineEditor.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,9 @@ function ScenePipelineEditorInner({ episodeId, settings, onPipelineReady }: Scen
700700
{/* Empty state */}
701701
{nodes.length === 0 && (
702702
<div className="canvas-empty">
703-
Drag nodes from the catalog to start building your pipeline
703+
<span className="material-icons" style={{ fontSize: 48, opacity: 0.3, marginBottom: 12 }}>account_tree</span>
704+
<div style={{ fontSize: '1.1rem', marginBottom: 6 }}>No nodes yet</div>
705+
<div style={{ fontSize: '0.85rem', opacity: 0.5 }}>Drag nodes from the catalog on the left to build your pipeline</div>
704706
</div>
705707
)}
706708

web-ui/src/components/pipeline/pipeline.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,7 @@ input:checked + .toggle-slider::before {
995995
position: absolute;
996996
inset: 0;
997997
display: flex;
998+
flex-direction: column;
998999
align-items: center;
9991000
justify-content: center;
10001001
color: var(--text-muted);

0 commit comments

Comments
 (0)