feat: New Chat, moved new chat button, fixed reloading animation to be more user friendly
This commit is contained in:
parent
959875bdc0
commit
f5025c3684
3 changed files with 92 additions and 26 deletions
|
|
@ -29,7 +29,7 @@ interface ChatsTabProps {
|
|||
onSelectChat?: (chatId: string, featureInstanceId: string) => void;
|
||||
onDragStart?: (chatId: string, event: React.DragEvent) => void;
|
||||
activeWorkflowId?: string;
|
||||
onCreateNew?: () => void;
|
||||
chatListRefreshKey?: number;
|
||||
onRenameChat?: (chatId: string, newName: string) => void | Promise<void>;
|
||||
onDeleteChat?: (chatId: string) => void | Promise<void>;
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
onSelectChat,
|
||||
onDragStart,
|
||||
activeWorkflowId,
|
||||
onCreateNew,
|
||||
chatListRefreshKey,
|
||||
onRenameChat,
|
||||
onDeleteChat,
|
||||
}) => {
|
||||
|
|
@ -82,13 +82,14 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
const [search, setSearch] = useState('');
|
||||
const [filter, setFilter] = useState<ChatFilter>('active');
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editName, setEditName] = useState('');
|
||||
const renameInputRef = useRef<HTMLInputElement>(null);
|
||||
const groupsRef = useRef(groups);
|
||||
groupsRef.current = groups;
|
||||
|
||||
const _loadChats = useCallback(async (serverSearch?: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params: Record<string, unknown> = { includeArchived: true };
|
||||
if (serverSearch) params.search = serverSearch;
|
||||
|
|
@ -140,7 +141,7 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
} catch (err) {
|
||||
console.error('Failed to load chats:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setHasLoadedOnce(true);
|
||||
}
|
||||
}, [context.instanceId, t]);
|
||||
|
||||
|
|
@ -163,6 +164,12 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
}
|
||||
}, [activeWorkflowId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatListRefreshKey) {
|
||||
_loadChats();
|
||||
}
|
||||
}, [chatListRefreshKey, _loadChats]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingId && renameInputRef.current) {
|
||||
renameInputRef.current.focus();
|
||||
|
|
@ -188,8 +195,18 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
const trimmed = editName.trim();
|
||||
setEditingId(null);
|
||||
if (!trimmed || !onRenameChat) return;
|
||||
await onRenameChat(chatId, trimmed);
|
||||
_loadChats();
|
||||
const prev = groupsRef.current;
|
||||
setGroups(gs => gs.map(g => ({
|
||||
...g,
|
||||
chats: g.chats.map(c => (c.id === chatId ? { ...c, label: trimmed } : c)),
|
||||
})));
|
||||
try {
|
||||
await onRenameChat(chatId, trimmed);
|
||||
_loadChats();
|
||||
} catch (err) {
|
||||
console.error('Failed to rename chat:', err);
|
||||
setGroups(prev);
|
||||
}
|
||||
};
|
||||
|
||||
const _handleRenameKeyDown = (e: React.KeyboardEvent, chatId: string) => {
|
||||
|
|
@ -201,23 +218,41 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
}
|
||||
};
|
||||
|
||||
const _setChatStatus = useCallback((chatId: string, status: string) => {
|
||||
setGroups(gs => gs.map(g => ({
|
||||
...g,
|
||||
chats: g.chats.map(c => (c.id === chatId ? { ...c, status } : c)),
|
||||
})));
|
||||
}, []);
|
||||
|
||||
const _removeChat = useCallback((chatId: string) => {
|
||||
setGroups(gs => gs.map(g => ({
|
||||
...g,
|
||||
chats: g.chats.filter(c => c.id !== chatId),
|
||||
})).filter(g => g.chats.length > 0));
|
||||
}, []);
|
||||
|
||||
const _archiveChat = useCallback(async (chatId: string) => {
|
||||
const prev = groupsRef.current;
|
||||
_setChatStatus(chatId, 'archived');
|
||||
try {
|
||||
await api.patch(`/api/workspace/${context.instanceId}/workflows/${chatId}`, { status: 'archived' });
|
||||
_loadChats();
|
||||
} catch (err) {
|
||||
console.error('Failed to archive chat:', err);
|
||||
setGroups(prev);
|
||||
}
|
||||
}, [context.instanceId, _loadChats]);
|
||||
}, [context.instanceId, _setChatStatus]);
|
||||
|
||||
const _restoreChat = useCallback(async (chatId: string) => {
|
||||
const prev = groupsRef.current;
|
||||
_setChatStatus(chatId, 'active');
|
||||
try {
|
||||
await api.patch(`/api/workspace/${context.instanceId}/workflows/${chatId}`, { status: 'active' });
|
||||
_loadChats();
|
||||
} catch (err) {
|
||||
console.error('Failed to restore chat:', err);
|
||||
setGroups(prev);
|
||||
}
|
||||
}, [context.instanceId, _loadChats]);
|
||||
}, [context.instanceId, _setChatStatus]);
|
||||
|
||||
const _isArchived = (chat: ChatItem) => chat.status === 'archived';
|
||||
|
||||
|
|
@ -311,7 +346,17 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
{onDeleteChat && (
|
||||
<button
|
||||
className={`${styles.actionBtn} ${styles.actionBtnDanger}`}
|
||||
onClick={async (e) => { e.stopPropagation(); await onDeleteChat(chat.id); _loadChats(); }}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const prev = groupsRef.current;
|
||||
_removeChat(chat.id);
|
||||
try {
|
||||
await onDeleteChat(chat.id);
|
||||
} catch (err) {
|
||||
console.error('Failed to delete chat:', err);
|
||||
setGroups(prev);
|
||||
}
|
||||
}}
|
||||
title={t('Löschen')}
|
||||
>
|
||||
🗑️
|
||||
|
|
@ -334,8 +379,6 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
return labels[code] || code;
|
||||
};
|
||||
|
||||
if (loading) return <div className={styles.loading}>{t('Chats werden geladen…')}</div>;
|
||||
|
||||
return (
|
||||
<div className={styles.chatsTab}>
|
||||
<div className={styles.toolbar}>
|
||||
|
|
@ -346,11 +389,6 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
{onCreateNew && (
|
||||
<button className={styles.createBtn} onClick={() => { onCreateNew(); setTimeout(_loadChats, 500); }} title={t('Neuer Chat')}>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={`${styles.modeToggle} ${flatMode ? styles.modeActive : ''}`}
|
||||
onClick={() => setFlatMode(!flatMode)}
|
||||
|
|
@ -437,7 +475,7 @@ const ChatsTab: React.FC<ChatsTabProps> = ({ context,
|
|||
</div>
|
||||
)}
|
||||
|
||||
{_allChats.length === 0 && (
|
||||
{hasLoadedOnce && _allChats.length === 0 && (
|
||||
<div className={styles.emptyState}>
|
||||
{filter === 'archived' ? t('Keine archivierten Chats') : t('Keine aktiven Chats')}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ interface UnifiedDataBarProps {
|
|||
hideTabs?: UdbTab[];
|
||||
onSelectChat?: (chatId: string, featureInstanceId: string) => void;
|
||||
activeWorkflowId?: string;
|
||||
onCreateNewChat?: () => void;
|
||||
onRenameChat?: (chatId: string, newName: string) => void;
|
||||
chatListRefreshKey?: number;
|
||||
onDeleteChat?: (chatId: string) => void;
|
||||
onChatDragStart?: (chatId: string, event: React.DragEvent) => void;
|
||||
onFileSelect?: (fileId: string, fileName?: string) => void;
|
||||
|
|
@ -78,8 +78,8 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
|||
hideTabs,
|
||||
onSelectChat,
|
||||
activeWorkflowId,
|
||||
onCreateNewChat,
|
||||
onRenameChat,
|
||||
chatListRefreshKey,
|
||||
onDeleteChat,
|
||||
onChatDragStart,
|
||||
onFileSelect,
|
||||
|
|
@ -122,7 +122,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
|||
onSelectChat={onSelectChat}
|
||||
onDragStart={onChatDragStart}
|
||||
activeWorkflowId={activeWorkflowId}
|
||||
onCreateNew={onCreateNewChat}
|
||||
chatListRefreshKey={chatListRefreshKey}
|
||||
onRenameChat={onRenameChat}
|
||||
onDeleteChat={onDeleteChat}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
);
|
||||
const [mobileLeftOpen, setMobileLeftOpen] = useState(false);
|
||||
const [mobileRightOpen, setMobileRightOpen] = useState(false);
|
||||
const [chatListRefreshKey, setChatListRefreshKey] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const _handleResize = () => {
|
||||
|
|
@ -254,6 +255,27 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
workspace.loadWorkflow(wfId);
|
||||
};
|
||||
|
||||
const sidebarHeaderBtnStyle: React.CSSProperties = {
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
color: '#888',
|
||||
};
|
||||
|
||||
const createChatBtnStyle: React.CSSProperties = {
|
||||
...sidebarHeaderBtnStyle,
|
||||
fontSize: 20,
|
||||
fontWeight: 700,
|
||||
lineHeight: 1,
|
||||
color: 'var(--text-secondary, #555)',
|
||||
};
|
||||
|
||||
const _handleCreateNewChat = useCallback(() => {
|
||||
workspace.resetToNew();
|
||||
setChatListRefreshKey(k => k + 1);
|
||||
}, [workspace]);
|
||||
|
||||
const tabButtonStyle = (active: boolean): React.CSSProperties => ({
|
||||
flex: 1,
|
||||
padding: '6px 0',
|
||||
|
|
@ -356,7 +378,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
onTabChange={setUdbTab}
|
||||
onSelectChat={_handleConversationSelect}
|
||||
activeWorkflowId={workspace.workflowId ?? undefined}
|
||||
onCreateNewChat={workspace.resetToNew}
|
||||
chatListRefreshKey={chatListRefreshKey}
|
||||
onRenameChat={_handleRenameChat}
|
||||
onDeleteChat={_handleDeleteChat}
|
||||
onFileSelect={_handleFileSelect}
|
||||
|
|
@ -408,7 +430,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
}}>
|
||||
<div style={{ padding: '6px 12px', borderBottom: '1px solid var(--border-color, #e0e0e0)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>{t('Workspace')}</span>
|
||||
<button onClick={() => setLeftCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}>◀</button>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<button onClick={_handleCreateNewChat} style={createChatBtnStyle} title={t('Neuer Chat')}>+</button>
|
||||
<button onClick={() => setLeftCollapsed(true)} style={sidebarHeaderBtnStyle}>◀</button>
|
||||
</div>
|
||||
</div>
|
||||
{_leftPanelBody}
|
||||
</aside>
|
||||
|
|
@ -604,7 +629,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
>
|
||||
<div style={{ padding: '10px 12px', borderBottom: '1px solid var(--border-color, #e0e0e0)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>{t('Workspace')}</span>
|
||||
<button onClick={() => setMobileLeftOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: '#666' }}>×</button>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<button onClick={_handleCreateNewChat} style={createChatBtnStyle} title={t('Neuer Chat')}>+</button>
|
||||
<button onClick={() => setMobileLeftOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: '#666' }}>×</button>
|
||||
</div>
|
||||
</div>
|
||||
{_leftPanelBody}
|
||||
</aside>
|
||||
|
|
|
|||
Loading…
Reference in a new issue