injectableFactory = $injectableFactory; $this->acl = $acl; $this->user = $user; } private function getService(): SmartAssistantService { return $this->injectableFactory->create(SmartAssistantService::class); } private function checkAccess(): void { if (!$this->acl->checkScope('Case', 'read')) { throw new Forbidden('No access to Case.'); } } public function getActionStatus(Request $request, Response $response): array { return $this->getService()->getStatus(); } public function postActionChat(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); if (empty($data->message)) { throw new BadRequest('message is required.'); } $mode = $data->mode ?? 'office'; $caseId = $data->caseId ?? null; if ($mode === 'case' && empty($caseId)) { throw new BadRequest('caseId is required for case mode.'); } return $this->getService()->chat( trim($data->message), $mode, $caseId, $data->conversationId ?? null ); } public function postActionExecute(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); if (empty($data->conversationId) || empty($data->actionId)) { throw new BadRequest('conversationId and actionId are required.'); } return $this->getService()->executeAction( $data->conversationId, $data->actionId, (bool) ($data->approved ?? false) ); } public function getActionSummary(Request $request, Response $response): array { $this->checkAccess(); return $this->getService()->getSummary(); } public function getActionAlerts(Request $request, Response $response): array { $this->checkAccess(); return $this->getService()->getAlerts(); } public function getActionHistory(Request $request, Response $response): array { $this->checkAccess(); $caseId = $request->getQueryParam('caseId'); return $this->getService()->getHistory($caseId); } public function getActionConversations(Request $request, Response $response): array { $this->checkAccess(); $type = $request->getQueryParam('type'); return $this->getService()->getConversations($type); } public function getActionConversationMessages(Request $request, Response $response): array { $this->checkAccess(); $key = $request->getQueryParam('conversationKey'); if (empty($key)) { throw new BadRequest('conversationKey is required.'); } return $this->getService()->getConversationMessages($key); } public function getActionSearchHistory(Request $request, Response $response): array { $this->checkAccess(); $query = $request->getQueryParam('query'); if (empty($query)) { throw new BadRequest('query is required.'); } return $this->getService()->searchHistory($query); } public function getActionCaseMemory(Request $request, Response $response): array { $this->checkAccess(); $caseId = $request->getQueryParam('caseId'); if (empty($caseId)) { throw new BadRequest('caseId is required.'); } $category = $request->getQueryParam('category'); $includeArchived = (bool) $request->getQueryParam('includeArchived'); $service = $this->injectableFactory->create(CaseMemoryService::class); return $service->getMemoriesByCase($caseId, $category, $includeArchived); } public function postActionSaveMemory(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); if (empty($data->caseId) || empty($data->content)) { throw new BadRequest('caseId and content are required.'); } $service = $this->injectableFactory->create(CaseMemoryService::class); $entry = $service->createMemory( $data->caseId, $data->category ?? 'key_facts', $data->name ?? mb_substr($data->content, 0, 100), $data->content, $data->importance ?? 'normal', 'manual' ); return [ 'success' => true, 'id' => $entry->get('id'), ]; } public function postActionExecuteTool(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); if (empty($data->tool)) { throw new BadRequest('tool is required.'); } $tool = $data->tool; $params = json_decode(json_encode($data->params ?? []), true) ?? []; $caseId = $data->caseId ?? null; $executor = $this->injectableFactory->create(ActionExecutor::class); return $executor->execute($tool, $params, $caseId, $this->user->getId()); } public function postActionReadDocument(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $filePath = $data->filePath ?? null; if (empty($filePath)) { throw new BadRequest('filePath is required.'); } $maxLength = isset($data->maxLength) ? (int) $data->maxLength : null; $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); $text = $analyzer->extractFullText($filePath, $maxLength); return [ 'success' => $text !== null, 'text' => $text, 'fileName' => basename($filePath), 'charCount' => $text !== null ? mb_strlen($text) : 0, ]; } public function postActionGetDocumentBytes(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $filePath = $data->filePath ?? null; if (empty($filePath)) { throw new BadRequest('filePath is required.'); } $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); return $analyzer->getDocumentBytes($filePath); } public function postActionReadMultipleDocuments(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $filePaths = $data->filePaths ?? []; if (empty($filePaths)) { throw new BadRequest('filePaths is required (array of file paths).'); } $maxPerFile = isset($data->maxCharsPerFile) ? (int) $data->maxCharsPerFile : 50000; $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); $results = $analyzer->extractMultipleDocuments((array) $filePaths, $maxPerFile); return [ 'success' => true, 'documents' => $results, ]; } public function postActionBatchRename(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $renames = $data->renames ?? []; if (empty($renames)) { throw new BadRequest('renames is required (array of {sourcePath, newName}).'); } $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); $results = []; $successCount = 0; foreach ($renames as $rename) { $rename = (array) $rename; $sourcePath = $rename['sourcePath'] ?? null; $newName = $rename['newName'] ?? null; if (!$sourcePath || !$newName) { $results[] = ['success' => false, 'error' => 'Missing sourcePath or newName']; continue; } try { $result = $analyzer->renameDocument($sourcePath, $newName); $results[] = ['success' => true, 'oldName' => $result['oldName'], 'newName' => $result['newName']]; $successCount++; } catch (\Exception $e) { $results[] = ['success' => false, 'oldName' => basename($sourcePath), 'error' => $e->getMessage()]; } } return [ 'success' => true, 'totalCount' => count($renames), 'successCount' => $successCount, 'results' => $results, ]; } public function postActionListDocuments(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; if (empty($caseId)) { throw new BadRequest('caseId is required.'); } $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); return $analyzer->listDocumentsRecursive($caseId); } public function postActionBrowseFolder(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $path = $data->path ?? null; if ($path === null) { throw new BadRequest('path is required.'); } $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); return [ 'path' => $path, 'entries' => $analyzer->browseFolder((string) $path), ]; } public function postActionFindCaseFolder(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $query = $data->query ?? null; if (empty($query)) { throw new BadRequest('query is required.'); } $limit = isset($data->limit) ? max(1, min((int) $data->limit, 50)) : 20; $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); return [ 'query' => $query, 'matches' => $analyzer->findCaseFolder((string) $query, $limit), ]; } public function postActionSetCaseFolderPath(Request $request, Response $response): array { $this->checkAccess(); if (!$this->acl->checkScope('Case', 'edit')) { throw new Forbidden('No edit access to Case.'); } $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $path = $data->path ?? null; if (empty($caseId) || empty($path)) { throw new BadRequest('caseId and path are required.'); } $analyzer = $this->injectableFactory->create(DocumentAnalyzer::class); return $analyzer->setCaseFolderPath((string) $caseId, (string) $path); } public function postActionGenerateFromTemplate(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $templateId = $data->templateId ?? null; $caseId = $data->caseId ?? null; if (empty($templateId)) { throw new BadRequest('templateId is required.'); } if (empty($caseId)) { throw new BadRequest('caseId is required.'); } $documentSubject = $data->documentSubject ?? null; $customPlaceholders = (array) ($data->customPlaceholders ?? []); $generator = $this->injectableFactory->create(GenericTemplateGenerator::class); return $generator->generate($templateId, $caseId, $documentSubject, $customPlaceholders); } public function postActionCreateDocument(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $title = $data->title ?? null; $body = $data->body ?? null; if (empty($caseId)) { throw new BadRequest('caseId is required.'); } if (empty($title)) { throw new BadRequest('title is required.'); } if (empty($body)) { throw new BadRequest('body is required.'); } $documentType = $data->documentType ?? 'letter'; $recipient = $data->recipient ?? null; $generator = $this->injectableFactory->create(FreeDocumentGenerator::class); return $generator->generate($caseId, $title, $body, $documentType, $recipient); } public function postActionWriteToFolder(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $relPath = $data->relPath ?? null; $contentBase64 = $data->contentBase64 ?? null; if (empty($caseId)) throw new BadRequest('caseId is required.'); if (empty($relPath)) throw new BadRequest('relPath is required.'); if (!isset($contentBase64)) throw new BadRequest('contentBase64 is required.'); $content = base64_decode((string) $contentBase64, true); if ($content === false) { throw new BadRequest('contentBase64 is not valid base64.'); } $manager = $this->injectableFactory->create(CaseFolderManager::class); return $manager->writeFile($caseId, $relPath, $content); } public function postActionCreateFolder(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $relPath = $data->relPath ?? null; if (empty($caseId)) throw new BadRequest('caseId is required.'); if (empty($relPath)) throw new BadRequest('relPath is required.'); $manager = $this->injectableFactory->create(CaseFolderManager::class); return $manager->createFolder($caseId, $relPath); } public function postActionRenameItem(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $currentRelPath = $data->currentRelPath ?? null; $newName = $data->newName ?? null; if (empty($caseId)) throw new BadRequest('caseId is required.'); if (empty($currentRelPath)) throw new BadRequest('currentRelPath is required.'); if (empty($newName)) throw new BadRequest('newName is required.'); $manager = $this->injectableFactory->create(CaseFolderManager::class); return $manager->renameItem($caseId, $currentRelPath, $newName); } public function postActionMoveItem(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $sourceRelPath = $data->sourceRelPath ?? null; $targetRelPath = $data->targetRelPath ?? null; if (empty($caseId)) throw new BadRequest('caseId is required.'); if (empty($sourceRelPath)) throw new BadRequest('sourceRelPath is required.'); if (empty($targetRelPath)) throw new BadRequest('targetRelPath is required.'); $manager = $this->injectableFactory->create(CaseFolderManager::class); return $manager->moveItem($caseId, $sourceRelPath, $targetRelPath); } public function postActionMarkForDeletion(Request $request, Response $response): array { $this->checkAccess(); $data = $request->getParsedBody(); $caseId = $data->caseId ?? null; $fileRelPath = $data->fileRelPath ?? null; if (empty($caseId)) throw new BadRequest('caseId is required.'); if (empty($fileRelPath)) throw new BadRequest('fileRelPath is required.'); $manager = $this->injectableFactory->create(CaseFolderManager::class); return $manager->markForDeletion($caseId, $fileRelPath); } }