admin pages streamlined
This commit is contained in:
parent
932bc3e11b
commit
ff1caba925
14 changed files with 82 additions and 354 deletions
32
src/App.tsx
32
src/App.tsx
|
|
@ -43,15 +43,9 @@ import { GDPRPage } from './pages/GDPR';
|
||||||
import { FeatureViewPage } from './pages/FeatureView';
|
import { FeatureViewPage } from './pages/FeatureView';
|
||||||
import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage } from './pages/admin';
|
import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage } from './pages/admin';
|
||||||
|
|
||||||
// Workflow Pages (global)
|
|
||||||
import { PlaygroundPage, WorkflowsPage, AutomationsPage, AutomationTemplatesPage } from './pages/workflows';
|
|
||||||
|
|
||||||
// Basedata Pages (global)
|
// Basedata Pages (global)
|
||||||
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
||||||
|
|
||||||
// Migrate Pages (temporary - to be migrated to feature instances)
|
|
||||||
import { PekPage, SpeechPage } from './pages/migrate';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// Load saved theme preference and set app name on app mount
|
// Load saved theme preference and set app name on app mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -107,16 +101,6 @@ function App() {
|
||||||
<Route path="settings" element={<SettingsPage />} />
|
<Route path="settings" element={<SettingsPage />} />
|
||||||
<Route path="gdpr" element={<GDPRPage />} />
|
<Route path="gdpr" element={<GDPRPage />} />
|
||||||
|
|
||||||
{/* ============================================== */}
|
|
||||||
{/* WORKFLOWS ROUTES (global) */}
|
|
||||||
{/* ============================================== */}
|
|
||||||
<Route path="workflows">
|
|
||||||
<Route path="playground" element={<PlaygroundPage />} />
|
|
||||||
<Route path="list" element={<WorkflowsPage />} />
|
|
||||||
<Route path="automations" element={<AutomationsPage />} />
|
|
||||||
<Route path="automation-templates" element={<AutomationTemplatesPage />} />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
{/* BASISDATEN ROUTES (global) */}
|
{/* BASISDATEN ROUTES (global) */}
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
|
|
@ -126,13 +110,6 @@ function App() {
|
||||||
<Route path="connections" element={<ConnectionsPage />} />
|
<Route path="connections" element={<ConnectionsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* ============================================== */}
|
|
||||||
{/* MIGRATE TO FEATURES (temporary) */}
|
|
||||||
{/* ============================================== */}
|
|
||||||
<Route path="chatbot" element={<Navigate to="/" replace />} />
|
|
||||||
<Route path="pek" element={<PekPage />} />
|
|
||||||
<Route path="speech" element={<SpeechPage />} />
|
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
{/* FEATURE-INSTANZ ROUTES */}
|
{/* FEATURE-INSTANZ ROUTES */}
|
||||||
{/* /mandates/:mandateId/:featureCode/:instanceId */}
|
{/* /mandates/:mandateId/:featureCode/:instanceId */}
|
||||||
|
|
@ -159,6 +136,15 @@ function App() {
|
||||||
<Route path="projects" element={<FeatureViewPage view="projects" />} />
|
<Route path="projects" element={<FeatureViewPage view="projects" />} />
|
||||||
<Route path="parcels" element={<FeatureViewPage view="parcels" />} />
|
<Route path="parcels" element={<FeatureViewPage view="parcels" />} />
|
||||||
|
|
||||||
|
{/* Chat Playground Feature Views */}
|
||||||
|
<Route path="playground" element={<FeatureViewPage view="playground" />} />
|
||||||
|
<Route path="workflows" element={<FeatureViewPage view="workflows" />} />
|
||||||
|
|
||||||
|
{/* Automation Feature Views */}
|
||||||
|
<Route path="definitions" element={<FeatureViewPage view="definitions" />} />
|
||||||
|
<Route path="templates" element={<FeatureViewPage view="templates" />} />
|
||||||
|
<Route path="logs" element={<FeatureViewPage view="logs" />} />
|
||||||
|
|
||||||
{/* Catch-all für unbekannte Sub-Pfade */}
|
{/* Catch-all für unbekannte Sub-Pfade */}
|
||||||
<Route path="*" element={<FeatureViewPage view="not-found" />} />
|
<Route path="*" element={<FeatureViewPage view="not-found" />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
||||||
|
|
@ -231,17 +231,18 @@ export async function fetchWorkflowLogs(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch unified chat data (messages, logs, stats, documents)
|
* Fetch unified chat data (messages, logs, stats, documents)
|
||||||
* Endpoint: GET /api/chat/playground/{workflowId}/chatData
|
* Endpoint: GET /api/chatplayground/{instanceId}/{workflowId}/chatData
|
||||||
* Query params: afterTimestamp (optional) - fetch only data created after this time
|
* Query params: afterTimestamp (optional) - fetch only data created after this time
|
||||||
*/
|
*/
|
||||||
export async function fetchChatData(
|
export async function fetchChatData(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
|
instanceId: string,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
afterTimestamp?: number
|
afterTimestamp?: number
|
||||||
): Promise<ChatDataResponse> {
|
): Promise<ChatDataResponse> {
|
||||||
const params = afterTimestamp ? { afterTimestamp: afterTimestamp.toString() } : undefined;
|
const params = afterTimestamp ? { afterTimestamp: afterTimestamp.toString() } : undefined;
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
url: `/api/chat/playground/${workflowId}/chatData`,
|
url: `/api/chatplayground/${instanceId}/${workflowId}/chatData`,
|
||||||
method: 'get' as const,
|
method: 'get' as const,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
|
|
@ -314,11 +315,12 @@ export async function fetchChatData(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new workflow or continue an existing one
|
* Start a new workflow or continue an existing one
|
||||||
* Endpoint: POST /api/chat/playground/start
|
* Endpoint: POST /api/chatplayground/{instanceId}/start
|
||||||
* Query params: workflowId (optional), workflowMode (default: "Actionplan")
|
* Query params: workflowId (optional), workflowMode (default: "Dynamic")
|
||||||
*/
|
*/
|
||||||
export async function startWorkflowApi(
|
export async function startWorkflowApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
|
instanceId: string,
|
||||||
workflowData: StartWorkflowRequest,
|
workflowData: StartWorkflowRequest,
|
||||||
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
||||||
): Promise<StartWorkflowResponse> {
|
): Promise<StartWorkflowResponse> {
|
||||||
|
|
@ -345,7 +347,7 @@ export async function startWorkflowApi(
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
url: '/api/chat/playground/start',
|
url: `/api/chatplayground/${instanceId}/start`,
|
||||||
method: 'post' as const,
|
method: 'post' as const,
|
||||||
data: requestBody,
|
data: requestBody,
|
||||||
params: params // Always include workflowMode
|
params: params // Always include workflowMode
|
||||||
|
|
@ -368,14 +370,15 @@ export async function startWorkflowApi(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop a running workflow
|
* Stop a running workflow
|
||||||
* Endpoint: POST /api/chat/playground/{workflowId}/stop
|
* Endpoint: POST /api/chatplayground/{instanceId}/{workflowId}/stop
|
||||||
*/
|
*/
|
||||||
export async function stopWorkflowApi(
|
export async function stopWorkflowApi(
|
||||||
request: ApiRequestFunction,
|
request: ApiRequestFunction,
|
||||||
|
instanceId: string,
|
||||||
workflowId: string
|
workflowId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await request({
|
await request({
|
||||||
url: `/api/chat/playground/${workflowId}/stop`,
|
url: `/api/chatplayground/${instanceId}/${workflowId}/stop`,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export interface WorkflowFile {
|
||||||
source?: 'user_uploaded' | 'ai_created';
|
source?: 'user_uploaded' | 'ai_created';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDashboardInputForm() {
|
export function useDashboardInputForm(instanceId: string) {
|
||||||
const [inputValue, setInputValue] = useState<string>('');
|
const [inputValue, setInputValue] = useState<string>('');
|
||||||
const [pendingFiles, setPendingFiles] = useState<WorkflowFile[]>([]);
|
const [pendingFiles, setPendingFiles] = useState<WorkflowFile[]>([]);
|
||||||
const [isFileAttachmentPopupOpen, setIsFileAttachmentPopupOpen] = useState(false);
|
const [isFileAttachmentPopupOpen, setIsFileAttachmentPopupOpen] = useState(false);
|
||||||
|
|
@ -54,7 +54,7 @@ export function useDashboardInputForm() {
|
||||||
resetWorkflow,
|
resetWorkflow,
|
||||||
selectWorkflow,
|
selectWorkflow,
|
||||||
setWorkflowStatusOptimistic
|
setWorkflowStatusOptimistic
|
||||||
} = useWorkflowLifecycle();
|
} = useWorkflowLifecycle(instanceId);
|
||||||
|
|
||||||
// Dashboard log tree hook
|
// Dashboard log tree hook
|
||||||
const {
|
const {
|
||||||
|
|
@ -824,7 +824,7 @@ export function useDashboardInputForm() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDashboardHook() {
|
export function createDashboardHook(instanceId: string) {
|
||||||
return () => useDashboardInputForm();
|
return () => useDashboardInputForm(instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ interface UnifiedChatDataItem {
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWorkflowLifecycle() {
|
export function useWorkflowLifecycle(instanceId: string) {
|
||||||
const [workflowId, setWorkflowId] = useState<string | null>(null);
|
const [workflowId, setWorkflowId] = useState<string | null>(null);
|
||||||
const [workflowStatus, setWorkflowStatus] = useState<string>('idle');
|
const [workflowStatus, setWorkflowStatus] = useState<string>('idle');
|
||||||
const [currentRound, setCurrentRound] = useState<number | undefined>(undefined);
|
const [currentRound, setCurrentRound] = useState<number | undefined>(undefined);
|
||||||
|
|
@ -341,7 +341,7 @@ export function useWorkflowLifecycle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch unified chat data
|
// Fetch unified chat data
|
||||||
const chatData = await fetchChatData(request, id, afterTimestamp);
|
const chatData = await fetchChatData(request, instanceId, id, afterTimestamp);
|
||||||
|
|
||||||
console.log('📊 Processed chat data:', {
|
console.log('📊 Processed chat data:', {
|
||||||
messagesCount: chatData.messages?.length || 0,
|
messagesCount: chatData.messages?.length || 0,
|
||||||
|
|
@ -419,7 +419,7 @@ export function useWorkflowLifecycle() {
|
||||||
// Reset lastRenderedTimestamp to fetch all historical data
|
// Reset lastRenderedTimestamp to fetch all historical data
|
||||||
lastRenderedTimestampRef.current = null;
|
lastRenderedTimestampRef.current = null;
|
||||||
try {
|
try {
|
||||||
const chatData = await fetchChatData(request, id, undefined);
|
const chatData = await fetchChatData(request, instanceId, id, undefined);
|
||||||
console.log('📥 loadWorkflowData: Fetched unified chat data:', {
|
console.log('📥 loadWorkflowData: Fetched unified chat data:', {
|
||||||
messagesCount: chatData.messages?.length || 0,
|
messagesCount: chatData.messages?.length || 0,
|
||||||
logsCount: chatData.logs?.length || 0
|
logsCount: chatData.logs?.length || 0
|
||||||
|
|
@ -514,7 +514,7 @@ export function useWorkflowLifecycle() {
|
||||||
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const result = await startWorkflow(workflowData, options);
|
const result = await startWorkflow(instanceId, workflowData, options);
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
const workflow = result.data as Workflow;
|
const workflow = result.data as Workflow;
|
||||||
|
|
@ -529,7 +529,7 @@ export function useWorkflowLifecycle() {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { success: false, error: error.message || 'Failed to start workflow' };
|
return { success: false, error: error.message || 'Failed to start workflow' };
|
||||||
}
|
}
|
||||||
}, [startWorkflow, updateWorkflowStatus]);
|
}, [instanceId, startWorkflow, updateWorkflowStatus]);
|
||||||
|
|
||||||
const handleStopWorkflow = useCallback(async () => {
|
const handleStopWorkflow = useCallback(async () => {
|
||||||
if (!workflowId) {
|
if (!workflowId) {
|
||||||
|
|
@ -537,7 +537,7 @@ export function useWorkflowLifecycle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await stopWorkflow(workflowId);
|
const result = await stopWorkflow(instanceId, workflowId);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
updateWorkflowStatus('stopped');
|
updateWorkflowStatus('stopped');
|
||||||
|
|
@ -548,7 +548,7 @@ export function useWorkflowLifecycle() {
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { success: false, error: error.message || 'Failed to stop workflow' };
|
return { success: false, error: error.message || 'Failed to stop workflow' };
|
||||||
}
|
}
|
||||||
}, [workflowId, stopWorkflow, updateWorkflowStatus]);
|
}, [instanceId, workflowId, stopWorkflow, updateWorkflowStatus]);
|
||||||
|
|
||||||
const resetWorkflow = useCallback(() => {
|
const resetWorkflow = useCallback(() => {
|
||||||
setWorkflowId(null);
|
setWorkflowId(null);
|
||||||
|
|
@ -597,7 +597,7 @@ export function useWorkflowLifecycle() {
|
||||||
// Always fetch unified chat data to get all messages and logs (regardless of status)
|
// Always fetch unified chat data to get all messages and logs (regardless of status)
|
||||||
// This ensures completed workflows also show their logs
|
// This ensures completed workflows also show their logs
|
||||||
try {
|
try {
|
||||||
const chatData = await fetchChatData(request, workflowIdToSelect, undefined);
|
const chatData = await fetchChatData(request, instanceId, workflowIdToSelect, undefined);
|
||||||
console.log('📥 selectWorkflow: Fetched unified chat data:', {
|
console.log('📥 selectWorkflow: Fetched unified chat data:', {
|
||||||
messagesCount: chatData.messages?.length || 0,
|
messagesCount: chatData.messages?.length || 0,
|
||||||
logsCount: chatData.logs?.length || 0,
|
logsCount: chatData.logs?.length || 0,
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ export function useUserMandates() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all available roles (global and mandate-specific)
|
* Fetch all available roles (global and mandate-specific, excluding feature-instance roles)
|
||||||
*/
|
*/
|
||||||
const fetchRoles = useCallback(async (mandateId?: string): Promise<Role[]> => {
|
const fetchRoles = useCallback(async (mandateId?: string): Promise<Role[]> => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -238,13 +238,15 @@ export function useUserMandates() {
|
||||||
roles = response.data;
|
roles = response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter to global roles and roles for this mandate
|
// Filter to global roles and mandate-specific roles only
|
||||||
|
// Exclude feature-instance roles (they have featureInstanceId set)
|
||||||
if (mandateId) {
|
if (mandateId) {
|
||||||
return roles.filter(r =>
|
return roles.filter(r =>
|
||||||
!r.mandateId || r.mandateId === mandateId
|
!r.featureInstanceId && (!r.mandateId || r.mandateId === mandateId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return roles;
|
// Without mandateId, return only global roles (no mandateId and no featureInstanceId)
|
||||||
|
return roles.filter(r => !r.mandateId && !r.featureInstanceId);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Error fetching roles:', err);
|
console.error('Error fetching roles:', err);
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -432,6 +432,7 @@ export function useWorkflowOperations() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const startWorkflow = async (
|
const startWorkflow = async (
|
||||||
|
instanceId: string,
|
||||||
workflowData: StartWorkflowRequest,
|
workflowData: StartWorkflowRequest,
|
||||||
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -439,7 +440,7 @@ export function useWorkflowOperations() {
|
||||||
setStartingWorkflow(true);
|
setStartingWorkflow(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await startWorkflowApi(request, workflowData, options);
|
const response = await startWorkflowApi(request, instanceId, workflowData, options);
|
||||||
return { success: true, data: response };
|
return { success: true, data: response };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error.message || 'Failed to start workflow';
|
const errorMessage = error.message || 'Failed to start workflow';
|
||||||
|
|
@ -450,12 +451,12 @@ export function useWorkflowOperations() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopWorkflow = async (workflowId: string) => {
|
const stopWorkflow = async (instanceId: string, workflowId: string) => {
|
||||||
setStopError(null);
|
setStopError(null);
|
||||||
setStoppingWorkflows(prev => new Set(prev).add(workflowId));
|
setStoppingWorkflows(prev => new Set(prev).add(workflowId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stopWorkflowApi(request, workflowId);
|
await stopWorkflowApi(request, instanceId, workflowId);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error.message || 'Failed to stop workflow';
|
const errorMessage = error.message || 'Failed to stop workflow';
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ import { ChatbotConversationsView } from './views/chatbot/ChatbotConversationsVi
|
||||||
// RealEstate Views
|
// RealEstate Views
|
||||||
import { RealEstatePekView, RealEstateProjectsView, RealEstateParcelsView, RealEstateInstanceRolesPlaceholder } from './views/realestate';
|
import { RealEstatePekView, RealEstateProjectsView, RealEstateParcelsView, RealEstateInstanceRolesPlaceholder } from './views/realestate';
|
||||||
|
|
||||||
|
// Chat Playground Views (reusing existing workflow pages)
|
||||||
|
import { PlaygroundPage, WorkflowsPage } from './workflows';
|
||||||
|
|
||||||
|
// Automation Views (reusing existing workflow pages)
|
||||||
|
import { AutomationsPage, AutomationTemplatesPage } from './workflows';
|
||||||
|
|
||||||
import styles from './FeatureView.module.css';
|
import styles from './FeatureView.module.css';
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -103,6 +109,15 @@ const VIEW_COMPONENTS: Record<string, Record<string, ViewComponent>> = {
|
||||||
parcels: RealEstateParcelsView,
|
parcels: RealEstateParcelsView,
|
||||||
'instance-roles': RealEstateInstanceRolesPlaceholder,
|
'instance-roles': RealEstateInstanceRolesPlaceholder,
|
||||||
},
|
},
|
||||||
|
chatplayground: {
|
||||||
|
playground: PlaygroundPage,
|
||||||
|
workflows: WorkflowsPage,
|
||||||
|
},
|
||||||
|
automation: {
|
||||||
|
definitions: AutomationsPage,
|
||||||
|
templates: AutomationTemplatesPage,
|
||||||
|
logs: () => <PlaceholderView title="Execution Logs" description="Automatisierungs-Ausführungsprotokolle" />,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
/* MigratePages.module.css - Styles for migrate-to-feature pages */
|
|
||||||
|
|
||||||
.page {
|
|
||||||
padding: 2rem;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
height: calc(100vh - 4rem);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-primary, #1a1a2e);
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
color: var(--color-text-secondary, #6b7280);
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.migrateTag {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: var(--color-warning-bg, #fef3c7);
|
|
||||||
color: var(--color-warning, #d97706);
|
|
||||||
font-size: 0.65rem;
|
|
||||||
font-weight: 700;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Placeholder for migrate pages */
|
|
||||||
.placeholder {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem;
|
|
||||||
background: var(--color-surface, #ffffff);
|
|
||||||
border: 2px dashed var(--color-border, #e5e7eb);
|
|
||||||
border-radius: 12px;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholderIcon {
|
|
||||||
font-size: 4rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder h2 {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
color: var(--color-text-primary, #1a1a2e);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder p {
|
|
||||||
color: var(--color-text-secondary, #6b7280);
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--color-text-tertiary, #9ca3af);
|
|
||||||
margin-top: 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chat container for ChatbotPage */
|
|
||||||
.chatContainer {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: var(--color-surface, #ffffff);
|
|
||||||
border: 1px solid var(--color-border, #e5e7eb);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messagesArea {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emptyChat {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: var(--color-text-secondary, #6b7280);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
max-width: 70%;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.user {
|
|
||||||
align-self: flex-end;
|
|
||||||
background: var(--color-primary, #4f46e5);
|
|
||||||
color: white;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.assistant {
|
|
||||||
align-self: flex-start;
|
|
||||||
background: var(--color-surface-secondary, #f3f4f6);
|
|
||||||
color: var(--color-text-primary, #1a1a2e);
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.system {
|
|
||||||
align-self: center;
|
|
||||||
background: var(--color-warning-bg, #fef3c7);
|
|
||||||
color: var(--color-warning, #d97706);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageContent {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageTime {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Typing indicator */
|
|
||||||
.typing {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing span {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: var(--color-text-secondary, #6b7280);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: typing 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing span:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing span:nth-child(3) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes typing {
|
|
||||||
0%, 100% {
|
|
||||||
opacity: 0.3;
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Input area */
|
|
||||||
.inputArea {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border-top: 1px solid var(--color-border, #e5e7eb);
|
|
||||||
background: var(--color-surface-secondary, #f9fafb);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatInput {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border: 1px solid var(--color-border, #e5e7eb);
|
|
||||||
border-radius: 24px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
background: var(--color-surface, #ffffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatInput:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-primary, #4f46e5);
|
|
||||||
box-shadow: 0 0 0 3px var(--color-primary-light, rgba(79, 70, 229, 0.1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButton {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
background: var(--color-primary, #4f46e5);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 24px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButton:hover:not(:disabled) {
|
|
||||||
background: var(--color-primary-dark, #4338ca);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sendButton:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
/**
|
|
||||||
* PekPage
|
|
||||||
*
|
|
||||||
* PEK (Projekt-Entwicklungs-Koordination) page - temporary global page.
|
|
||||||
* TODO: Migrate to feature instance.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './MigratePages.module.css';
|
|
||||||
|
|
||||||
export const PekPage: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div className={styles.page}>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<h1>PEK</h1>
|
|
||||||
<p className={styles.subtitle}>
|
|
||||||
<span className={styles.migrateTag}>MIGRATE TO FEATURE</span>
|
|
||||||
Projekt-Entwicklungs-Koordination
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main className={styles.content}>
|
|
||||||
<div className={styles.placeholder}>
|
|
||||||
<div className={styles.placeholderIcon}>📊</div>
|
|
||||||
<h2>PEK-Modul</h2>
|
|
||||||
<p>
|
|
||||||
Dieses Modul wird zu einer Feature-Instanz migriert.
|
|
||||||
</p>
|
|
||||||
<p className={styles.hint}>
|
|
||||||
Nach der Migration wird PEK als Feature pro Mandant verfügbar sein,
|
|
||||||
mit instanz-spezifischen Daten und Berechtigungen.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PekPage;
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
/**
|
|
||||||
* SpeechPage
|
|
||||||
*
|
|
||||||
* Speech recognition and transcription page - temporary global page.
|
|
||||||
* TODO: Migrate to feature instance.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './MigratePages.module.css';
|
|
||||||
|
|
||||||
export const SpeechPage: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div className={styles.page}>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<h1>Speech</h1>
|
|
||||||
<p className={styles.subtitle}>
|
|
||||||
<span className={styles.migrateTag}>MIGRATE TO FEATURE</span>
|
|
||||||
Spracherkennung und Transkription
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main className={styles.content}>
|
|
||||||
<div className={styles.placeholder}>
|
|
||||||
<div className={styles.placeholderIcon}>🎤</div>
|
|
||||||
<h2>Speech-Modul</h2>
|
|
||||||
<p>
|
|
||||||
Dieses Modul wird zu einer Feature-Instanz migriert.
|
|
||||||
</p>
|
|
||||||
<p className={styles.hint}>
|
|
||||||
Nach der Migration wird Speech als Feature pro Mandant verfügbar sein,
|
|
||||||
mit instanz-spezifischen Transkriptionen und Einstellungen.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SpeechPage;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export { PekPage } from './PekPage';
|
|
||||||
export { SpeechPage } from './SpeechPage';
|
|
||||||
|
|
@ -426,7 +426,7 @@ export const AutomationsPage: React.FC = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await request({
|
await request({
|
||||||
url: `/api/chat/playground/${executionModal.workflowId}/stop`,
|
url: `/api/workflows/${executionModal.workflowId}/stop`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { useSearchParams } from 'react-router-dom';
|
||||||
import { useDashboardInputForm } from '../../hooks/usePlayground';
|
import { useDashboardInputForm } from '../../hooks/usePlayground';
|
||||||
import { useResizablePanels } from '../../hooks/useResizablePanels';
|
import { useResizablePanels } from '../../hooks/useResizablePanels';
|
||||||
import { usePrompts } from '../../hooks/usePrompts';
|
import { usePrompts } from '../../hooks/usePrompts';
|
||||||
|
import { useCurrentInstance } from '../../hooks/useCurrentInstance';
|
||||||
import { FaComment, FaTasks, FaPaperPlane, FaStop, FaFile, FaPlus, FaMicrophone, FaSquare, FaFileAlt } from 'react-icons/fa';
|
import { FaComment, FaTasks, FaPaperPlane, FaStop, FaFile, FaPlus, FaMicrophone, FaSquare, FaFileAlt } from 'react-icons/fa';
|
||||||
import { useToast } from '../../contexts/ToastContext';
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import { useVoiceLanguage, VoiceLanguageSelect, Messages } from '../../components/UiComponents';
|
import { useVoiceLanguage, VoiceLanguageSelect, Messages } from '../../components/UiComponents';
|
||||||
|
|
@ -23,8 +24,12 @@ export const PlaygroundPage: React.FC = () => {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const urlWorkflowId = searchParams.get('workflowId');
|
const urlWorkflowId = searchParams.get('workflowId');
|
||||||
|
|
||||||
|
// Get feature instance context
|
||||||
|
const { instance } = useCurrentInstance();
|
||||||
|
const instanceId = instance?.id || '';
|
||||||
|
|
||||||
// Main hook for input form and data
|
// Main hook for input form and data
|
||||||
const hookData = useDashboardInputForm();
|
const hookData = useDashboardInputForm(instanceId);
|
||||||
const {
|
const {
|
||||||
inputValue,
|
inputValue,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,25 @@ export const FEATURE_REGISTRY: Record<string, FeatureConfig> = {
|
||||||
{ code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true },
|
{ code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
chatplayground: {
|
||||||
|
code: 'chatplayground',
|
||||||
|
label: { de: 'Chat Playground', en: 'Chat Playground' },
|
||||||
|
icon: 'message',
|
||||||
|
views: [
|
||||||
|
{ code: 'playground', label: { de: 'Playground', en: 'Playground' }, path: 'playground' },
|
||||||
|
{ code: 'workflows', label: { de: 'Workflows', en: 'Workflows' }, path: 'workflows' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
automation: {
|
||||||
|
code: 'automation',
|
||||||
|
label: { de: 'Automatisierung', en: 'Automation' },
|
||||||
|
icon: 'settings',
|
||||||
|
views: [
|
||||||
|
{ code: 'definitions', label: { de: 'Definitionen', en: 'Definitions' }, path: 'definitions' },
|
||||||
|
{ code: 'templates', label: { de: 'Vorlagen', en: 'Templates' }, path: 'templates' },
|
||||||
|
{ code: 'logs', label: { de: 'Protokolle', en: 'Logs' }, path: 'logs' },
|
||||||
|
]
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue