667 lines
26 KiB
TypeScript
667 lines
26 KiB
TypeScript
/**
|
||
* AdminFeatureAccessPage
|
||
*
|
||
* Admin page for managing feature instances within mandates.
|
||
* Allows creating, viewing, and managing feature instances.
|
||
*/
|
||
|
||
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||
import { useFeatureAccess, type FeatureInstance } from '../../hooks/useFeatureAccess';
|
||
import { useUserMandates, type Mandate } from '../../hooks/useUserMandates';
|
||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||
import { FaPlus, FaSync, FaCube, FaBuilding, FaCogs, FaEdit } from 'react-icons/fa';
|
||
import { useToast } from '../../contexts/ToastContext';
|
||
import api from '../../api';
|
||
import { ChatbotConfigSection } from './ChatbotConfigSection';
|
||
import { DropdownSelect } from '../../components/UiComponents/DropdownSelect';
|
||
import { TextField } from '../../components/UiComponents/TextField';
|
||
import styles from './Admin.module.css';
|
||
|
||
export const AdminFeatureAccessPage: React.FC = () => {
|
||
const {
|
||
features,
|
||
instances,
|
||
instancesPagination,
|
||
loading,
|
||
error,
|
||
fetchFeatures,
|
||
fetchInstances,
|
||
createInstance,
|
||
updateInstance,
|
||
deleteInstance,
|
||
syncInstanceRoles,
|
||
} = useFeatureAccess();
|
||
|
||
const { fetchMandates } = useUserMandates();
|
||
const { showSuccess, showError } = useToast();
|
||
|
||
// State
|
||
const [mandates, setMandates] = useState<Mandate[]>([]);
|
||
const [selectedMandateId, setSelectedMandateId] = useState<string>('');
|
||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||
const [showEditModal, setShowEditModal] = useState(false);
|
||
const [editingInstance, setEditingInstance] = useState<FeatureInstance | null>(null);
|
||
const [, setIsSubmitting] = useState(false);
|
||
const [syncingInstance, setSyncingInstance] = useState<string | null>(null);
|
||
const [backendAttributes, setBackendAttributes] = useState<AttributeDefinition[]>([]);
|
||
|
||
// Chatbot configuration state
|
||
const [createFeatureCode, setCreateFeatureCode] = useState<string>('');
|
||
const [createLabel, setCreateLabel] = useState<string>(''); // Label field value
|
||
const [chatbotConnectors, setChatbotConnectors] = useState<string[]>(['preprocessor']); // Array for multiselect (database connectors only)
|
||
const [chatbotSystemPrompt, setChatbotSystemPrompt] = useState<string>('');
|
||
const [chatbotEnableWebResearch, setChatbotEnableWebResearch] = useState<boolean>(true); // Enable Tavily web research
|
||
|
||
// Ref to track form data for featureCode detection
|
||
const formDataRef = useRef<Record<string, any>>({});
|
||
|
||
// Load features, mandates, and attributes on mount
|
||
useEffect(() => {
|
||
fetchFeatures();
|
||
fetchMandates().then(setMandates);
|
||
// Fetch FeatureInstance attributes from backend
|
||
api.get('/api/attributes/FeatureInstance').then(response => {
|
||
const attrs = response.data?.attributes || response.data || [];
|
||
setBackendAttributes(Array.isArray(attrs) ? attrs : []);
|
||
}).catch(() => setBackendAttributes([]));
|
||
}, [fetchFeatures, fetchMandates]);
|
||
|
||
// Load instances when mandate changes
|
||
useEffect(() => {
|
||
if (selectedMandateId) {
|
||
fetchInstances(selectedMandateId);
|
||
}
|
||
}, [selectedMandateId, fetchInstances]);
|
||
|
||
// Table columns
|
||
const columns = useMemo(() => [
|
||
{ key: 'label', label: 'Name', type: 'string' as const, sortable: true, filterable: true, searchable: true, width: 200 },
|
||
{ key: 'featureCode', label: 'Feature', type: 'string' as const, sortable: true, filterable: true, width: 150,
|
||
render: (value: string) => {
|
||
const feature = features.find(f => f.code === value);
|
||
if (feature) {
|
||
const label = typeof feature.label === 'object'
|
||
? (feature.label.de || feature.label.en || value)
|
||
: feature.label;
|
||
return label;
|
||
}
|
||
return value;
|
||
}
|
||
},
|
||
{ key: 'enabled', label: 'Aktiv', type: 'boolean' as const, sortable: true, filterable: true, width: 80 },
|
||
], [features]);
|
||
|
||
// Form attributes from backend - merge with dynamic feature options
|
||
// Exclude featureCode, config, and label since we handle them separately
|
||
const createFields: AttributeDefinition[] = useMemo(() => {
|
||
const excludedFields = ['id', 'mandateId', 'featureCode', 'config', 'label']; // Exclude featureCode, config, and label - handled separately
|
||
|
||
return backendAttributes
|
||
.filter(attr => !excludedFields.includes(attr.name))
|
||
.map(attr => ({
|
||
...attr,
|
||
editable: attr.name === 'enabled' ? true : attr.editable,
|
||
})) as AttributeDefinition[];
|
||
}, [backendAttributes]);
|
||
|
||
// Handle create instance
|
||
const handleCreateInstance = async (data: { featureCode: string; enabled?: boolean; copyTemplateRoles?: boolean }) => {
|
||
if (!selectedMandateId) return;
|
||
setIsSubmitting(true);
|
||
try {
|
||
// Validate label
|
||
if (!createLabel || createLabel.trim() === '') {
|
||
showError('Fehler', 'Label ist erforderlich.');
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
|
||
// Build config for chatbot instances
|
||
let config: Record<string, any> | undefined = undefined;
|
||
if (createFeatureCode === 'chatbot') {
|
||
// Validate required fields
|
||
if (!chatbotSystemPrompt || chatbotSystemPrompt.trim() === '') {
|
||
showError('Fehler', 'System Prompt ist erforderlich für Chatbot-Instanzen.');
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
if (chatbotConnectors.length === 0) {
|
||
showError('Fehler', 'Mindestens ein Connector muss ausgewählt werden.');
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
|
||
// Use first connector as primary type (for backward compatibility)
|
||
// Store all connectors in types array
|
||
const primaryConnector = chatbotConnectors.length > 0 ? chatbotConnectors[0] : 'preprocessor';
|
||
config = {
|
||
connector: {
|
||
types: chatbotConnectors.length > 0 ? chatbotConnectors : ['preprocessor'], // Array of selected connectors
|
||
type: primaryConnector, // Primary connector (for backward compatibility)
|
||
customConnectorClass: null
|
||
},
|
||
prompts: {
|
||
useCustomPrompts: true, // Always true since system prompt is required
|
||
customAnalysisPrompt: chatbotSystemPrompt,
|
||
customFinalAnswerPrompt: chatbotSystemPrompt
|
||
},
|
||
behavior: {
|
||
maxQueries: 5,
|
||
enableWebResearch: chatbotEnableWebResearch,
|
||
enableRetryOnEmpty: true,
|
||
maxRetryAttempts: 2
|
||
}
|
||
};
|
||
}
|
||
|
||
const result = await createInstance(selectedMandateId, {
|
||
featureCode: createFeatureCode,
|
||
label: createLabel,
|
||
enabled: data.enabled !== false,
|
||
copyTemplateRoles: data.copyTemplateRoles !== false,
|
||
config: config
|
||
});
|
||
if (result.success) {
|
||
setShowCreateModal(false);
|
||
setCreateFeatureCode('');
|
||
setCreateLabel('');
|
||
formDataRef.current = {};
|
||
setChatbotConnectors(['preprocessor']);
|
||
setChatbotSystemPrompt('');
|
||
setChatbotEnableWebResearch(true);
|
||
fetchInstances(selectedMandateId);
|
||
showSuccess('Feature-Instanz erstellt', `Die Instanz "${createLabel}" wurde erfolgreich erstellt.`);
|
||
} else {
|
||
showError('Fehler', result.error || 'Fehler beim Erstellen der Feature-Instanz');
|
||
}
|
||
} finally {
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
// Wrapper for form submission that includes featureCode from selector
|
||
const handleFormSubmit = (data: Record<string, any>) => {
|
||
// Use label from state and featureCode from selector
|
||
handleCreateInstance({
|
||
featureCode: createFeatureCode,
|
||
...(data as { enabled?: boolean; copyTemplateRoles?: boolean })
|
||
});
|
||
};
|
||
|
||
// Handle edit click
|
||
const handleEditClick = (instance: FeatureInstance) => {
|
||
setEditingInstance(instance);
|
||
// Load chatbot config if it's a chatbot instance
|
||
if (instance.featureCode === 'chatbot' && instance.config) {
|
||
const config = instance.config as any;
|
||
// Support both new array format and legacy single type format
|
||
// Filter out 'websearch' if it exists (legacy)
|
||
const connectorTypes = (config?.connector?.types || (config?.connector?.type ? [config.connector.type] : ['preprocessor']))
|
||
.filter((c: string) => c !== 'websearch'); // Remove websearch from connectors
|
||
setChatbotConnectors(connectorTypes.length > 0 ? connectorTypes : ['preprocessor']);
|
||
setChatbotSystemPrompt(config?.prompts?.customAnalysisPrompt || config?.prompts?.customFinalAnswerPrompt || '');
|
||
setChatbotEnableWebResearch(config?.behavior?.enableWebResearch !== false); // Default to true if not set
|
||
} else {
|
||
setChatbotConnectors(['preprocessor']);
|
||
setChatbotSystemPrompt('');
|
||
setChatbotEnableWebResearch(true);
|
||
}
|
||
setShowEditModal(true);
|
||
};
|
||
|
||
// Handle update instance
|
||
const handleUpdateInstance = async (data: { label: string; enabled?: boolean }) => {
|
||
if (!selectedMandateId || !editingInstance) return;
|
||
setIsSubmitting(true);
|
||
try {
|
||
// Build config for chatbot instances
|
||
let config: Record<string, any> | undefined = undefined;
|
||
if (editingInstance.featureCode === 'chatbot') {
|
||
// Validate required fields
|
||
if (!chatbotSystemPrompt || chatbotSystemPrompt.trim() === '') {
|
||
showError('Fehler', 'System Prompt ist erforderlich für Chatbot-Instanzen.');
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
if (chatbotConnectors.length === 0) {
|
||
showError('Fehler', 'Mindestens ein Connector muss ausgewählt werden.');
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
|
||
// Merge with existing config if it exists
|
||
const existingConfig = editingInstance.config as any || {};
|
||
// Use first connector as primary type (for backward compatibility)
|
||
const primaryConnector = chatbotConnectors.length > 0 ? chatbotConnectors[0] : 'preprocessor';
|
||
config = {
|
||
...existingConfig,
|
||
connector: {
|
||
types: chatbotConnectors.length > 0 ? chatbotConnectors : ['preprocessor'], // Array of selected connectors
|
||
type: primaryConnector, // Primary connector (for backward compatibility)
|
||
customConnectorClass: existingConfig.connector?.customConnectorClass || null
|
||
},
|
||
prompts: {
|
||
useCustomPrompts: true, // Always true since system prompt is required
|
||
customAnalysisPrompt: chatbotSystemPrompt,
|
||
customFinalAnswerPrompt: chatbotSystemPrompt
|
||
},
|
||
behavior: {
|
||
...(existingConfig.behavior || {}),
|
||
maxQueries: existingConfig.behavior?.maxQueries || 5,
|
||
enableWebResearch: chatbotEnableWebResearch,
|
||
enableRetryOnEmpty: existingConfig.behavior?.enableRetryOnEmpty !== false,
|
||
maxRetryAttempts: existingConfig.behavior?.maxRetryAttempts || 2
|
||
}
|
||
};
|
||
}
|
||
|
||
const result = await updateInstance(selectedMandateId, editingInstance.id, {
|
||
label: data.label,
|
||
enabled: data.enabled,
|
||
config: config
|
||
});
|
||
if (result.success) {
|
||
setShowEditModal(false);
|
||
setEditingInstance(null);
|
||
setChatbotConnectors(['preprocessor']);
|
||
setChatbotSystemPrompt('');
|
||
fetchInstances(selectedMandateId);
|
||
showSuccess('Feature-Instanz aktualisiert', `Die Instanz "${data.label}" wurde erfolgreich aktualisiert.`);
|
||
} else {
|
||
showError('Fehler', result.error || 'Fehler beim Aktualisieren der Feature-Instanz');
|
||
}
|
||
} finally {
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
// Handle delete instance - called by DeleteActionButton with instanceId
|
||
const handleDeleteInstance = async (instanceId: string): Promise<boolean> => {
|
||
if (!selectedMandateId) return false;
|
||
const result = await deleteInstance(selectedMandateId, instanceId);
|
||
if (result.success) {
|
||
showSuccess('Instanz gelöscht', 'Die Feature-Instanz wurde gelöscht.');
|
||
return true;
|
||
} else {
|
||
showError('Fehler', result.error || 'Fehler beim Löschen der Feature-Instanz');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// Handle sync roles
|
||
const handleSyncRoles = async (instance: FeatureInstance) => {
|
||
if (!selectedMandateId) return;
|
||
setSyncingInstance(instance.id);
|
||
try {
|
||
const result = await syncInstanceRoles(selectedMandateId, instance.id, true);
|
||
if (result.success && result.data) {
|
||
showSuccess(
|
||
'Rollen synchronisiert',
|
||
`Hinzugefügt: ${result.data.added}\nEntfernt: ${result.data.removed}\nUnverändert: ${result.data.unchanged}`
|
||
);
|
||
} else {
|
||
showError('Synchronisierung fehlgeschlagen', result.error || 'Fehler beim Synchronisieren der Rollen');
|
||
}
|
||
} finally {
|
||
setSyncingInstance(null);
|
||
}
|
||
};
|
||
|
||
// Get mandate name
|
||
const getMandateName = (mandate: Mandate) => {
|
||
if (mandate.label) return mandate.label;
|
||
if (typeof mandate.name === 'object') {
|
||
return mandate.name.de || mandate.name.en || Object.values(mandate.name)[0] || mandate.id;
|
||
}
|
||
return mandate.name || mandate.id;
|
||
};
|
||
|
||
// Get feature label
|
||
const getFeatureLabel = (code: string) => {
|
||
const feature = features.find(f => f.code === code);
|
||
if (feature) {
|
||
return typeof feature.label === 'object'
|
||
? (feature.label.de || feature.label.en || code)
|
||
: (feature.label || code);
|
||
}
|
||
return code;
|
||
};
|
||
|
||
if (error && !selectedMandateId) {
|
||
return (
|
||
<div className={styles.adminPage}>
|
||
<div className={styles.errorContainer}>
|
||
<span className={styles.errorIcon}>⚠️</span>
|
||
<p className={styles.errorMessage}>Fehler: {error}</p>
|
||
<button className={styles.secondaryButton} onClick={() => fetchFeatures()}>
|
||
<FaSync /> Erneut versuchen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className={styles.adminPage}>
|
||
<div className={styles.pageHeader}>
|
||
<div>
|
||
<h1 className={styles.pageTitle}>Feature-Instanzen</h1>
|
||
<p className={styles.pageSubtitle}>Verwalten Sie Feature-Instanzen für jeden Mandanten</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Mandate Selector */}
|
||
<div className={styles.filterSection}>
|
||
<div className={styles.filterGroup}>
|
||
<label className={styles.filterLabel}>
|
||
<FaBuilding style={{ marginRight: 8 }} />
|
||
Mandant auswählen:
|
||
</label>
|
||
<select
|
||
className={styles.filterSelect}
|
||
value={selectedMandateId}
|
||
onChange={(e) => setSelectedMandateId(e.target.value)}
|
||
>
|
||
<option value="">-- Mandant wählen --</option>
|
||
{mandates.map(m => (
|
||
<option key={m.id} value={m.id}>
|
||
{getMandateName(m)}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
{selectedMandateId && (
|
||
<div className={styles.headerActions}>
|
||
<button
|
||
className={styles.secondaryButton}
|
||
onClick={() => fetchInstances(selectedMandateId)}
|
||
disabled={loading}
|
||
>
|
||
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
|
||
</button>
|
||
<button
|
||
className={styles.primaryButton}
|
||
onClick={() => setShowCreateModal(true)}
|
||
disabled={features.length === 0}
|
||
>
|
||
<FaPlus /> Neue Instanz
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Available Features Info */}
|
||
{features.length > 0 && (
|
||
<div className={styles.infoBox}>
|
||
<FaCube style={{ marginRight: 8 }} />
|
||
<span>Verfügbare Features: </span>
|
||
{features.map((f, i) => (
|
||
<span key={f.code}>
|
||
{i > 0 && ', '}
|
||
<strong>{getFeatureLabel(f.code)}</strong>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Content */}
|
||
{!selectedMandateId ? (
|
||
<div className={styles.emptyState}>
|
||
<FaBuilding className={styles.emptyIcon} />
|
||
<h3 className={styles.emptyTitle}>Kein Mandant ausgewählt</h3>
|
||
<p className={styles.emptyDescription}>
|
||
Wählen Sie einen Mandanten aus, um dessen Feature-Instanzen zu verwalten.
|
||
</p>
|
||
</div>
|
||
) : loading && instances.length === 0 ? (
|
||
<div className={styles.loadingContainer}>
|
||
<div className={styles.spinner} />
|
||
<span>Lade Feature-Instanzen...</span>
|
||
</div>
|
||
) : instances.length === 0 ? (
|
||
<div className={styles.emptyState}>
|
||
<FaCube className={styles.emptyIcon} />
|
||
<h3 className={styles.emptyTitle}>Keine Feature-Instanzen</h3>
|
||
<p className={styles.emptyDescription}>
|
||
Für diesen Mandanten wurden noch keine Feature-Instanzen erstellt.
|
||
</p>
|
||
<button
|
||
className={styles.primaryButton}
|
||
onClick={() => setShowCreateModal(true)}
|
||
disabled={features.length === 0}
|
||
>
|
||
<FaPlus /> Erste Instanz erstellen
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className={styles.tableContainer}>
|
||
<FormGeneratorTable
|
||
data={instances}
|
||
columns={columns}
|
||
apiEndpoint="/api/features/instances"
|
||
loading={loading}
|
||
pagination={true}
|
||
pageSize={25}
|
||
searchable={true}
|
||
filterable={true}
|
||
sortable={true}
|
||
selectable={false}
|
||
actionButtons={[
|
||
{
|
||
type: 'delete' as const,
|
||
title: 'Instanz löschen',
|
||
}
|
||
]}
|
||
customActions={[
|
||
{
|
||
id: 'edit',
|
||
icon: <FaEdit />,
|
||
onClick: handleEditClick,
|
||
title: 'Instanz bearbeiten',
|
||
},
|
||
{
|
||
id: 'syncRoles',
|
||
icon: <FaCogs />,
|
||
onClick: handleSyncRoles,
|
||
title: 'Rollen synchronisieren',
|
||
loading: (row: FeatureInstance) => syncingInstance === row.id,
|
||
disabled: (row: FeatureInstance) => !row.enabled,
|
||
}
|
||
]}
|
||
hookData={{
|
||
refetch: fetchInstances,
|
||
pagination: instancesPagination,
|
||
handleDelete: handleDeleteInstance,
|
||
}}
|
||
emptyMessage="Keine Feature-Instanzen gefunden"
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Create Instance Modal */}
|
||
{showCreateModal && (
|
||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||
<div className={styles.modalHeader}>
|
||
<h2 className={styles.modalTitle}>Neue Feature-Instanz erstellen</h2>
|
||
<button
|
||
className={styles.modalClose}
|
||
onClick={() => setShowCreateModal(false)}
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<div className={styles.modalContent}>
|
||
{features.length === 0 ? (
|
||
<p>Keine Features verfügbar. Bitte wenden Sie sich an den System-Administrator.</p>
|
||
) : createFields.length === 0 ? (
|
||
<div className={styles.loadingContainer}>
|
||
<div className={styles.spinner} />
|
||
<span>Lade Formular...</span>
|
||
</div>
|
||
) : (
|
||
<div>
|
||
{/* Feature Code Selector - Required for chatbot config */}
|
||
<div className={styles.configField} style={{ marginBottom: '1.5rem', paddingBottom: '1rem', borderBottom: '1px solid var(--border-color)' }}>
|
||
<label className={styles.configLabel} style={{ fontWeight: 600 }}>
|
||
Feature auswählen: <span style={{ color: 'var(--error-color)' }}>*</span>
|
||
</label>
|
||
<DropdownSelect
|
||
items={features.map(f => ({
|
||
id: f.code,
|
||
label: typeof f.label === 'object'
|
||
? (f.label.de || f.label.en || f.code)
|
||
: (f.label || f.code),
|
||
value: f.code
|
||
}))}
|
||
selectedItemId={createFeatureCode}
|
||
onSelect={(item) => {
|
||
const selectedCode = item?.value || '';
|
||
setCreateFeatureCode(selectedCode);
|
||
// Reset chatbot config when switching
|
||
setChatbotConnectors(['preprocessor']);
|
||
setChatbotSystemPrompt('');
|
||
setChatbotEnableWebResearch(true);
|
||
}}
|
||
placeholder="Feature auswählen (erforderlich)"
|
||
className={styles.configSelect}
|
||
/>
|
||
{!createFeatureCode && (
|
||
<p style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginTop: '0.5rem' }}>
|
||
Bitte wählen Sie ein Feature aus, um fortzufahren.
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Chatbot Configuration Title - Show when chatbot is selected */}
|
||
{createFeatureCode === 'chatbot' && (
|
||
<h3 className={styles.configSectionTitle} style={{ marginTop: '1.5rem', marginBottom: '1.5rem' }}>
|
||
Chatbot-Konfiguration
|
||
</h3>
|
||
)}
|
||
|
||
{/* Label Field - Always shown after title */}
|
||
{createFeatureCode && (
|
||
<div className={styles.configField} style={{ marginBottom: '1.5rem' }}>
|
||
<label className={styles.configLabel}>
|
||
Label: <span style={{ color: 'var(--error-color)' }}>*</span>
|
||
</label>
|
||
<TextField
|
||
type="text"
|
||
value={createLabel}
|
||
onChange={(value) => setCreateLabel(value)}
|
||
placeholder="Instanz-Bezeichnung eingeben..."
|
||
className={styles.configSelect}
|
||
size="md"
|
||
required={true}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Chatbot Configuration Section - Show when chatbot is selected */}
|
||
{createFeatureCode === 'chatbot' && (
|
||
<ChatbotConfigSection
|
||
connectors={chatbotConnectors}
|
||
systemPrompt={chatbotSystemPrompt}
|
||
enableWebResearch={chatbotEnableWebResearch}
|
||
onConnectorsChange={setChatbotConnectors}
|
||
onSystemPromptChange={setChatbotSystemPrompt}
|
||
onEnableWebResearchChange={setChatbotEnableWebResearch}
|
||
/>
|
||
)}
|
||
|
||
{/* Main Form - Only show if featureCode is selected */}
|
||
{createFeatureCode && (
|
||
<div style={{ marginTop: '1.5rem' }}>
|
||
<FormGeneratorForm
|
||
attributes={createFields}
|
||
mode="create"
|
||
onSubmit={handleFormSubmit}
|
||
onCancel={() => {
|
||
setShowCreateModal(false);
|
||
setCreateFeatureCode('');
|
||
setCreateLabel('');
|
||
formDataRef.current = {};
|
||
setChatbotConnectors(['preprocessor']);
|
||
setChatbotSystemPrompt('');
|
||
setChatbotEnableWebResearch(true);
|
||
}}
|
||
submitButtonText="Erstellen"
|
||
cancelButtonText="Abbrechen"
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Edit Instance Modal */}
|
||
{showEditModal && editingInstance && (
|
||
<div className={styles.modalOverlay} onClick={() => { setShowEditModal(false); setEditingInstance(null); }}>
|
||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||
<div className={styles.modalHeader}>
|
||
<h2 className={styles.modalTitle}>Feature-Instanz bearbeiten</h2>
|
||
<button
|
||
className={styles.modalClose}
|
||
onClick={() => { setShowEditModal(false); setEditingInstance(null); }}
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
<div className={styles.modalContent}>
|
||
<FormGeneratorForm
|
||
attributes={[
|
||
{
|
||
name: 'label',
|
||
type: 'string' as const,
|
||
label: 'Bezeichnung',
|
||
required: true,
|
||
editable: true,
|
||
},
|
||
{
|
||
name: 'enabled',
|
||
type: 'boolean' as const,
|
||
label: 'Aktiviert',
|
||
required: false,
|
||
editable: true,
|
||
}
|
||
]}
|
||
data={editingInstance}
|
||
mode="edit"
|
||
onSubmit={handleUpdateInstance}
|
||
onCancel={() => {
|
||
setShowEditModal(false);
|
||
setEditingInstance(null);
|
||
setChatbotConnectors(['preprocessor']);
|
||
setChatbotSystemPrompt('');
|
||
setChatbotEnableWebResearch(true);
|
||
}}
|
||
submitButtonText="Speichern"
|
||
cancelButtonText="Abbrechen"
|
||
/>
|
||
|
||
{/* Chatbot Configuration Section */}
|
||
{editingInstance?.featureCode === 'chatbot' && (
|
||
<ChatbotConfigSection
|
||
connectors={chatbotConnectors}
|
||
systemPrompt={chatbotSystemPrompt}
|
||
enableWebResearch={chatbotEnableWebResearch}
|
||
onConnectorsChange={setChatbotConnectors}
|
||
onSystemPromptChange={setChatbotSystemPrompt}
|
||
onEnableWebResearchChange={setChatbotEnableWebResearch}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AdminFeatureAccessPage;
|