adminizer 5.0.0-build.2 → 5.0.0-build.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/abnfDiagram-VCTEODGH-CkmsvdDi.js +1 -0
- package/assets/add-CESPH_aI.js +1 -0
- package/assets/add-group-CQ4Xm8Ru.js +1 -0
- package/assets/add-user-B-QNjR4B.js +1 -0
- package/assets/ai-assistant/agent.es.js +28362 -0
- package/assets/app-C5Ju5Zra.css +1 -0
- package/assets/app-Ca5yUSBv.js +138 -0
- package/assets/arc-B-Dt8YNl.js +1 -0
- package/assets/architectureDiagram-5GKGNRK7-Ca58kq-l.js +36 -0
- package/assets/asmcrypto-DZeQH0LC.js +1 -0
- package/assets/blockDiagram-NRAW4CY4-CuqE52IG.js +129 -0
- package/assets/c4Diagram-UCG6FXSJ-Cx8z-0Bw.js +38 -0
- package/assets/catalog-CkSbxvx0.js +6 -0
- package/assets/channel-C9z6Pi_j.js +1 -0
- package/assets/chunk-2Q5K7J3B-CXISVKTC.js +1 -0
- package/assets/chunk-5VM5RSS4-DYGeeVYe.js +15 -0
- package/assets/chunk-F27PBJKO-BeY-dbKj.js +1 -0
- package/assets/chunk-G27WJ6UU-DeX_eH7C.js +231 -0
- package/assets/chunk-JWPE2WC7-DaxI9q0u.js +1 -0
- package/assets/chunk-LCL6LL3I-B7PkaEz_.js +206 -0
- package/assets/chunk-POPQ4Y6H-lwu6ZpsN.js +1 -0
- package/assets/chunk-SVP7TREG-CVFllgl1.js +88 -0
- package/assets/chunk-XXDRQBXY-DDLN0rcg.js +1 -0
- package/assets/classDiagram-DTDB5LWJ-BKtCIC3B.js +1 -0
- package/assets/classDiagram-v2-JRS7N3AN-BKtCIC3B.js +1 -0
- package/assets/controls/ckeditor.es.js +9370 -9306
- package/assets/controls/handsontable.es.js +11971 -11938
- package/assets/controls/jsoneditor.es.js +13104 -13008
- package/assets/controls/leaflet.es.js +1023 -1004
- package/assets/controls/toast-ui.es.js +1843 -1834
- package/assets/cose-bilkent-JH36ORCC-CWRyUkhQ.js +1 -0
- package/assets/cynefin-OW5HDTMX-B1iLDXCC.js +166 -0
- package/assets/cynefinDiagram-5FMLGOSQ-DaXQHr3l.js +62 -0
- package/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/assets/dagre-3AP2YEHR-DFORa4Hz.js +4 -0
- package/assets/dashboard-Ssa1_Of3.js +3 -0
- package/assets/debounce-B3mXI_sx.js +1 -0
- package/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/assets/diagram-S7CK7UJ4-CgP6KBo0.js +30 -0
- package/assets/diagram-UQ7AKVKN-oYfQe6w0.js +41 -0
- package/assets/diagram-VSXAHHWV-DfCU5wgt.js +3 -0
- package/assets/diagram-VX7I27RA-BITJVrNY.js +24 -0
- package/assets/diagram-Z3DM3KII-D8J9Ssjy.js +24 -0
- package/assets/docs-DHMt_Rq1.js +1 -0
- package/assets/ebnfDiagram-PWID7BFC-qpNmoYgx.js +1 -0
- package/assets/erDiagram-SSCWMZ5O-DDF-Y0zs.js +99 -0
- package/assets/flowDiagram-A5DVABFB-CjKVkR9T.js +168 -0
- package/assets/ganttDiagram-EL5Y4UJY-BXGQ53w6.js +292 -0
- package/assets/gitGraphDiagram-WWUBYQGX-BLMevdLh.js +106 -0
- package/assets/history-tBOo2gd5.js +1 -0
- package/assets/infoDiagram-RXCK75RN-BAnPdnyw.js +2 -0
- package/assets/init-Gi6I4Gst.js +1 -0
- package/assets/init-user-DrNUHa6r.js +1 -0
- package/assets/isObject-DL-BuaBl.js +1 -0
- package/assets/ishikawaDiagram-5VMMS53U-PnL0Z4Li.js +70 -0
- package/assets/journeyDiagram-EYS64GPL-CLcsCSwF.js +139 -0
- package/assets/kanban-definition-3QL26DDD-C2eU3uBE.js +89 -0
- package/assets/katex-HP8lGamR.js +257 -0
- package/assets/layout-BXoYdHdx.js +1 -0
- package/assets/linear-BSzrP0oP.js +1 -0
- package/assets/list-CLg6xeIK.js +4 -0
- package/assets/login-Ce7o3-o_.js +1 -0
- package/assets/manifest.json +834 -58
- package/assets/{media-manager-BDbpSSjH.js → media-manager-D7G1P3fz.js} +3 -3
- package/assets/mermaid.core-aKMJHeEk.js +310 -0
- package/assets/mindmap-definition-FBJOCRG2-Dzbz5ghA.js +96 -0
- package/assets/module-BsXph_pA.js +1 -0
- package/assets/notification-D-Zz1gF_.js +20 -0
- package/assets/ordinal-Cboi1Yqb.js +1 -0
- package/assets/pegDiagram-XKGWAZYB-BTdWkU0Y.js +1 -0
- package/assets/pieDiagram-E7YTZNPT-Dnw8-TB-.js +39 -0
- package/assets/quadrantDiagram-AXDQQJYC-CALzLlg9.js +7 -0
- package/assets/railroadDiagram-O6MQD6OU-CTA1vzcv.js +1 -0
- package/assets/register-CNnBkzej.js +1 -0
- package/assets/requirementDiagram-EFPCY7ZU-UX2CF5HA.js +84 -0
- package/assets/ru-CoNgAwFK.js +1 -0
- package/assets/sankeyDiagram-P5KCCOFB-4aPp3ATV.js +40 -0
- package/assets/sequenceDiagram-WJ2MYXX4--kltkwJw.js +162 -0
- package/assets/sizeCapture-X5ZJPWSS-Ca9-g5QY.js +1 -0
- package/assets/stateDiagram-HBIQ2CUA-Diaf5Q7I.js +1 -0
- package/assets/stateDiagram-v2-4QOOHH4V-C6FlRp7u.js +1 -0
- package/assets/swimlanes-XN3QIQJK-DhqX_myE.js +1 -0
- package/assets/swimlanesDiagram-VK2B7HYN-i_3LTEBk.js +8 -0
- package/assets/timeline-definition-24CTP7MA-CwkJQbhq.js +120 -0
- package/assets/toNumber-DZb-r9sy.js +1 -0
- package/assets/use-appearance-DC-DESKB.js +1 -0
- package/assets/user-filters-list-W47DTBp2.js +1 -0
- package/assets/vennDiagram-4TSXK5OY-BNM3_cVS.js +34 -0
- package/assets/wardleyDiagram-VM6X3IG4-BODb1NP8.js +78 -0
- package/assets/welcome-BeBm6NIi.js +1 -0
- package/assets/with-app-layout-B90DtzYC.js +31 -0
- package/assets/xychartDiagram-S5SC5T6Z-DthqEh3g.js +7 -0
- package/controllers/access-rights/permissionOptions.d.ts +2 -0
- package/controllers/access-rights/permissionOptions.js +23 -0
- package/controllers/add.js +1 -1
- package/controllers/addGroup.js +15 -6
- package/controllers/ai/AiAgentController.d.ts +66 -0
- package/controllers/ai/AiAgentController.js +435 -0
- package/controllers/ai/AiAssistantController.d.ts +0 -4
- package/controllers/ai/AiAssistantController.js +4 -78
- package/controllers/catalog/FrontendCatalogAdapter.d.ts +1 -1
- package/controllers/catalog/FrontentCatalogAdapter.d.ts +1 -1
- package/controllers/docs/index.d.ts +22 -0
- package/controllers/docs/index.js +126 -0
- package/controllers/edit.js +1 -1
- package/controllers/editGroup.js +15 -6
- package/controllers/feedback/feedbackController.d.ts +1 -1
- package/controllers/filter-fields/savedFilters.js +1 -1
- package/controllers/history-actions/HistoryController.d.ts +1 -1
- package/controllers/history-actions/HistoryController.js +6 -6
- package/controllers/list.js +1 -1
- package/controllers/login.js +1 -1
- package/controllers/media-manager/mediaManagerAdapter.d.ts +1 -1
- package/controllers/notifications/NotificationController.js +14 -8
- package/controllers/view.js +2 -2
- package/controllers/widgets/filterInfo.js +1 -1
- package/helpers/accessRightsHelper.d.ts +11 -4
- package/helpers/accessRightsHelper.js +82 -12
- package/helpers/assetVersionHelper.d.ts +14 -0
- package/helpers/assetVersionHelper.js +42 -0
- package/helpers/configHelper.d.ts +3 -3
- package/helpers/configHelper.js +20 -18
- package/helpers/controllerHelper.d.ts +3 -2
- package/helpers/controllerHelper.js +25 -15
- package/helpers/fieldsHelper.d.ts +2 -2
- package/helpers/fieldsHelper.js +3 -3
- package/helpers/filterDatetimeHelper.d.ts +1 -1
- package/helpers/inertiaActionsHelper.d.ts +3 -3
- package/helpers/inertiaActionsHelper.js +4 -5
- package/helpers/inertiaAddHelper.d.ts +5 -5
- package/helpers/inertiaAddHelper.js +7 -6
- package/helpers/inertiaGroupHelper.d.ts +3 -3
- package/helpers/inertiaGroupHelper.js +10 -2
- package/helpers/inertiaListHelper.d.ts +4 -4
- package/helpers/inertiaListHelper.js +7 -8
- package/helpers/inertiaMenuHelper.d.ts +2 -3
- package/helpers/inertiaMenuHelper.js +3 -26
- package/helpers/inertiaUserHelper.d.ts +3 -3
- package/helpers/menuHelper.d.ts +2 -2
- package/helpers/modelResourceHelper.d.ts +9 -0
- package/helpers/modelResourceHelper.js +43 -0
- package/helpers/navigationAccessHelper.d.ts +13 -0
- package/helpers/navigationAccessHelper.js +40 -0
- package/helpers/translationHelper.d.ts +2 -2
- package/index.d.ts +69 -58
- package/index.js +11 -0
- package/interfaces/adminpanelConfig.d.ts +18 -5
- package/interfaces/global.d.ts +6 -6
- package/interfaces/internalModelAccess.d.ts +1 -1
- package/interfaces/listQueryBuilder.d.ts +1 -1
- package/interfaces/types.d.ts +171 -2
- package/lib/Adminizer.d.ts +46 -26
- package/lib/Adminizer.js +37 -1
- package/lib/DataAccessor.d.ts +7 -6
- package/lib/DataAccessor.js +50 -48
- package/lib/MiddlewareManager.d.ts +1 -1
- package/lib/access-rights/AccessRightsHandler.d.ts +2 -2
- package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
- package/lib/admin-links/AdminLinkHandler.js +175 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
- package/lib/ai-assistant/AbstractAiModelService.d.ts +140 -3
- package/lib/ai-assistant/AbstractAiModelService.js +154 -1
- package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
- package/lib/ai-assistant/AiAgentRunManager.js +109 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +58 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +87 -0
- package/lib/ai-assistant/AiAssistantHandler.d.ts +16 -5
- package/lib/ai-assistant/AiAssistantHandler.js +35 -1
- package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.js +138 -0
- package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
- package/lib/ai-assistant/builtinAgentSkills.js +256 -0
- package/lib/ai-assistant/jsonSafe.d.ts +13 -0
- package/lib/ai-assistant/jsonSafe.js +81 -0
- package/lib/app-manager/AdminizerApp.d.ts +40 -11
- package/lib/app-manager/AppManager.d.ts +2 -2
- package/lib/app-manager/AppManager.js +54 -50
- package/lib/app-manager/AssetHandler.d.ts +2 -2
- package/lib/app-manager/ConfigLayerHandler.d.ts +2 -2
- package/lib/app-manager/ControllerHandler.d.ts +2 -2
- package/lib/catalog/AbstractCatalog.d.ts +1 -1
- package/lib/catalog/CatalogHandler.d.ts +1 -1
- package/lib/controls/ControlsHandler.d.ts +1 -1
- package/lib/controls/codeEditor/MonacoEditor.d.ts +1 -1
- package/lib/controls/geojsoneditor/GeoEditor.d.ts +1 -1
- package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -1
- package/lib/controls/markdown/ToastUiEditor.d.ts +1 -1
- package/lib/controls/table/Handsontable.d.ts +1 -1
- package/lib/controls/wysiwyg/CKeditor.d.ts +1 -1
- package/lib/docs/AbstractDocumentation.d.ts +100 -0
- package/lib/docs/AbstractDocumentation.js +108 -0
- package/lib/docs/DocumentationHandler.d.ts +100 -0
- package/lib/docs/DocumentationHandler.js +198 -0
- package/lib/docs/FileDocumentation.d.ts +47 -0
- package/lib/docs/FileDocumentation.js +189 -0
- package/lib/docs/docsAgentSkills.d.ts +10 -0
- package/lib/docs/docsAgentSkills.js +93 -0
- package/lib/feedback/FeedbackHandler.d.ts +2 -2
- package/lib/filters/ConditionValidator.d.ts +1 -1
- package/lib/filters/FilterCustomFieldHandler.d.ts +2 -2
- package/lib/filters/FilterService.d.ts +4 -4
- package/lib/filters/FilterService.js +4 -3
- package/lib/filters/index.d.ts +6 -6
- package/lib/history-actions/AbstractHistoryAdapter.d.ts +6 -6
- package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
- package/lib/history-actions/DefaultHistoryAdapter.d.ts +4 -4
- package/lib/history-actions/HistoryHandler.d.ts +1 -1
- package/lib/list-query-builder/ListQueryBuilder.d.ts +7 -7
- package/lib/media-manager/AbstractMediaManager.d.ts +2 -2
- package/lib/media-manager/MediaManagerHandler.d.ts +1 -1
- package/lib/media-manager/Thumb.d.ts +1 -1
- package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -5
- package/lib/model/AbstractModel.d.ts +11 -4
- package/lib/model/InternalModelAccessFactory.d.ts +2 -2
- package/lib/model/ModelHandler.d.ts +50 -5
- package/lib/model/ModelHandler.js +107 -8
- package/lib/model/adapter/sequelize.d.ts +7 -5
- package/lib/model/adapter/sequelize.js +79 -46
- package/lib/model/adapter/typeorm.d.ts +2 -2
- package/lib/model/adapter/typeorm.js +1 -1
- package/lib/model/internalModelRepository.d.ts +2 -2
- package/lib/notifications/AbstractNotificationService.d.ts +4 -4
- package/lib/notifications/GeneralNotificationService.d.ts +2 -2
- package/lib/notifications/GeneralNotificationService.js +6 -5
- package/lib/notifications/NotificationHandler.d.ts +2 -2
- package/lib/notifications/SystemNotificationService.d.ts +2 -2
- package/lib/notifications/SystemNotificationService.js +1 -1
- package/lib/requestProcessor.d.ts +1 -1
- package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
- package/lib/ui-i18n/uiTranslationKeys.js +42 -1
- package/lib/widgets/abstractAction.d.ts +1 -1
- package/lib/widgets/abstractCustom.d.ts +2 -2
- package/lib/widgets/abstractInfo.d.ts +1 -1
- package/lib/widgets/abstractLink.d.ts +2 -2
- package/lib/widgets/abstractSwitch.d.ts +1 -1
- package/lib/widgets/abstractWidgetBase.d.ts +1 -1
- package/lib/widgets/widgetHandler.d.ts +9 -9
- package/lib/widgets/widgetHandler.js +5 -1
- package/models/Filter.d.ts +1 -1
- package/models/FilterColumn.d.ts +1 -1
- package/models/Group.d.ts +3 -2
- package/models/HistoryActions.d.ts +1 -1
- package/models/MediaManagerAP.d.ts +2 -2
- package/models/MediaManagerAssociationsAP.d.ts +1 -1
- package/models/MediaManagerMetaAP.d.ts +1 -1
- package/models/User.d.ts +2 -2
- package/models/UserNotification.d.ts +1 -1
- package/package.json +29 -20
- package/policies/authPolicies.js +6 -6
- package/policies/permissionResolvers.d.ts +1 -0
- package/policies/permissionResolvers.js +3 -0
- package/services/FeedService.d.ts +3 -3
- package/system/Router.d.ts +1 -1
- package/system/Router.js +27 -1
- package/system/bindAccessRights.d.ts +1 -1
- package/system/bindAccessRights.js +2 -3
- package/system/bindAuthorization.d.ts +1 -1
- package/system/bindControls.d.ts +1 -1
- package/system/bindCors.d.ts +1 -1
- package/system/bindCustomFilterHandlers.d.ts +1 -1
- package/system/bindDashboardWidgets.d.ts +1 -1
- package/system/bindDev.d.ts +1 -1
- package/system/bindDocs.d.ts +13 -0
- package/system/bindDocs.js +77 -0
- package/system/bindHistory.d.ts +1 -1
- package/system/bindInertia.d.ts +1 -1
- package/system/bindInertia.js +23 -1
- package/system/bindMediaManager.d.ts +1 -1
- package/system/bindNotifications.d.ts +1 -1
- package/system/bindReqFunctions.d.ts +1 -1
- package/system/bindTranslations.d.ts +1 -1
- package/system/buildInternalModelAccess.d.ts +3 -3
- package/system/buildInternalModelAccess.js +5 -2
- package/system/defaults.d.ts +1 -1
- package/system/routePolicies.d.ts +1 -1
- package/system/systemModelContracts.d.ts +1 -1
- package/system/systemModelContracts.js +11 -7
- package/system/validateSystemModels.d.ts +1 -1
- package/system/validateSystemModels.js +29 -9
- package/assets/add-group-BdHFzKng.js +0 -1
- package/assets/add-p6yGCoZ7.js +0 -1
- package/assets/add-user-CDspCLu4.js +0 -1
- package/assets/app-DNXiApCk.css +0 -1
- package/assets/app-layout-DCr_-JY3.js +0 -1
- package/assets/app.js +0 -138
- package/assets/catalog-XN-_p9TB.js +0 -6
- package/assets/dashboard-CHZO2QFY.js +0 -3
- package/assets/debounce-BMmXVQ06.js +0 -1
- package/assets/history-CQd0FojQ.js +0 -1
- package/assets/init-user-8aUohVDq.js +0 -1
- package/assets/list-PHWgTHsg.js +0 -4
- package/assets/login-CO4laf6m.js +0 -1
- package/assets/module-DVyQtIxm.js +0 -1
- package/assets/notification-B87XG8nQ.js +0 -20
- package/assets/register-DBYIMXGQ.js +0 -1
- package/assets/ru-rYKdQbWF.js +0 -1
- package/assets/use-appearance-_EbbjHTh.js +0 -1
- package/assets/user-filters-list-BRzDbPkx.js +0 -1
- package/assets/welcome-DiXmrTgc.js +0 -1
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import multer from 'multer';
|
|
2
|
+
import { Adminizer } from '../../lib/Adminizer.js';
|
|
3
|
+
import { AiAgentRunManager } from '../../lib/ai-assistant/AiAgentRunManager.js';
|
|
4
|
+
const MAX_FILES = 5;
|
|
5
|
+
const MAX_FILE_SIZE = 8 * 1024 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* Inline cap per text attachment: roughly 64k tokens, half of a typical 128k
|
|
8
|
+
* context window, so a single file cannot immediately trigger compaction.
|
|
9
|
+
*/
|
|
10
|
+
const MAX_INLINE_TEXT_CHARS = 256 * 1024;
|
|
11
|
+
const TEXT_EXTENSIONS = new Set([
|
|
12
|
+
'txt', 'md', 'markdown', 'csv', 'tsv', 'json', 'yaml', 'yml', 'xml', 'html', 'css',
|
|
13
|
+
'js', 'ts', 'jsx', 'tsx', 'sql', 'log', 'ini', 'conf', 'env', 'sh', 'graphql',
|
|
14
|
+
]);
|
|
15
|
+
const TEXT_MIME_TYPES = new Set([
|
|
16
|
+
'application/json', 'application/xml', 'application/x-yaml', 'application/sql', 'image/svg+xml',
|
|
17
|
+
]);
|
|
18
|
+
const runs = new AiAgentRunManager();
|
|
19
|
+
// multer only engages on multipart/form-data; JSON bodies pass through untouched.
|
|
20
|
+
const upload = multer({
|
|
21
|
+
storage: multer.memoryStorage(),
|
|
22
|
+
limits: { fileSize: MAX_FILE_SIZE, files: MAX_FILES, fieldSize: 1024 * 1024 },
|
|
23
|
+
}).array('files', MAX_FILES);
|
|
24
|
+
function isTextFile(file) {
|
|
25
|
+
const mime = file.mimetype || '';
|
|
26
|
+
if (mime.startsWith('text/'))
|
|
27
|
+
return true;
|
|
28
|
+
if (TEXT_MIME_TYPES.has(mime))
|
|
29
|
+
return true;
|
|
30
|
+
const ext = (file.originalname || '').split('.').pop()?.toLowerCase() || '';
|
|
31
|
+
return TEXT_EXTENSIONS.has(ext);
|
|
32
|
+
}
|
|
33
|
+
/** Turns prompt + uploaded files into an ai-sdk ModelMessage[] for the agent. */
|
|
34
|
+
function buildInput(prompt, files, vision, savedFiles = []) {
|
|
35
|
+
if (!files?.length)
|
|
36
|
+
return prompt;
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (prompt)
|
|
39
|
+
parts.push({ type: 'text', text: prompt });
|
|
40
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
41
|
+
const file = files[index];
|
|
42
|
+
const saved = savedFiles[index];
|
|
43
|
+
const name = file.originalname || 'file';
|
|
44
|
+
const savedNote = saved?.id
|
|
45
|
+
? ` saved_file_id=${JSON.stringify(saved.id)} saved_file_name=${JSON.stringify(saved.name || name)}`
|
|
46
|
+
: '';
|
|
47
|
+
if (isTextFile(file)) {
|
|
48
|
+
let text = file.buffer.toString('utf8');
|
|
49
|
+
let note = '';
|
|
50
|
+
if (text.length > MAX_INLINE_TEXT_CHARS) {
|
|
51
|
+
text = text.slice(0, MAX_INLINE_TEXT_CHARS);
|
|
52
|
+
note = `\n[attachment truncated at ${MAX_INLINE_TEXT_CHARS} characters]`;
|
|
53
|
+
}
|
|
54
|
+
parts.push({
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: `<attachment name=${JSON.stringify(name)}${savedNote}>\n${text}${note}\n</attachment>`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if ((file.mimetype || '').startsWith('image/')) {
|
|
60
|
+
if (vision) {
|
|
61
|
+
parts.push({ type: 'image', image: `data:${file.mimetype};base64,${file.buffer.toString('base64')}` });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
parts.push({
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: `[Image attached: ${name}${saved?.id ? `, saved_file_id=${saved.id}` : ''} — the current model cannot see images]`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
parts.push({
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `[Unsupported attachment saved but not inlined: ${name}${saved?.id ? `, saved_file_id=${saved.id}` : ''} (${file.mimetype || 'unknown type'})]`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [{ role: 'user', content: parts }];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Streaming agent endpoints, consumed by the assistant panel bundle
|
|
81
|
+
* (src/assets/js/ai-assistant).
|
|
82
|
+
*
|
|
83
|
+
* Every route is scoped to one registered model service; permissions are
|
|
84
|
+
* enforced by the router (`ai-assistant-<modelId>`). Services that implement
|
|
85
|
+
* only `generateReply` are supported too — their answer is streamed as a
|
|
86
|
+
* single text frame, so one UI serves both kinds.
|
|
87
|
+
*/
|
|
88
|
+
export class AiAgentController {
|
|
89
|
+
/** GET /api/ai-assistant/agent/:modelId/status */
|
|
90
|
+
static async getStatus(req, res) {
|
|
91
|
+
const service = AiAgentController.resolveService(req, res);
|
|
92
|
+
if (!service)
|
|
93
|
+
return;
|
|
94
|
+
const locale = AiAgentController.resolveLocale(req);
|
|
95
|
+
const schema = {
|
|
96
|
+
...service.getUiSchema(locale),
|
|
97
|
+
uiMethods: req.adminizer.aiAssistantUiMethodHandler.getAvailable(req.user),
|
|
98
|
+
};
|
|
99
|
+
if (typeof service.getConnectionStatus !== 'function') {
|
|
100
|
+
res.json({ state: 'ready', locale, schema });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const status = await service.getConnectionStatus();
|
|
105
|
+
res.json({ ...status, locale, schema });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
Adminizer.log.error('AI agent status check failed', error);
|
|
109
|
+
res.status(500).json({ error: error?.message || 'Status check failed' });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** GET /api/ai-assistant/agent/:modelId/limits */
|
|
113
|
+
static async getLimits(req, res) {
|
|
114
|
+
const service = AiAgentController.resolveService(req, res);
|
|
115
|
+
if (!service || !await AiAgentController.ensureReady(service, res))
|
|
116
|
+
return;
|
|
117
|
+
if (typeof service.getLimits !== 'function') {
|
|
118
|
+
res.json({ provider: service.id, supported: false });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const forceRefresh = req.query.refresh === '1' || req.query.refresh === 'true';
|
|
122
|
+
try {
|
|
123
|
+
const limits = await service.getLimits(forceRefresh);
|
|
124
|
+
res.json(limits ?? { provider: service.id, supported: false });
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
res.status(500).json({ error: error?.message || 'Failed to read limits' });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** GET /api/ai-assistant/agent/:modelId/meta */
|
|
131
|
+
static async getMeta(req, res) {
|
|
132
|
+
const service = AiAgentController.resolveService(req, res);
|
|
133
|
+
if (!service || !await AiAgentController.ensureReady(service, res))
|
|
134
|
+
return;
|
|
135
|
+
try {
|
|
136
|
+
res.json(await AiAgentController.buildMeta(service, req));
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
Adminizer.log.error('AI agent meta failed', error);
|
|
140
|
+
res.status(500).json({ error: error?.message || 'Failed to load agent info' });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** POST /api/ai-assistant/agent/:modelId/model */
|
|
144
|
+
static async setModel(req, res) {
|
|
145
|
+
const service = AiAgentController.resolveService(req, res);
|
|
146
|
+
if (!service || !await AiAgentController.ensureReady(service, res))
|
|
147
|
+
return;
|
|
148
|
+
if (typeof service.setCurrentModel !== 'function') {
|
|
149
|
+
res.status(501).json({ error: 'This assistant does not support switching models' });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const model = typeof req.body?.model === 'string' ? req.body.model.trim() : '';
|
|
153
|
+
if (!model) {
|
|
154
|
+
res.status(400).json({ error: 'Model is required.' });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await service.setCurrentModel(req.user, model);
|
|
159
|
+
res.json(await AiAgentController.buildMeta(service, req));
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
res.status(500).json({ error: error?.message || 'Failed to switch the model' });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** GET /api/ai-assistant/agent/:modelId/history */
|
|
166
|
+
static async getHistory(req, res) {
|
|
167
|
+
const service = AiAgentController.resolveService(req, res);
|
|
168
|
+
if (!service)
|
|
169
|
+
return;
|
|
170
|
+
const messages = await AiAgentController.syncActiveConversation(service, req);
|
|
171
|
+
res.json({ messages });
|
|
172
|
+
}
|
|
173
|
+
/** GET /api/ai-assistant/agent/:modelId/conversations */
|
|
174
|
+
static async listConversations(req, res) {
|
|
175
|
+
const service = AiAgentController.resolveService(req, res);
|
|
176
|
+
if (!service)
|
|
177
|
+
return;
|
|
178
|
+
await AiAgentController.syncActiveConversation(service, req);
|
|
179
|
+
const active = service.conversationHistory.getActive(req.user);
|
|
180
|
+
res.json({ activeId: active.id, conversations: service.conversationHistory.list(req.user).map(AiAgentController.serializeConversation) });
|
|
181
|
+
}
|
|
182
|
+
/** POST /api/ai-assistant/agent/:modelId/conversations */
|
|
183
|
+
static async createConversation(req, res) {
|
|
184
|
+
const service = AiAgentController.resolveService(req, res);
|
|
185
|
+
if (!service)
|
|
186
|
+
return;
|
|
187
|
+
await AiAgentController.syncActiveConversation(service, req);
|
|
188
|
+
const title = typeof req.body?.title === 'string' ? req.body.title.trim().slice(0, 120) : undefined;
|
|
189
|
+
const conversation = service.conversationHistory.create(req.user, title || undefined);
|
|
190
|
+
await AiAgentController.restoreConversation(service, req, conversation.messages);
|
|
191
|
+
res.status(201).json({ conversation: AiAgentController.serializeConversation(conversation) });
|
|
192
|
+
}
|
|
193
|
+
/** POST /api/ai-assistant/agent/:modelId/conversations/:conversationId/select */
|
|
194
|
+
static async selectConversation(req, res) {
|
|
195
|
+
const service = AiAgentController.resolveService(req, res);
|
|
196
|
+
if (!service)
|
|
197
|
+
return;
|
|
198
|
+
await AiAgentController.syncActiveConversation(service, req);
|
|
199
|
+
const conversation = service.conversationHistory.select(req.user, req.params.conversationId);
|
|
200
|
+
if (!conversation) {
|
|
201
|
+
res.sendStatus(404);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
await AiAgentController.restoreConversation(service, req, conversation.messages);
|
|
205
|
+
res.json({ conversation: AiAgentController.serializeConversation(conversation) });
|
|
206
|
+
}
|
|
207
|
+
/** DELETE /api/ai-assistant/agent/:modelId/conversations/:conversationId */
|
|
208
|
+
static async deleteConversation(req, res) {
|
|
209
|
+
const service = AiAgentController.resolveService(req, res);
|
|
210
|
+
if (!service)
|
|
211
|
+
return;
|
|
212
|
+
const conversationId = req.params.conversationId;
|
|
213
|
+
const active = service.conversationHistory.getActive(req.user);
|
|
214
|
+
if (active.id === conversationId) {
|
|
215
|
+
res.status(400).json({ error: 'Create or select another conversation before deleting this one' });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
res.json({ deleted: service.conversationHistory.remove(req.user, conversationId) });
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* POST /api/ai-assistant/agent/:modelId/runs
|
|
222
|
+
*
|
|
223
|
+
* Starts a turn and answers 202 with the stream URL. The run itself is
|
|
224
|
+
* kicked off after the response so the browser can open the EventSource
|
|
225
|
+
* before the first frame is published.
|
|
226
|
+
*/
|
|
227
|
+
static async startRun(req, res) {
|
|
228
|
+
const service = AiAgentController.resolveService(req, res);
|
|
229
|
+
if (!service || !await AiAgentController.ensureReady(service, res))
|
|
230
|
+
return;
|
|
231
|
+
upload(req, res, async (uploadError) => {
|
|
232
|
+
if (uploadError) {
|
|
233
|
+
const message = uploadError.code === 'LIMIT_FILE_SIZE'
|
|
234
|
+
? `File is too large (max ${Math.round(MAX_FILE_SIZE / 1024 / 1024)}MB)`
|
|
235
|
+
: uploadError.message || 'Upload failed';
|
|
236
|
+
res.status(413).json({ error: message });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const prompt = typeof req.body?.message === 'string' ? req.body.message.trim() : '';
|
|
240
|
+
const files = req.files || [];
|
|
241
|
+
if (!prompt && !files.length) {
|
|
242
|
+
res.status(400).json({ error: 'Message is required' });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const savedFiles = typeof service.saveUploadedFiles === 'function'
|
|
247
|
+
? await service.saveUploadedFiles(req.user, files)
|
|
248
|
+
: [];
|
|
249
|
+
const vision = Boolean(service.getSessionMeta?.(req.user)?.vision);
|
|
250
|
+
const input = buildInput(prompt || 'See the attached files.', files, vision, savedFiles);
|
|
251
|
+
const run = runs.create(req.user.id, service.id);
|
|
252
|
+
const prefix = req.adminizer.config.routePrefix;
|
|
253
|
+
res.status(202).json({
|
|
254
|
+
runId: run.id,
|
|
255
|
+
stream: `${prefix}/api/ai-assistant/agent/${encodeURIComponent(service.id)}/runs/${run.id}/stream`,
|
|
256
|
+
});
|
|
257
|
+
setImmediate(() => {
|
|
258
|
+
void (async () => {
|
|
259
|
+
try {
|
|
260
|
+
await AiAgentController.execute(service, req, input, prompt, (event) => runs.publish(run, event));
|
|
261
|
+
runs.finish(run, { type: 'done' });
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
Adminizer.log.error('AI agent run failed', error);
|
|
265
|
+
runs.finish(run, { type: 'error', error: error?.message || 'Agent run failed' });
|
|
266
|
+
}
|
|
267
|
+
})();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
Adminizer.log.error('AI agent run could not be started', error);
|
|
272
|
+
res.status(500).json({ error: error?.message || 'Failed to start the run' });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/** GET /api/ai-assistant/agent/:modelId/runs/:runId/stream */
|
|
277
|
+
static async streamRun(req, res) {
|
|
278
|
+
const service = AiAgentController.resolveService(req, res);
|
|
279
|
+
if (!service)
|
|
280
|
+
return;
|
|
281
|
+
const run = runs.get(req.params.runId, req.user.id);
|
|
282
|
+
if (!run || run.modelId !== service.id) {
|
|
283
|
+
res.sendStatus(404);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
runs.subscribe(run, req, res);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Streaming agents publish their own frames; a legacy `generateReply` model
|
|
290
|
+
* is wrapped into the same protocol so the panel needs no special case.
|
|
291
|
+
*/
|
|
292
|
+
static async execute(service, req, input, prompt, publish) {
|
|
293
|
+
const commandResult = await service.executeChatCommand(prompt, input, req.user, publish);
|
|
294
|
+
if (commandResult) {
|
|
295
|
+
if (commandResult.text) {
|
|
296
|
+
publish({ type: 'text.delta', text: commandResult.text });
|
|
297
|
+
publish({ type: 'text.done' });
|
|
298
|
+
}
|
|
299
|
+
publish({ type: 'turn.done' });
|
|
300
|
+
if (commandResult.persist !== false && commandResult.text) {
|
|
301
|
+
await AiAgentController.appendCommandToSession(service, req, prompt, commandResult.text);
|
|
302
|
+
}
|
|
303
|
+
service.conversationHistory.saveActive(req.user, AiAgentController.getSessionMessages(service, req));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (service.isStreamingAgent()) {
|
|
307
|
+
await service.streamReply(input, req.user, publish);
|
|
308
|
+
service.conversationHistory.saveActive(req.user, AiAgentController.getSessionMessages(service, req));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const { history } = await req.adminizer.aiAssistantHandler.sendMessage(req.user, service.id, prompt);
|
|
312
|
+
service.conversationHistory.saveActive(req.user, history.map((message) => ({
|
|
313
|
+
role: message.role,
|
|
314
|
+
content: [{ type: 'text', text: message.content }],
|
|
315
|
+
})));
|
|
316
|
+
const reply = history[history.length - 1]?.content ?? '';
|
|
317
|
+
publish({ type: 'text.delta', text: reply });
|
|
318
|
+
publish({ type: 'text.done' });
|
|
319
|
+
publish({ type: 'turn.done' });
|
|
320
|
+
}
|
|
321
|
+
/** Session meta plus the model catalogue and the upload caps of this transport. */
|
|
322
|
+
static async buildMeta(service, req) {
|
|
323
|
+
const availableModels = typeof service.getModelChoices === 'function'
|
|
324
|
+
// Warming the catalogue first means a cold-start meta call reports a
|
|
325
|
+
// real discovered model instead of a configured placeholder.
|
|
326
|
+
? await service.getModelChoices()
|
|
327
|
+
: [];
|
|
328
|
+
const meta = service.getSessionMeta?.(req.user) ?? {
|
|
329
|
+
model: service.id,
|
|
330
|
+
contextWindow: 0,
|
|
331
|
+
vision: false,
|
|
332
|
+
turns: req.adminizer.aiAssistantHandler.getHistory(req.user.id, service.id).length,
|
|
333
|
+
contextTokens: 0,
|
|
334
|
+
totalUsage: {},
|
|
335
|
+
};
|
|
336
|
+
return { ...meta, availableModels, maxFiles: MAX_FILES, maxFileSize: MAX_FILE_SIZE };
|
|
337
|
+
}
|
|
338
|
+
static getSessionMessages(service, req) {
|
|
339
|
+
if (typeof service.getSessionHistory === 'function')
|
|
340
|
+
return service.getSessionHistory(req.user);
|
|
341
|
+
return req.adminizer.aiAssistantHandler.getHistory(req.user.id, service.id).map((message) => ({
|
|
342
|
+
role: message.role,
|
|
343
|
+
content: [{ type: 'text', text: message.content }],
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The persisted dialog is restored into a fresh agent session before it is
|
|
348
|
+
* saved again. This keeps initial demo dialogs and database-backed history
|
|
349
|
+
* intact when an agent has not created its in-memory session yet.
|
|
350
|
+
*/
|
|
351
|
+
static async syncActiveConversation(service, req) {
|
|
352
|
+
const messages = AiAgentController.getSessionMessages(service, req);
|
|
353
|
+
const active = service.conversationHistory.getActive(req.user);
|
|
354
|
+
if (messages.length === 0 && active.messages.length > 0) {
|
|
355
|
+
await AiAgentController.restoreConversation(service, req, active.messages);
|
|
356
|
+
return active.messages;
|
|
357
|
+
}
|
|
358
|
+
service.conversationHistory.saveActive(req.user, messages);
|
|
359
|
+
return messages;
|
|
360
|
+
}
|
|
361
|
+
static async restoreConversation(service, req, messages) {
|
|
362
|
+
if (typeof service.restoreSessionHistory === 'function') {
|
|
363
|
+
await service.restoreSessionHistory(req.user, messages);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (typeof service.getSessionHistory === 'function') {
|
|
367
|
+
throw new Error('This agent does not support restoring conversations yet.');
|
|
368
|
+
}
|
|
369
|
+
req.adminizer.aiAssistantHandler.replaceHistory(req.user.id, service.id, messages.map((message) => {
|
|
370
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
371
|
+
const text = content.find((part) => part?.type === 'text')?.text ?? '';
|
|
372
|
+
return { id: `${Date.now()}-${Math.random()}`, role: message.role, content: String(text), timestamp: new Date(), modelId: service.id };
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
/** Persists command turns just like ordinary chat turns for both agent kinds. */
|
|
376
|
+
static async appendCommandToSession(service, req, prompt, response) {
|
|
377
|
+
const messages = [
|
|
378
|
+
...AiAgentController.getSessionMessages(service, req),
|
|
379
|
+
{ role: 'user', content: [{ type: 'text', text: prompt }] },
|
|
380
|
+
{ role: 'assistant', content: [{ type: 'text', text: response }] },
|
|
381
|
+
];
|
|
382
|
+
await AiAgentController.restoreConversation(service, req, messages);
|
|
383
|
+
}
|
|
384
|
+
static serializeConversation(conversation) {
|
|
385
|
+
return conversation;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Resolves the registered model service for this request, answering the
|
|
389
|
+
* response itself when the assistant is off, unknown or unusable.
|
|
390
|
+
*/
|
|
391
|
+
static resolveService(req, res) {
|
|
392
|
+
if (!req.adminizer.config.aiAssistant?.enabled) {
|
|
393
|
+
res.status(503).json({ error: 'AI assistant is disabled' });
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
const handler = req.adminizer.aiAssistantHandler;
|
|
397
|
+
if (!handler) {
|
|
398
|
+
res.status(500).json({ error: 'AI assistant is not available' });
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
const modelId = req.params?.modelId;
|
|
402
|
+
const service = modelId ? handler.getModel(modelId) : undefined;
|
|
403
|
+
if (!service) {
|
|
404
|
+
res.status(404).json({ error: `AI assistant model not found: ${modelId}` });
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
return service;
|
|
408
|
+
}
|
|
409
|
+
/** Endpoints that talk to the LLM also require a provisioned connection. */
|
|
410
|
+
static async ensureReady(service, res) {
|
|
411
|
+
if (typeof service.getConnectionStatus !== 'function')
|
|
412
|
+
return true;
|
|
413
|
+
const status = await service.getConnectionStatus();
|
|
414
|
+
if (status?.state === 'ready')
|
|
415
|
+
return true;
|
|
416
|
+
res.status(503).json({
|
|
417
|
+
error: 'The assistant is not connected to an LLM endpoint yet.',
|
|
418
|
+
status,
|
|
419
|
+
});
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
/** Admin UI language, resolved the way Inertia shares it with the pages. */
|
|
423
|
+
static resolveLocale(req) {
|
|
424
|
+
const translation = req.adminizer.config.translation;
|
|
425
|
+
const defaultLocale = typeof translation !== 'boolean' ? translation?.defaultLocale ?? 'en' : 'en';
|
|
426
|
+
const available = typeof translation !== 'boolean' ? translation?.locales ?? [] : [];
|
|
427
|
+
const requested = String(req.user?.locale || defaultLocale);
|
|
428
|
+
const normalized = requested.replace('_', '-').split('-')[0];
|
|
429
|
+
if (available.includes(requested))
|
|
430
|
+
return requested;
|
|
431
|
+
if (available.includes(normalized))
|
|
432
|
+
return normalized;
|
|
433
|
+
return defaultLocale;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
export declare class AiAssistantController {
|
|
2
2
|
static getModels(req: ReqType, res: ResType): Promise<ResType>;
|
|
3
|
-
static getHistory(req: ReqType, res: ResType): Promise<ResType>;
|
|
4
|
-
static sendMessage(req: ReqType, res: ResType): Promise<ResType>;
|
|
5
|
-
static resetHistory(req: ReqType, res: ResType): Promise<ResType>;
|
|
6
|
-
private static serializeMessage;
|
|
7
3
|
}
|
|
@@ -9,84 +9,10 @@ export class AiAssistantController {
|
|
|
9
9
|
Adminizer.log.warn('AI assistant handler is not initialized');
|
|
10
10
|
return res.json([]);
|
|
11
11
|
}
|
|
12
|
-
const models = handler
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const models = (await Promise.all(handler.getModels().map(async (model) => ({
|
|
13
|
+
model,
|
|
14
|
+
allowed: await req.adminizer.accessRightsHelper.hasPermission(`ai-assistant-${model.id}`, req.user),
|
|
15
|
+
})))).filter(({ allowed }) => allowed).map(({ model }) => model);
|
|
15
16
|
return res.json(models);
|
|
16
17
|
}
|
|
17
|
-
static async getHistory(req, res) {
|
|
18
|
-
if (!req.adminizer.config.aiAssistant?.enabled) {
|
|
19
|
-
return res.json({ history: [] });
|
|
20
|
-
}
|
|
21
|
-
const { modelId } = req.params;
|
|
22
|
-
if (!modelId) {
|
|
23
|
-
return res.status(400).json({ error: 'Model id is required' });
|
|
24
|
-
}
|
|
25
|
-
if (!req.adminizer.accessRightsHelper.hasPermission(`ai-assistant-${modelId}`, req.user)) {
|
|
26
|
-
return res.status(403).json({ error: 'Forbidden' });
|
|
27
|
-
}
|
|
28
|
-
const handler = req.adminizer.aiAssistantHandler;
|
|
29
|
-
if (!handler) {
|
|
30
|
-
return res.status(500).json({ error: 'AI assistant is not available' });
|
|
31
|
-
}
|
|
32
|
-
const history = handler.getHistory(req.user.id, modelId).map(AiAssistantController.serializeMessage);
|
|
33
|
-
return res.json({ history });
|
|
34
|
-
}
|
|
35
|
-
static async sendMessage(req, res) {
|
|
36
|
-
if (!req.adminizer.config.aiAssistant?.enabled) {
|
|
37
|
-
return res.status(503).json({ error: 'AI assistant is disabled' });
|
|
38
|
-
}
|
|
39
|
-
const { modelId, message } = req.body;
|
|
40
|
-
if (!modelId || typeof modelId !== 'string') {
|
|
41
|
-
return res.status(400).json({ error: 'Model id is required' });
|
|
42
|
-
}
|
|
43
|
-
if (!message || typeof message !== 'string' || message.trim().length === 0) {
|
|
44
|
-
return res.status(400).json({ error: 'Message is required' });
|
|
45
|
-
}
|
|
46
|
-
if (!req.adminizer.accessRightsHelper.hasPermission(`ai-assistant-${modelId}`, req.user)) {
|
|
47
|
-
return res.status(403).json({ error: 'Forbidden' });
|
|
48
|
-
}
|
|
49
|
-
const handler = req.adminizer.aiAssistantHandler;
|
|
50
|
-
if (!handler) {
|
|
51
|
-
return res.status(500).json({ error: 'AI assistant is not available' });
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
const response = await handler.sendMessage(req.user, modelId, message.trim());
|
|
55
|
-
return res.json({
|
|
56
|
-
modelId,
|
|
57
|
-
history: response.history.map(AiAssistantController.serializeMessage),
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
Adminizer.log.error('AI assistant error', error);
|
|
62
|
-
return res.status(500).json({ error: 'Failed to process request' });
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
static async resetHistory(req, res) {
|
|
66
|
-
if (!req.adminizer.config.aiAssistant?.enabled) {
|
|
67
|
-
return res.status(503).json({ error: 'AI assistant is disabled' });
|
|
68
|
-
}
|
|
69
|
-
const { modelId } = req.params;
|
|
70
|
-
if (!modelId) {
|
|
71
|
-
return res.status(400).json({ error: 'Model id is required' });
|
|
72
|
-
}
|
|
73
|
-
if (!req.adminizer.accessRightsHelper.hasPermission(`ai-assistant-${modelId}`, req.user)) {
|
|
74
|
-
return res.status(403).json({ error: 'Forbidden' });
|
|
75
|
-
}
|
|
76
|
-
const handler = req.adminizer.aiAssistantHandler;
|
|
77
|
-
if (!handler) {
|
|
78
|
-
return res.status(500).json({ error: 'AI assistant is not available' });
|
|
79
|
-
}
|
|
80
|
-
handler.resetHistory(req.user.id, modelId);
|
|
81
|
-
return res.json({ history: [] });
|
|
82
|
-
}
|
|
83
|
-
static serializeMessage(message) {
|
|
84
|
-
return {
|
|
85
|
-
id: message.id,
|
|
86
|
-
role: message.role,
|
|
87
|
-
content: message.content,
|
|
88
|
-
timestamp: message.timestamp instanceof Date ? message.timestamp.toISOString() : message.timestamp,
|
|
89
|
-
modelId: message.modelId,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
18
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare function docsList(req: ReqType, res: ResType): Promise<ResType>;
|
|
2
|
+
export declare function docsKeywords(req: ReqType, res: ResType): Promise<ResType>;
|
|
3
|
+
export declare function docsSearch(req: ReqType, res: ResType): Promise<ResType>;
|
|
4
|
+
export declare function docsContext(req: ReqType, res: ResType): Promise<ResType>;
|
|
5
|
+
export declare function docsDoc(req: ReqType, res: ResType): Promise<ResType>;
|
|
6
|
+
/**
|
|
7
|
+
* Resolves references written inside a document to document ids. How a
|
|
8
|
+
* reference is spelled is the implementation's business; the viewer only
|
|
9
|
+
* learns which of them point at a document this user may open — the rest stay
|
|
10
|
+
* unresolved and are rendered as plain text.
|
|
11
|
+
*/
|
|
12
|
+
export declare function docsResolve(req: ReqType, res: ResType): Promise<ResType>;
|
|
13
|
+
/**
|
|
14
|
+
* The viewer page. The document itself is fetched by the page through the API,
|
|
15
|
+
* so a deep link only has to name the document it opens with.
|
|
16
|
+
*/
|
|
17
|
+
export declare function docsPage(req: ReqType): Promise<import("express").Response<any, Record<string, any>>>;
|
|
18
|
+
/**
|
|
19
|
+
* Structure of a model resource as this user is allowed to see it — the same
|
|
20
|
+
* fields DataAccessor would hand to the list page, nothing more.
|
|
21
|
+
*/
|
|
22
|
+
export declare function docsSchema(req: ReqType, res: ResType): Promise<ResType>;
|