diff --git a/src/App.tsx b/src/App.tsx index fe2f1dd..3c9b260 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -164,6 +164,9 @@ function App() { } /> } /> + {/* Code Editor Feature Views */} + } /> + {/* Teams Bot Feature Views */} } /> } /> diff --git a/src/config/pageRegistry.tsx b/src/config/pageRegistry.tsx index e4544d6..b20d8bf 100644 --- a/src/config/pageRegistry.tsx +++ b/src/config/pageRegistry.tsx @@ -97,6 +97,7 @@ export const PAGE_ICONS: Record = { 'feature.realestate': , 'feature.chatworkflow': , 'feature.chatplayground': , + 'feature.codeeditor': , 'feature.automation': , 'feature.chatbot': , 'feature.teamsbot': , diff --git a/src/pages/FeatureView.tsx b/src/pages/FeatureView.tsx index 83cca54..9f960ac 100644 --- a/src/pages/FeatureView.tsx +++ b/src/pages/FeatureView.tsx @@ -32,6 +32,9 @@ import { PlaygroundPage, WorkflowsPage } from './workflows'; // Automation Views (reusing existing workflow pages) import { AutomationsPage, AutomationTemplatesPage } from './workflows'; +// CodeEditor Views +import { CodeEditorPage } from './views/codeeditor'; + // Teamsbot Views import { TeamsbotDashboardView } from './views/teamsbot/TeamsbotDashboardView'; import { TeamsbotSessionView } from './views/teamsbot/TeamsbotSessionView'; @@ -123,6 +126,10 @@ const VIEW_COMPONENTS: Record> = { templates: AutomationTemplatesPage, logs: () => , }, + codeeditor: { + editor: CodeEditorPage, + workflows: WorkflowsPage, + }, teamsbot: { dashboard: TeamsbotDashboardView, sessions: TeamsbotSessionView, diff --git a/src/pages/Store.tsx b/src/pages/Store.tsx index 0f023e7..dc9fb03 100644 --- a/src/pages/Store.tsx +++ b/src/pages/Store.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { FaCogs, FaComments, FaHeadset } from 'react-icons/fa'; +import { FaCogs, FaComments, FaFileAlt, FaHeadset } from 'react-icons/fa'; import { useLanguage } from '../providers/language/LanguageContext'; import { useStore } from '../hooks/useStore'; import type { StoreFeature } from '../api/storeApi'; @@ -16,6 +16,7 @@ import styles from './Store.module.css'; const FEATURE_ICONS: Record = { automation: , chatplayground: , + codeeditor: , teamsbot: , }; @@ -30,6 +31,11 @@ const FEATURE_DESCRIPTIONS: Record> = { en: 'Test and experiment with AI chat models in an interactive environment.', fr: 'Testez et experimentez avec des modeles de chat IA dans un environnement interactif.', }, + codeeditor: { + de: 'AI-gestuetzter Editor fuer Text-Dateien mit Cursor-artigem Chat und Diff-Preview.', + en: 'AI-powered editor for text files with Cursor-style chat and diff preview.', + fr: 'Editeur de fichiers texte assiste par IA avec chat et apercu des modifications.', + }, teamsbot: { de: 'Integriere einen AI-Bot in deine Microsoft Teams Meetings und Channels.', en: 'Integrate an AI bot into your Microsoft Teams meetings and channels.', diff --git a/src/pages/views/codeeditor/CodeEditor.module.css b/src/pages/views/codeeditor/CodeEditor.module.css new file mode 100644 index 0000000..95dcd68 --- /dev/null +++ b/src/pages/views/codeeditor/CodeEditor.module.css @@ -0,0 +1,344 @@ +/* CodeEditor Feature Styles */ + +.container { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.panels { + display: flex; + flex: 1; + overflow: hidden; +} + +.filePanel, +.chatPanel, +.diffPanel { + display: flex; + flex-direction: column; + overflow: hidden; +} + +.mainArea { + display: flex; + overflow: hidden; +} + +.divider { + width: 6px; + cursor: col-resize; + background: var(--border-color, #e0e0e0); + flex-shrink: 0; + transition: background 0.15s; +} + +.divider:hover { + background: var(--primary-color, #4a90d9); +} + +.dragging { + cursor: col-resize; + user-select: none; +} + +.dragging .divider { + background: var(--primary-color, #4a90d9); +} + +/* File List Panel */ +.fileList { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.panelHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid var(--border-color, #e0e0e0); +} + +.panelHeader h3 { + margin: 0; + font-size: 14px; + font-weight: 600; +} + +.selectedCount { + font-size: 12px; + color: var(--text-secondary, #666); +} + +.fileItems { + flex: 1; + overflow-y: auto; + padding: 4px 0; +} + +.fileItem { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + cursor: pointer; + transition: background 0.1s; +} + +.fileItem:hover { + background: var(--hover-bg, #f5f5f5); +} + +.fileItemSelected { + background: var(--selected-bg, #e8f0fe); +} + +.fileCheckbox { + color: var(--primary-color, #4a90d9); + flex-shrink: 0; +} + +.fileIcon { + color: var(--text-secondary, #666); + flex-shrink: 0; + font-size: 12px; +} + +.fileName { + flex: 1; + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.fileSize { + font-size: 11px; + color: var(--text-secondary, #999); + flex-shrink: 0; +} + +.emptyState { + padding: 24px 16px; + text-align: center; + color: var(--text-secondary, #999); + font-size: 13px; +} + +/* Chat Panel */ +.messagesArea { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.inputArea { + border-top: 1px solid var(--border-color, #e0e0e0); + padding: 12px 16px; +} + +.input { + width: 100%; + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 8px; + padding: 10px 12px; + font-size: 14px; + resize: none; + font-family: inherit; + outline: none; + transition: border-color 0.15s; +} + +.input:focus { + border-color: var(--primary-color, #4a90d9); +} + +.input:disabled { + background: var(--disabled-bg, #f5f5f5); +} + +.inputActions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 8px; +} + +.fileCount { + font-size: 12px; + color: var(--text-secondary, #666); +} + +.sendButton, +.stopButton { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + border: none; + border-radius: 6px; + font-size: 13px; + cursor: pointer; + transition: opacity 0.15s; +} + +.sendButton { + background: var(--primary-color, #4a90d9); + color: white; +} + +.sendButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.stopButton { + background: var(--danger-color, #dc3545); + color: white; +} + +/* Diff Preview Panel */ +.diffPreview { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.diffItems { + flex: 1; + overflow-y: auto; + padding: 8px; +} + +.diffCard { + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 8px; + margin-bottom: 8px; + overflow: hidden; +} + +.diffCard_pending { + border-color: var(--warning-color, #ffc107); +} + +.diffCard_accepted { + border-color: var(--success-color, #28a745); + opacity: 0.7; +} + +.diffCard_rejected { + border-color: var(--danger-color, #dc3545); + opacity: 0.5; +} + +.diffCardHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: var(--surface-bg, #f8f9fa); + border-bottom: 1px solid var(--border-color, #e0e0e0); +} + +.diffFileName { + font-size: 13px; + font-weight: 600; + font-family: monospace; +} + +.diffStatus { + font-size: 11px; + padding: 2px 8px; + border-radius: 10px; + text-transform: uppercase; + font-weight: 600; +} + +.diffStatus_pending { + background: var(--warning-light, #fff3cd); + color: var(--warning-dark, #856404); +} + +.diffStatus_accepted { + background: var(--success-light, #d4edda); + color: var(--success-dark, #155724); +} + +.diffStatus_rejected { + background: var(--danger-light, #f8d7da); + color: var(--danger-dark, #721c24); +} + +.diffContent { + padding: 8px 12px; + max-height: 300px; + overflow-y: auto; +} + +.diffOld, +.diffNew { + margin-bottom: 8px; +} + +.diffLabel { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + color: var(--text-secondary, #666); + margin-bottom: 4px; +} + +.diffOld pre, +.diffNew pre { + margin: 0; + font-size: 12px; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + padding: 8px; + border-radius: 4px; + overflow-x: auto; +} + +.diffOld pre { + background: var(--danger-light, #fff0f0); +} + +.diffNew pre { + background: var(--success-light, #f0fff0); +} + +.diffActions { + display: flex; + gap: 8px; + padding: 8px 12px; + border-top: 1px solid var(--border-color, #e0e0e0); +} + +.acceptButton, +.rejectButton { + display: flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border: none; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + transition: opacity 0.15s; +} + +.acceptButton { + background: var(--success-color, #28a745); + color: white; +} + +.rejectButton { + background: var(--danger-color, #dc3545); + color: white; +} diff --git a/src/pages/views/codeeditor/CodeEditorPage.tsx b/src/pages/views/codeeditor/CodeEditorPage.tsx new file mode 100644 index 0000000..b548b0a --- /dev/null +++ b/src/pages/views/codeeditor/CodeEditorPage.tsx @@ -0,0 +1,146 @@ +/** + * CodeEditorPage + * + * Main page for the CodeEditor feature. + * Three-panel layout: FileList (left) | Chat (center) | DiffPreview (right) + */ + +import React, { useState, useRef, useCallback } from 'react'; +import { useCurrentInstance } from '../../../hooks/useCurrentInstance'; +import { useCodeEditor } from './useCodeEditor'; +import { FileListPanel } from './FileListPanel'; +import { DiffPreviewPanel } from './DiffPreviewPanel'; +import { useResizablePanels } from '../../../hooks/useResizablePanels'; +import { Messages } from '../../../components/UiComponents'; +import { FaPaperPlane, FaStop } from 'react-icons/fa'; +import styles from './CodeEditor.module.css'; + +export const CodeEditorPage: React.FC = () => { + const { instance } = useCurrentInstance(); + const instanceId = instance?.id || ''; + const inputRef = useRef(null); + const [inputValue, setInputValue] = useState(''); + + const { + messages, + selectedFileIds, + toggleFileSelection, + pendingEdits, + acceptEdit, + rejectEdit, + isProcessing, + sendMessage, + stopProcessing, + files, + } = useCodeEditor(instanceId); + + const { + leftWidth: fileListWidth, + handleMouseDown: fileListMouseDown, + containerRef: outerContainerRef, + isDragging: isDraggingLeft, + } = useResizablePanels({ + storageKey: 'codeeditor-filelist-width', + defaultLeftWidth: 20, + minLeftWidth: 10, + maxLeftWidth: 40, + }); + + const { + leftWidth: chatWidth, + handleMouseDown: chatMouseDown, + containerRef: innerContainerRef, + isDragging: isDraggingRight, + } = useResizablePanels({ + storageKey: 'codeeditor-chat-width', + defaultLeftWidth: 60, + minLeftWidth: 30, + maxLeftWidth: 80, + }); + + const handleSubmit = useCallback(() => { + const trimmed = inputValue.trim(); + if (!trimmed || isProcessing) return; + sendMessage(trimmed, selectedFileIds); + setInputValue(''); + }, [inputValue, isProcessing, sendMessage, selectedFileIds]); + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmit(); + } + }, [handleSubmit]); + + return ( +
+
+ {/* Left: File List */} +
+ +
+ +
+ + {/* Center + Right */} +
+ {/* Center: Chat */} +
+
+ +
+ +
+