fix:readded deleted code, fixed build
This commit is contained in:
parent
5808bd4bee
commit
239fd328bc
11 changed files with 243 additions and 102 deletions
|
|
@ -43,10 +43,10 @@ export async function fetchAttributes(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
entityType: string
|
entityType: string
|
||||||
): Promise<AttributeDefinition[]> {
|
): Promise<AttributeDefinition[]> {
|
||||||
const data = await request<any>({
|
const data = await request({
|
||||||
url: `/api/attributes/${entityType}`,
|
url: `/api/attributes/${entityType}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
}) as any;
|
||||||
|
|
||||||
// Extract attributes from response - check if response.data.attributes exists, otherwise check if response.data is an array
|
// Extract attributes from response - check if response.data.attributes exists, otherwise check if response.data is an array
|
||||||
let attrs: AttributeDefinition[] = [];
|
let attrs: AttributeDefinition[] = [];
|
||||||
|
|
@ -81,10 +81,10 @@ export async function fetchConnectionAttributes(request: ApiRequestFunction): Pr
|
||||||
* Endpoint: GET /api/attributes/FileItem
|
* Endpoint: GET /api/attributes/FileItem
|
||||||
*/
|
*/
|
||||||
export async function fetchFileAttributes(request: ApiRequestFunction): Promise<AttributeDefinition[]> {
|
export async function fetchFileAttributes(request: ApiRequestFunction): Promise<AttributeDefinition[]> {
|
||||||
const data = await request<AttributeDefinition[] | { attributes: AttributeDefinition[] }>({
|
const data = await request({
|
||||||
url: '/api/attributes/FileItem',
|
url: '/api/attributes/FileItem',
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
}) as AttributeDefinition[] | { attributes: AttributeDefinition[] };
|
||||||
|
|
||||||
// Handle different response formats
|
// Handle different response formats
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
|
|
|
||||||
|
|
@ -206,12 +206,12 @@ export async function stopChatbotApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
workflowId: string
|
workflowId: string
|
||||||
): Promise<ChatbotWorkflow> {
|
): Promise<ChatbotWorkflow> {
|
||||||
const data = await request<ChatbotWorkflow>({
|
const data = await request({
|
||||||
url: `/api/chatbot/${workflowId}/stop`,
|
url: `/api/chatbot/${workflowId}/stop`,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data as ChatbotWorkflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -229,11 +229,11 @@ export async function getChatbotThreadsApi(
|
||||||
|
|
||||||
console.log(`[getChatbotThreadsApi] Fetching threads with params:`, requestParams);
|
console.log(`[getChatbotThreadsApi] Fetching threads with params:`, requestParams);
|
||||||
|
|
||||||
const data = await request<any>({
|
const data = await request({
|
||||||
url: '/api/chatbot/threads',
|
url: '/api/chatbot/threads',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: requestParams
|
params: requestParams
|
||||||
});
|
}) as any;
|
||||||
|
|
||||||
console.log(`[getChatbotThreadsApi] Full response:`, JSON.stringify(data, null, 2));
|
console.log(`[getChatbotThreadsApi] Full response:`, JSON.stringify(data, null, 2));
|
||||||
console.log(`[getChatbotThreadsApi] Response structure:`, {
|
console.log(`[getChatbotThreadsApi] Response structure:`, {
|
||||||
|
|
@ -265,11 +265,11 @@ export async function getChatbotThreadApi(
|
||||||
): Promise<{ workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } }> {
|
): Promise<{ workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } }> {
|
||||||
console.log(`[getChatbotThreadApi] Fetching thread with workflowId: ${workflowId}`);
|
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',
|
url: '/api/chatbot/threads',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { workflowId }
|
params: { workflowId }
|
||||||
});
|
}) as { workflow: ChatbotWorkflow; chatData: { items: ChatDataItem[] } };
|
||||||
|
|
||||||
console.log(`[getChatbotThreadApi] Full response for workflowId ${workflowId}:`, JSON.stringify(data, null, 2));
|
console.log(`[getChatbotThreadApi] Full response for workflowId ${workflowId}:`, JSON.stringify(data, null, 2));
|
||||||
console.log(`[getChatbotThreadApi] Response structure:`, {
|
console.log(`[getChatbotThreadApi] Response structure:`, {
|
||||||
|
|
@ -279,7 +279,7 @@ export async function getChatbotThreadApi(
|
||||||
hasItems: !!data.chatData?.items,
|
hasItems: !!data.chatData?.items,
|
||||||
chatDataKeys: data.chatData ? Object.keys(data.chatData) : [],
|
chatDataKeys: data.chatData ? Object.keys(data.chatData) : [],
|
||||||
itemsLength: Array.isArray(data.chatData?.items) ? data.chatData.items.length : 'not an array',
|
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 {
|
return {
|
||||||
|
|
@ -301,7 +301,7 @@ export async function deleteChatbotWorkflowApi(
|
||||||
workflowId: string
|
workflowId: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await request<any>({
|
await request({
|
||||||
url: `/api/chatbot/${workflowId}`,
|
url: `/api/chatbot/${workflowId}`,
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ export function FormGeneratorControls({
|
||||||
{/* Delete Controls - Show when items are selected */}
|
{/* Delete Controls - Show when items are selected */}
|
||||||
{selectable && selectedCount > 0 && (
|
{selectable && selectedCount > 0 && (
|
||||||
<div className={styles.deleteControlsIntegrated}>
|
<div className={styles.deleteControlsIntegrated}>
|
||||||
{selectedCount === 1 && onDeleteSingle && !(selectedCount === _displayData.length && _displayData.length > 0) && (
|
{selectedCount === 1 && onDeleteSingle && !(selectedCount === displayData.length && displayData.length > 0) && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onDeleteSingle}
|
onClick={onDeleteSingle}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|
@ -172,7 +172,7 @@ export function FormGeneratorControls({
|
||||||
{t('formgen.delete.single', 'Delete')}
|
{t('formgen.delete.single', 'Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{(selectedCount > 1 || (selectedCount === _displayData.length && _displayData.length > 0)) && onDeleteMultiple && (
|
{(selectedCount > 1 || (selectedCount === displayData.length && displayData.length > 0)) && onDeleteMultiple && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onDeleteMultiple}
|
onClick={onDeleteMultiple}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectAllCheckbox {
|
.selectAllCheckbox {
|
||||||
|
|
@ -293,13 +294,8 @@
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.listItem:hover .itemActions,
|
|
||||||
.listItem.selected .itemActions {
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemFields {
|
.itemFields {
|
||||||
|
|
|
||||||
|
|
@ -701,74 +701,81 @@ export function FormGeneratorList<T extends Record<string, any>>({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Select All Header */}
|
{/* Select All Header */}
|
||||||
{selectable && displayData.length > 0 && (
|
{selectable && (
|
||||||
<div className={styles.listHeader}>
|
<div className={styles.listHeader}>
|
||||||
<input
|
{displayData.length > 0 && (
|
||||||
type="checkbox"
|
<>
|
||||||
checked={(() => {
|
{hasSelectedItems && (onDeleteMultiple || onDelete) ? (
|
||||||
const selectableIndices = displayData
|
// Show delete button when items are selected
|
||||||
.map((row, index) => ({ row, index }))
|
<div className={styles.headerDeleteButtonContainer}>
|
||||||
.filter(({ row }) => !isItemSelectable || isItemSelectable(row))
|
{isConfirmingDelete ? (
|
||||||
.map(({ index }) => index);
|
<div className={actionButtonStyles.deleteConfirmButtons}>
|
||||||
return selectedItems.size === selectableIndices.length && selectableIndices.length > 0;
|
<button
|
||||||
})()}
|
onClick={handleConfirmDelete}
|
||||||
onChange={handleSelectAll}
|
className={`${actionButtonStyles.actionButton} ${actionButtonStyles.confirmButton}`}
|
||||||
title={t('formgen.select.all', 'Select all items')}
|
title={t('formgen.delete.confirm', 'Confirm delete')}
|
||||||
className={styles.selectAllCheckbox}
|
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 && (
|
{title && (
|
||||||
<h3 className={styles.listTitle}>{title}</h3>
|
<h3 className={styles.listTitle}>{title}</h3>
|
||||||
)}
|
)}
|
||||||
{title && data.length > 0 && (
|
{title && data.length > 0 && (
|
||||||
<span className={styles.listCount}>({data.length})</span>
|
<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 && (
|
{headerButton && (
|
||||||
<div className={styles.headerButtonWrapper}>
|
<div className={styles.headerButtonWrapper}>
|
||||||
{headerButton}
|
{headerButton}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{sortable && (
|
{sortable && displayData.length > 0 && (
|
||||||
<div className={styles.sortControls}>
|
<div className={styles.sortControls}>
|
||||||
{detectedFields.map(field => (
|
{detectedFields.map(field => (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { GenericPageData, PageButton, PageContent, resolveLanguageText, SettingsFieldConfig, SettingsSectionConfig, GenericDataHook } from './pageInterface';
|
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 { FormGeneratorForm, AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { Button, UploadButton, CreateButton, TextField, Messages, ChatMessage, LogMessage, DropdownSelect, Log, WorkflowStatus, Tabs } from '../../components/UiComponents';
|
import { Button, UploadButton, CreateButton, TextField, Messages, ChatMessage, LogMessage, DropdownSelect, Log, WorkflowStatus, Tabs } from '../../components/UiComponents';
|
||||||
import { Popup } from '../../components/UiComponents/Popup';
|
import { Popup } from '../../components/UiComponents/Popup';
|
||||||
|
|
@ -10,6 +10,7 @@ import { DragDropOverlay } from '../../components/UiComponents/DragDropOverlay';
|
||||||
import { useLanguage } from '../../providers/language/LanguageContext';
|
import { useLanguage } from '../../providers/language/LanguageContext';
|
||||||
import { usePermissions } from '../../hooks/usePermissions';
|
import { usePermissions } from '../../hooks/usePermissions';
|
||||||
import { FiPaperclip } from 'react-icons/fi';
|
import { FiPaperclip } from 'react-icons/fi';
|
||||||
|
import { IoMdAdd } from 'react-icons/io';
|
||||||
import type { WorkflowFile } from '../../hooks/playground/useDashboardInputForm';
|
import type { WorkflowFile } from '../../hooks/playground/useDashboardInputForm';
|
||||||
import styles from '../../styles/pages.module.css';
|
import styles from '../../styles/pages.module.css';
|
||||||
|
|
||||||
|
|
@ -172,9 +173,11 @@ const TabsRenderer: React.FC<{
|
||||||
label: resolveLanguageText(tab.label, t),
|
label: resolveLanguageText(tab.label, t),
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
{tab.content.map((nestedContent, index) =>
|
{tab.content.map((nestedContent, index) => (
|
||||||
renderContent(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' }}>
|
<div style={{ flexShrink: 0, display: 'flex', gap: '8px' }}>
|
||||||
<UploadButton
|
<UploadButton
|
||||||
onUpload={hookData.handleFileUpload ? async (file: File) => {
|
onUpload={hookData.handleFileUpload ? async (file: File) => {
|
||||||
const result = await hookData.handleFileUpload!(file);
|
await hookData.handleFileUpload!(file);
|
||||||
// Error handling is done in the hook
|
// Error handling is done in the hook
|
||||||
} : async () => {}}
|
} : async () => {}}
|
||||||
disabled={hookData.isSubmitting || false}
|
disabled={hookData.isSubmitting || false}
|
||||||
|
|
@ -1774,8 +1777,8 @@ const PageRenderer: React.FC<PageRendererProps> = ({
|
||||||
const gap = columnsConfig.gap || '1rem';
|
const gap = columnsConfig.gap || '1rem';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={content.id}
|
key={content.id}
|
||||||
className={styles.columnsContainer}
|
className={styles.columnsContainer}
|
||||||
style={{
|
style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
|
|
@ -1785,15 +1788,152 @@ const PageRenderer: React.FC<PageRendererProps> = ({
|
||||||
>
|
>
|
||||||
{columnsConfig.columns.map((column, colIndex) => (
|
{columnsConfig.columns.map((column, colIndex) => (
|
||||||
<div key={column.id} className={styles.column}>
|
<div key={column.id} className={styles.column}>
|
||||||
{column.content.map((nestedContent, index) =>
|
{column.content.map((nestedContent, index) => (
|
||||||
renderContent(nestedContent, `${colIndex}-${index}`)
|
<React.Fragment key={nestedContent.id || `${colIndex}-${index}`}>
|
||||||
)}
|
{renderContent(nestedContent)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const PekMapView: React.FC = () => {
|
||||||
const adjacentSet = new Map<string, any>();
|
const adjacentSet = new Map<string, any>();
|
||||||
selectedParcels.forEach(parcel => {
|
selectedParcels.forEach(parcel => {
|
||||||
if (parcel.adjacent_parcels) {
|
if (parcel.adjacent_parcels) {
|
||||||
parcel.adjacent_parcels.forEach(adj => {
|
parcel.adjacent_parcels.forEach((adj: { id: string }) => {
|
||||||
if (!adjacentSet.has(adj.id)) {
|
if (!adjacentSet.has(adj.id)) {
|
||||||
adjacentSet.set(adj.id, adj);
|
adjacentSet.set(adj.id, adj);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export interface SettingsConfig {
|
||||||
// Content section for paragraphs
|
// Content section for paragraphs
|
||||||
export interface PageContent {
|
export interface PageContent {
|
||||||
id: string;
|
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
|
content?: string | LanguageText; // Optional for dividers
|
||||||
level?: number; // For headings (1-6)
|
level?: number; // For headings (1-6)
|
||||||
items?: (string | LanguageText)[]; // For lists
|
items?: (string | LanguageText)[]; // For lists
|
||||||
|
|
@ -150,6 +150,10 @@ export interface PageContent {
|
||||||
logConfig?: {
|
logConfig?: {
|
||||||
emptyMessage?: string | LanguageText;
|
emptyMessage?: string | LanguageText;
|
||||||
};
|
};
|
||||||
|
// Chat history-specific properties
|
||||||
|
chatHistoryConfig?: {
|
||||||
|
emptyMessage?: string | LanguageText;
|
||||||
|
};
|
||||||
// Tabs-specific properties
|
// Tabs-specific properties
|
||||||
tabsConfig?: {
|
tabsConfig?: {
|
||||||
tabs: Array<{
|
tabs: Array<{
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@ export function useChatbot() {
|
||||||
processedLogsRef.current.clear(); // Clear processed logs tracking
|
processedLogsRef.current.clear(); // Clear processed logs tracking
|
||||||
|
|
||||||
// Reset thinking message refs
|
// Reset thinking message refs
|
||||||
const thinkingId = thinkingMessageIdRef.current;
|
|
||||||
thinkingMessageIdRef.current = null;
|
thinkingMessageIdRef.current = null;
|
||||||
thinkingLogsRef.current = [];
|
thinkingLogsRef.current = [];
|
||||||
|
|
||||||
|
|
@ -189,13 +188,8 @@ export function useChatbot() {
|
||||||
return;
|
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)
|
// ALWAYS clear thinking message when ANY message arrives (not just assistant)
|
||||||
// The thinking message should disappear when the real message comes
|
// The thinking message should disappear when the real message comes
|
||||||
const thinkingId = thinkingMessageIdRef.current;
|
|
||||||
|
|
||||||
// Check if we've already processed this message
|
// Check if we've already processed this message
|
||||||
const messageId = messageData.id;
|
const messageId = messageData.id;
|
||||||
|
|
@ -367,7 +361,7 @@ export function useChatbot() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle file upload
|
// 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);
|
setUploadError(null);
|
||||||
setUploadingFile(true);
|
setUploadingFile(true);
|
||||||
|
|
||||||
|
|
@ -417,7 +411,7 @@ export function useChatbot() {
|
||||||
console.error('File upload failed:', err);
|
console.error('File upload failed:', err);
|
||||||
const errorMessage = err.message || 'Failed to upload file';
|
const errorMessage = err.message || 'Failed to upload file';
|
||||||
setUploadError(errorMessage);
|
setUploadError(errorMessage);
|
||||||
return { success: false, error: errorMessage };
|
return { success: false, data: null };
|
||||||
} finally {
|
} finally {
|
||||||
setUploadingFile(false);
|
setUploadingFile(false);
|
||||||
}
|
}
|
||||||
|
|
@ -517,9 +511,6 @@ export function useChatbot() {
|
||||||
|
|
||||||
console.log('[handleSubmit] Final requestBody:', JSON.stringify(requestBody, null, 2));
|
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
|
// Clear thinking message when starting a new request
|
||||||
clearThinkingMessage();
|
clearThinkingMessage();
|
||||||
processedLogsRef.current.clear(); // Clear processed logs for new request
|
processedLogsRef.current.clear(); // Clear processed logs for new request
|
||||||
|
|
@ -553,7 +544,6 @@ export function useChatbot() {
|
||||||
// New workflow created - select it automatically
|
// New workflow created - select it automatically
|
||||||
setWorkflowId(messageData.workflowId);
|
setWorkflowId(messageData.workflowId);
|
||||||
setSelectedThreadId(messageData.workflowId);
|
setSelectedThreadId(messageData.workflowId);
|
||||||
workflowCreated = true;
|
|
||||||
} else {
|
} else {
|
||||||
// Existing workflow - ensure it's selected
|
// Existing workflow - ensure it's selected
|
||||||
setSelectedThreadId(messageData.workflowId);
|
setSelectedThreadId(messageData.workflowId);
|
||||||
|
|
|
||||||
|
|
@ -527,7 +527,7 @@ export function createProjectsTableHook(): () => GenericDataHook {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update project
|
// 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 {
|
try {
|
||||||
setEditingProjects(prev => new Set(prev).add(id));
|
setEditingProjects(prev => new Set(prev).add(id));
|
||||||
|
|
||||||
|
|
@ -999,7 +999,7 @@ export function createParzellenTableHook(): () => GenericDataHook {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update parzelle
|
// 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 {
|
try {
|
||||||
setEditingParzellen(prev => new Set(prev).add(id));
|
setEditingParzellen(prev => new Set(prev).add(id));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -458,6 +458,10 @@
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatHistoryNewChatButton {
|
||||||
|
border-radius: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.chatHistoryEmpty {
|
.chatHistoryEmpty {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue