fix:readded deleted code, fixed build

This commit is contained in:
Ida Dittrich 2026-01-12 09:15:07 +01:00
parent 5808bd4bee
commit 239fd328bc
11 changed files with 243 additions and 102 deletions

View file

@ -43,10 +43,10 @@ export async function fetchAttributes(
request: ApiRequestFunction,
entityType: string
): Promise<AttributeDefinition[]> {
const data = await request<any>({
const data = await request({
url: `/api/attributes/${entityType}`,
method: 'get'
});
}) as any;
// Extract attributes from response - check if response.data.attributes exists, otherwise check if response.data is an array
let attrs: AttributeDefinition[] = [];
@ -81,10 +81,10 @@ export async function fetchConnectionAttributes(request: ApiRequestFunction): Pr
* Endpoint: GET /api/attributes/FileItem
*/
export async function fetchFileAttributes(request: ApiRequestFunction): Promise<AttributeDefinition[]> {
const data = await request<AttributeDefinition[] | { attributes: AttributeDefinition[] }>({
const data = await request({
url: '/api/attributes/FileItem',
method: 'get'
});
}) as AttributeDefinition[] | { attributes: AttributeDefinition[] };
// Handle different response formats
if (Array.isArray(data)) {

View file

@ -206,12 +206,12 @@ export async function stopChatbotApi(
request: ApiRequestFunction,
workflowId: string
): Promise<ChatbotWorkflow> {
const data = await request<ChatbotWorkflow>({
const data = await request({
url: `/api/chatbot/${workflowId}/stop`,
method: 'post'
});
return data;
return data as ChatbotWorkflow;
}
/**
@ -229,11 +229,11 @@ export async function getChatbotThreadsApi(
console.log(`[getChatbotThreadsApi] Fetching threads with params:`, requestParams);
const data = await request<any>({
const data = await request({
url: '/api/chatbot/threads',
method: 'get',
params: requestParams
});
}) as any;
console.log(`[getChatbotThreadsApi] Full response:`, JSON.stringify(data, null, 2));
console.log(`[getChatbotThreadsApi] Response structure:`, {
@ -265,11 +265,11 @@ export async function getChatbotThreadApi(
): Promise<{ workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } }> {
console.log(`[getChatbotThreadApi] Fetching thread with workflowId: ${workflowId}`);
const data = await request<{ workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } }>({
const data = await request({
url: '/api/chatbot/threads',
method: 'get',
params: { workflowId }
});
}) as { workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } };
console.log(`[getChatbotThreadApi] Full response for workflowId ${workflowId}:`, JSON.stringify(data, null, 2));
console.log(`[getChatbotThreadApi] Response structure:`, {
@ -279,7 +279,7 @@ export async function getChatbotThreadApi(
hasItems: !!data.chatData?.items,
chatDataKeys: data.chatData ? Object.keys(data.chatData) : [],
itemsLength: Array.isArray(data.chatData?.items) ? data.chatData.items.length : 'not an array',
chatDataTypes: Array.isArray(data.chatData?.items) ? data.chatData.items.map(item => item?.type).filter(Boolean) : []
chatDataTypes: Array.isArray(data.chatData?.items) ? data.chatData.items.map((item: ChatDataItem) => item?.type).filter(Boolean) : []
});
return {
@ -301,7 +301,7 @@ export async function deleteChatbotWorkflowApi(
workflowId: string
): Promise<boolean> {
try {
await request<any>({
await request({
url: `/api/chatbot/${workflowId}`,
method: 'delete'
});

View file

@ -162,7 +162,7 @@ export function FormGeneratorControls({
{/* Delete Controls - Show when items are selected */}
{selectable && selectedCount > 0 && (
<div className={styles.deleteControlsIntegrated}>
{selectedCount === 1 && onDeleteSingle && !(selectedCount === _displayData.length && _displayData.length > 0) && (
{selectedCount === 1 && onDeleteSingle && !(selectedCount === displayData.length && displayData.length > 0) && (
<Button
onClick={onDeleteSingle}
variant="primary"
@ -172,7 +172,7 @@ export function FormGeneratorControls({
{t('formgen.delete.single', 'Delete')}
</Button>
)}
{(selectedCount > 1 || (selectedCount === _displayData.length && _displayData.length > 0)) && onDeleteMultiple && (
{(selectedCount > 1 || (selectedCount === displayData.length && displayData.length > 0)) && onDeleteMultiple && (
<Button
onClick={onDeleteMultiple}
variant="primary"

View file

@ -74,6 +74,7 @@
padding: 0;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
margin-top: 10px;
}
.selectAllCheckbox {
@ -293,13 +294,8 @@
gap: 4px;
flex-shrink: 0;
padding-top: 0;
opacity: 0;
transition: opacity 0.15s ease;
}
.listItem:hover .itemActions,
.listItem.selected .itemActions {
opacity: 1;
transition: opacity 0.15s ease;
}
.itemFields {

View file

@ -701,74 +701,81 @@ export function FormGeneratorList<T extends Record<string, any>>({
) : (
<>
{/* Select All Header */}
{selectable && displayData.length > 0 && (
{selectable && (
<div className={styles.listHeader}>
<input
type="checkbox"
checked={(() => {
const selectableIndices = displayData
.map((row, index) => ({ row, index }))
.filter(({ row }) => !isItemSelectable || isItemSelectable(row))
.map(({ index }) => index);
return selectedItems.size === selectableIndices.length && selectableIndices.length > 0;
})()}
onChange={handleSelectAll}
title={t('formgen.select.all', 'Select all items')}
className={styles.selectAllCheckbox}
/>
{displayData.length > 0 && (
<>
{hasSelectedItems && (onDeleteMultiple || onDelete) ? (
// Show delete button when items are selected
<div className={styles.headerDeleteButtonContainer}>
{isConfirmingDelete ? (
<div className={actionButtonStyles.deleteConfirmButtons}>
<button
onClick={handleConfirmDelete}
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.confirmButton}`}
title={t('formgen.delete.confirm', 'Confirm delete')}
disabled={isDeleting}
>
<span className={actionButtonStyles.actionIcon}>
<IoIosCheckmark />
</span>
</button>
<button
onClick={handleCancelDelete}
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.cancelButton}`}
title={t('formgen.delete.cancel', 'Cancel delete')}
disabled={isDeleting}
>
<span className={actionButtonStyles.actionIcon}>
<IoIosClose />
</span>
</button>
</div>
) : (
<button
onClick={handleDeleteMultipleClick}
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.delete} ${styles.headerDeleteButton} ${isDeleting ? actionButtonStyles.loading : ''}`}
title={allItemsSelected
? t('formgen.delete.all', `Delete all ${selectedItems.size} items`)
: t('formgen.delete.multiple', `Delete ${selectedItems.size} selected items`)}
disabled={isDeleting}
>
<span className={actionButtonStyles.actionIcon}>
<IoIosTrash />
</span>
</button>
)}
</div>
) : (
// Show select all checkbox when no items are selected
<input
type="checkbox"
checked={(() => {
const selectableIndices = displayData
.map((row, index) => ({ row, index }))
.filter(({ row }) => !isItemSelectable || isItemSelectable(row))
.map(({ index }) => index);
return selectedItems.size === selectableIndices.length && selectableIndices.length > 0;
})()}
onChange={handleSelectAll}
title={t('formgen.select.all', 'Select all items')}
className={styles.selectAllCheckbox}
/>
)}
</>
)}
{title && (
<h3 className={styles.listTitle}>{title}</h3>
)}
{title && data.length > 0 && (
<span className={styles.listCount}>({data.length})</span>
)}
{hasSelectedItems && (onDeleteMultiple || onDelete) && (
<div className={styles.headerDeleteButtonContainer}>
{isConfirmingDelete ? (
<div className={actionButtonStyles.deleteConfirmButtons}>
<button
onClick={handleConfirmDelete}
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.confirmButton}`}
title={t('formgen.delete.confirm', 'Confirm delete')}
disabled={isDeleting}
>
<span className={actionButtonStyles.actionIcon}>
<IoIosCheckmark />
</span>
</button>
<button
onClick={handleCancelDelete}
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.cancelButton}`}
title={t('formgen.delete.cancel', 'Cancel delete')}
disabled={isDeleting}
>
<span className={actionButtonStyles.actionIcon}>
<IoIosClose />
</span>
</button>
</div>
) : (
<button
onClick={handleDeleteMultipleClick}
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.delete} ${styles.headerDeleteButton} ${isDeleting ? actionButtonStyles.loading : ''}`}
title={allItemsSelected
? t('formgen.delete.all', `Delete all ${selectedItems.size} items`)
: t('formgen.delete.multiple', `Delete ${selectedItems.size} selected items`)}
disabled={isDeleting}
>
<span className={actionButtonStyles.actionIcon}>
<IoIosTrash />
</span>
</button>
)}
</div>
)}
{headerButton && (
<div className={styles.headerButtonWrapper}>
{headerButton}
</div>
)}
{sortable && (
{sortable && displayData.length > 0 && (
<div className={styles.sortControls}>
{detectedFields.map(field => (
<button

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { GenericPageData, PageButton, PageContent, resolveLanguageText, SettingsFieldConfig, SettingsSectionConfig, GenericDataHook } from './pageInterface';
import { FormGenerator } from '../../components/FormGenerator';
import { FormGenerator, FormGeneratorList } from '../../components/FormGenerator';
import { FormGeneratorForm, AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
import { Button, UploadButton, CreateButton, TextField, Messages, ChatMessage, LogMessage, DropdownSelect, Log, WorkflowStatus, Tabs } from '../../components/UiComponents';
import { Popup } from '../../components/UiComponents/Popup';
@ -10,6 +10,7 @@ import { DragDropOverlay } from '../../components/UiComponents/DragDropOverlay';
import { useLanguage } from '../../providers/language/LanguageContext';
import { usePermissions } from '../../hooks/usePermissions';
import { FiPaperclip } from 'react-icons/fi';
import { IoMdAdd } from 'react-icons/io';
import type { WorkflowFile } from '../../hooks/playground/useDashboardInputForm';
import styles from '../../styles/pages.module.css';
@ -172,9 +173,11 @@ const TabsRenderer: React.FC<{
label: resolveLanguageText(tab.label, t),
content: (
<>
{tab.content.map((nestedContent, index) =>
renderContent(nestedContent, index)
)}
{tab.content.map((nestedContent, index) => (
<React.Fragment key={nestedContent.id || `${tab.id}-${index}`}>
{renderContent(nestedContent)}
</React.Fragment>
))}
</>
)
}));
@ -1435,7 +1438,7 @@ const PageRenderer: React.FC<PageRendererProps> = ({
<div style={{ flexShrink: 0, display: 'flex', gap: '8px' }}>
<UploadButton
onUpload={hookData.handleFileUpload ? async (file: File) => {
const result = await hookData.handleFileUpload!(file);
await hookData.handleFileUpload!(file);
// Error handling is done in the hook
} : async () => {}}
disabled={hookData.isSubmitting || false}
@ -1774,8 +1777,8 @@ const PageRenderer: React.FC<PageRendererProps> = ({
const gap = columnsConfig.gap || '1rem';
return (
<div
key={content.id}
<div
key={content.id}
className={styles.columnsContainer}
style={{
display: 'grid',
@ -1785,15 +1788,152 @@ const PageRenderer: React.FC<PageRendererProps> = ({
>
{columnsConfig.columns.map((column, colIndex) => (
<div key={column.id} className={styles.column}>
{column.content.map((nestedContent, index) =>
renderContent(nestedContent, `${colIndex}-${index}`)
)}
{column.content.map((nestedContent, index) => (
<React.Fragment key={nestedContent.id || `${colIndex}-${index}`}>
{renderContent(nestedContent)}
</React.Fragment>
))}
</div>
))}
</div>
);
}
case 'chatHistory': {
const config = content.chatHistoryConfig || {};
const threads = (hookData as any)?.threads || [];
const selectedThreadId = (hookData as any)?.selectedThreadId || null;
const threadsLoading = (hookData as any)?.threadsLoading || false;
const threadsError = (hookData as any)?.threadsError || null;
const selectThread = (hookData as any)?.selectThread;
const handleDelete = (hookData as any)?.handleDelete;
const deletingItems = (hookData as any)?.deletingItems || new Set();
const startNewChat = (hookData as any)?.startNewChat;
// Get thread preview text for display
const getThreadPreview = (thread: any): string => {
if (thread.name) return thread.name;
if (thread.firstMessage) return thread.firstMessage.substring(0, 50) + (thread.firstMessage.length > 50 ? '...' : '');
return t('chat_history.no_message_content', 'No message content available');
};
// Prepare data for FormGeneratorList - add a display name field
const threadsWithDisplayName = threads.map((thread: any) => ({
...thread,
displayName: getThreadPreview(thread)
}));
// Define fields for FormGeneratorList - only show creation time
const fields = [
{
key: 'displayName',
label: '', // No label needed, will be shown as first field
type: 'string' as const
},
{
key: 'startedAt',
label: '', // No label needed, will be shown as metadata
type: 'date' as const
}
];
// Ensure hookData has required properties for DeleteActionButton
const enhancedHookData = {
...hookData,
refetch: (hookData as any)?.refetch || (hookData as any)?.loadThreads || (() => {}),
handleDelete: handleDelete || (() => Promise.resolve(false)),
removeOptimistically: (hookData as any)?.removeOptimistically || (hookData as any)?.removeThreadOptimistically,
deletingItems: deletingItems
};
// Configure action buttons - delete button (always show)
const actionButtons = [
{
type: 'delete' as const,
disabled: (row: any) => !handleDelete || deletingItems.has(row.id),
loading: (row: any) => deletingItems.has(row.id),
title: t('chat_history.delete_tooltip', 'Delete workflow'),
operationName: 'handleDelete',
loadingStateName: 'deletingItems'
}
];
// Handle item click to select thread (checkbox and delete button stop propagation)
const handleItemClick = (row: any) => {
if (!deletingItems.has(row.id) && selectThread) {
selectThread(row.id);
}
};
// Get data attributes for styling selected items
const getItemDataAttributes = (row: any) => {
return {
'selected-thread-id': row.id === selectedThreadId ? 'true' : 'false'
};
};
// Show error state if there's an error
if (threadsError) {
return (
<div key={content.id} className={styles.chatHistorySection}>
<div className={styles.chatHistoryError}>
<p>{t('chat_history.error_loading', 'Error loading workflows:')} {threadsError}</p>
{(hookData as any)?.loadThreads && (
<button
onClick={() => (hookData as any).loadThreads()}
className={styles.retryButton}
>
{t('chat_history.try_again', 'Try Again')}
</button>
)}
</div>
</div>
);
}
return (
<div key={content.id} className={styles.chatHistorySection}>
<FormGeneratorList
data={threadsWithDisplayName}
fields={fields}
title={t('chat_history.title', 'Chat History')}
loading={threadsLoading}
emptyMessage={config.emptyMessage
? resolveLanguageText(config.emptyMessage, t)
: t('chat_history.empty_state', 'No workflows available')}
onItemClick={handleItemClick}
actionButtons={actionButtons}
onDelete={handleDelete}
onDeleteMultiple={handleDelete ? async (rowsToDelete: any[]) => {
// Handle multiple delete
for (const rowToDelete of rowsToDelete) {
await handleDelete(rowToDelete.id);
}
} : undefined}
hookData={enhancedHookData}
getItemDataAttributes={getItemDataAttributes}
searchable={false}
filterable={false}
sortable={false}
pagination={false}
selectable={true}
className={styles.chatHistoryList}
headerButton={startNewChat ? (
<Button
onClick={() => startNewChat()}
variant="primary"
size="sm"
icon={IoMdAdd}
className={styles.chatHistoryNewChatButton}
>
{t('chat_history.new_chat', 'New Chat')}
</Button>
) : undefined}
/>
</div>
);
}
default:
return null;
}

View file

@ -20,7 +20,7 @@ const PekMapView: React.FC = () => {
const adjacentSet = new Map<string, any>();
selectedParcels.forEach(parcel => {
if (parcel.adjacent_parcels) {
parcel.adjacent_parcels.forEach(adj => {
parcel.adjacent_parcels.forEach((adj: { id: string }) => {
if (!adjacentSet.has(adj.id)) {
adjacentSet.set(adj.id, adj);
}

View file

@ -125,7 +125,7 @@ export interface SettingsConfig {
// Content section for paragraphs
export interface PageContent {
id: string;
type: 'paragraph' | 'heading' | 'list' | 'code' | 'divider' | 'custom' | 'table' | 'inputForm' | 'messages' | 'settings' | 'log' | 'tabs' | 'columns';
type: 'paragraph' | 'heading' | 'list' | 'code' | 'divider' | 'custom' | 'table' | 'inputForm' | 'messages' | 'settings' | 'log' | 'tabs' | 'columns' | 'chatHistory';
content?: string | LanguageText; // Optional for dividers
level?: number; // For headings (1-6)
items?: (string | LanguageText)[]; // For lists
@ -150,6 +150,10 @@ export interface PageContent {
logConfig?: {
emptyMessage?: string | LanguageText;
};
// Chat history-specific properties
chatHistoryConfig?: {
emptyMessage?: string | LanguageText;
};
// Tabs-specific properties
tabsConfig?: {
tabs: Array<{

View file

@ -69,7 +69,6 @@ export function useChatbot() {
processedLogsRef.current.clear(); // Clear processed logs tracking
// Reset thinking message refs
const thinkingId = thinkingMessageIdRef.current;
thinkingMessageIdRef.current = null;
thinkingLogsRef.current = [];
@ -189,13 +188,8 @@ export function useChatbot() {
return;
}
// Check if this is an assistant message that should clear thinking message
const messageRole = messageData.role?.toLowerCase();
const isAssistantMessage = messageRole === 'assistant' || messageRole === 'ai' || messageRole === 'system';
// ALWAYS clear thinking message when ANY message arrives (not just assistant)
// The thinking message should disappear when the real message comes
const thinkingId = thinkingMessageIdRef.current;
// Check if we've already processed this message
const messageId = messageData.id;
@ -367,7 +361,7 @@ export function useChatbot() {
}, []);
// Handle file upload
const handleFileUpload = useCallback(async (file: File): Promise<{ success: boolean; data?: any }> => {
const handleFileUpload = useCallback(async (file: File): Promise<{ success: boolean; data: any }> => {
setUploadError(null);
setUploadingFile(true);
@ -417,7 +411,7 @@ export function useChatbot() {
console.error('File upload failed:', err);
const errorMessage = err.message || 'Failed to upload file';
setUploadError(errorMessage);
return { success: false, error: errorMessage };
return { success: false, data: null };
} finally {
setUploadingFile(false);
}
@ -517,9 +511,6 @@ export function useChatbot() {
console.log('[handleSubmit] Final requestBody:', JSON.stringify(requestBody, null, 2));
// Track if workflow was created in this request
let workflowCreated = false;
// Clear thinking message when starting a new request
clearThinkingMessage();
processedLogsRef.current.clear(); // Clear processed logs for new request
@ -553,7 +544,6 @@ export function useChatbot() {
// New workflow created - select it automatically
setWorkflowId(messageData.workflowId);
setSelectedThreadId(messageData.workflowId);
workflowCreated = true;
} else {
// Existing workflow - ensure it's selected
setSelectedThreadId(messageData.workflowId);

View file

@ -527,7 +527,7 @@ export function createProjectsTableHook(): () => GenericDataHook {
}, []);
// Update project
const handleProjectUpdate = useCallback(async (id: string, updateData: any, originalData?: any): Promise<{ success: boolean }> => {
const handleProjectUpdate = useCallback(async (id: string, updateData: any): Promise<{ success: boolean }> => {
try {
setEditingProjects(prev => new Set(prev).add(id));
@ -999,7 +999,7 @@ export function createParzellenTableHook(): () => GenericDataHook {
}, []);
// Update parzelle
const handleParzelleUpdate = useCallback(async (id: string, updateData: any, originalData?: any): Promise<{ success: boolean }> => {
const handleParzelleUpdate = useCallback(async (id: string, updateData: any): Promise<{ success: boolean }> => {
try {
setEditingParzellen(prev => new Set(prev).add(id));

View file

@ -458,6 +458,10 @@
height: 16px;
}
.chatHistoryNewChatButton {
border-radius: 25px !important;
}
.chatHistoryEmpty {
display: flex;
align-items: center;