@conduction/nextcloud-vue 2.119.0 → 2.121.0
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/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue.js +9 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue2.js +226 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue3.js +211 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue.js +9 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue2.js +64 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue2.js.map +1 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue3.js +89 -0
- package/dist/esm/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue3.js.map +1 -0
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +53 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +546 -523
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +9 -0
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue.js +9 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue.js.map +1 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue2.js +257 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue2.js.map +1 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue3.js +266 -0
- package/dist/esm/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js +14 -7
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnObjectSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue.js +9 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue2.js +278 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue3.js +128 -0
- package/dist/esm/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js +9 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue.js.map +1 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js +214 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue2.js.map +1 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js +125 -0
- package/dist/esm/components/CnReadinessChecklist/CnReadinessChecklist.vue3.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js +9 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js +163 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js +131 -0
- package/dist/esm/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js +127 -0
- package/dist/esm/components/CnSchemaRulesWidget/schemaRules.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js +9 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js +82 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue2.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js +61 -0
- package/dist/esm/components/CnSearchIndexStatus/CnSearchIndexStatus.vue3.js.map +1 -0
- package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js +60 -0
- package/dist/esm/components/CnSearchIndexStatus/searchIndexStatus.js.map +1 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js +10 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue.js.map +1 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js +226 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue2.js.map +1 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js +126 -0
- package/dist/esm/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue.js +10 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue2.js +944 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue3.js +276 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineEditor.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue.js +9 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue2.js +354 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue3.js +392 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineFields.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue.js +9 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue2.js +180 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue3.js +208 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachineNode.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue.js +9 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue2.js +244 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue2.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue3.js +76 -0
- package/dist/esm/components/CnStateMachineEditor/CnStateMachinePanel.vue3.js.map +1 -0
- package/dist/esm/components/CnStateMachineEditor/stateMachineFields.js +290 -0
- package/dist/esm/components/CnStateMachineEditor/stateMachineFields.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js +9 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js +448 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js +303 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineManage.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +19 -2
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +73 -62
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +6 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +42 -32
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/composables/indexSources.js +116 -16
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +207 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +207 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +951 -16
- package/dist/esm/utils/aiFeatures.js +245 -0
- package/dist/esm/utils/aiFeatures.js.map +1 -0
- package/dist/esm/utils/aiSuggestionTracker.js +182 -0
- package/dist/esm/utils/aiSuggestionTracker.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectPermissions.js +552 -0
- package/dist/esm/utils/objectPermissions.js.map +1 -0
- package/dist/esm/utils/objectTranslations.js +180 -0
- package/dist/esm/utils/objectTranslations.js.map +1 -0
- package/dist/esm/utils/stateMachineValidation.js +254 -0
- package/dist/esm/utils/stateMachineValidation.js.map +1 -0
- package/dist/esm/utils/widgetDispatch.js +3 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +23038 -14567
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +951 -16
- package/l10n/en.json +207 -1
- package/l10n/nl.json +207 -1
- package/package.json +1 -1
- package/src/components/CnAiFeaturesWidget/CnAiFeaturesWidget.vue +416 -0
- package/src/components/CnAiFeaturesWidget/CnAiSuggestionDecision.vue +136 -0
- package/src/components/CnAiFeaturesWidget/__tests__/CnAiFeaturesWidget.spec.js +213 -0
- package/src/components/CnAiFeaturesWidget/index.js +14 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +74 -3
- package/src/components/CnIndexPage/useNamedSource.js +9 -0
- package/src/components/CnObjectPermissionsTab/CnObjectPermissionsTab.vue +499 -0
- package/src/components/CnObjectPermissionsTab/__tests__/CnObjectPermissionsTab.spec.js +186 -0
- package/src/components/CnObjectPermissionsTab/index.js +13 -0
- package/src/components/CnObjectSidebar/CnObjectSidebar.vue +8 -1
- package/src/components/CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue +424 -0
- package/src/components/CnObjectTranslationsWidget/__tests__/CnObjectTranslationsWidget.spec.js +189 -0
- package/src/components/CnObjectTranslationsWidget/index.js +13 -0
- package/src/components/CnReadinessChecklist/CnReadinessChecklist.vue +335 -0
- package/src/components/CnReadinessChecklist/index.js +9 -0
- package/src/components/CnSchemaRulesWidget/CnSchemaRulesWidget.vue +300 -0
- package/src/components/CnSchemaRulesWidget/index.js +10 -0
- package/src/components/CnSchemaRulesWidget/schemaRules.js +123 -0
- package/src/components/CnSearchIndexStatus/CnSearchIndexStatus.vue +160 -0
- package/src/components/CnSearchIndexStatus/index.js +10 -0
- package/src/components/CnSearchIndexStatus/searchIndexStatus.js +56 -0
- package/src/components/CnShippedConfigurationSection/CnShippedConfigurationSection.vue +328 -0
- package/src/components/CnShippedConfigurationSection/index.js +9 -0
- package/src/components/CnStateMachineEditor/CnStateMachineEditor.vue +1332 -0
- package/src/components/CnStateMachineEditor/CnStateMachineFields.vue +791 -0
- package/src/components/CnStateMachineEditor/CnStateMachineNode.vue +442 -0
- package/src/components/CnStateMachineEditor/CnStateMachinePanel.vue +326 -0
- package/src/components/CnStateMachineEditor/index.js +8 -0
- package/src/components/CnStateMachineEditor/stateMachineFields.js +287 -0
- package/src/components/CnTimelineWidget/CnTimelineManage.vue +827 -0
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +29 -3
- package/src/components/CnWidgetGrid/builtInWidgets.js +6 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +10 -0
- package/src/components/index.js +8 -0
- package/src/composables/indexSources.js +116 -16
- package/src/css/index-page.css +7 -0
- package/src/index.js +9 -0
- package/src/utils/aiFeatures.js +241 -0
- package/src/utils/aiSuggestionTracker.js +178 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/objectPermissions.js +548 -0
- package/src/utils/objectTranslations.js +176 -0
- package/src/utils/stateMachineValidation.js +250 -0
- package/src/utils/widgetDispatch.js +3 -1
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
<!-- SPDX-License-Identifier: EUPL-1.2 -->
|
|
2
2
|
<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->
|
|
3
3
|
<template>
|
|
4
|
-
<
|
|
4
|
+
<CnTimelineManage v-if="manage"
|
|
5
|
+
:register="resolvedRegister"
|
|
6
|
+
:schema="resolvedSchema"
|
|
7
|
+
:objectId="resolvedObjectId"
|
|
8
|
+
:apiBase="apiBase"
|
|
9
|
+
:content="content" />
|
|
10
|
+
<section v-else
|
|
11
|
+
class="cn-timeline-widget"
|
|
12
|
+
data-testid="cn-timeline-widget"
|
|
13
|
+
:aria-busy="loading ? 'true' : 'false'">
|
|
5
14
|
<h3 v-if="resolvedTitle" class="cn-timeline-widget__title">
|
|
6
15
|
{{ resolvedTitle }}
|
|
7
16
|
</h3>
|
|
@@ -53,6 +62,7 @@
|
|
|
53
62
|
<script>
|
|
54
63
|
import { getLanguage, translate as t } from '@nextcloud/l10n'
|
|
55
64
|
import { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
|
|
65
|
+
import CnTimelineManage from './CnTimelineManage.vue'
|
|
56
66
|
import { auditEvents, fieldEvents, listEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'
|
|
57
67
|
import { userLocale } from '../../utils/userLocale.js'
|
|
58
68
|
|
|
@@ -74,6 +84,12 @@ import { userLocale } from '../../utils/userLocale.js'
|
|
|
74
84
|
* Oldest first by default (`order: 'desc'` flips it). A moment after now is
|
|
75
85
|
* marked upcoming, in words as well as style.
|
|
76
86
|
*
|
|
87
|
+
* `manage: true` swaps the read view for the manage view (CnTimelineManage)
|
|
88
|
+
* over OpenRegister's timeline alone: kind chips, a visibility filter, pin,
|
|
89
|
+
* follow-up, a note composer with standard text blocks, CSV export, and
|
|
90
|
+
* `entryActions` that open a registry modal on an entry. The manage controls
|
|
91
|
+
* are drawn only when the timeline read answers `canManage: true`.
|
|
92
|
+
*
|
|
77
93
|
* ```json
|
|
78
94
|
* { "type": "timeline", "content": {
|
|
79
95
|
* "title": "Timeline",
|
|
@@ -95,6 +111,7 @@ export default {
|
|
|
95
111
|
name: 'CnTimelineWidget',
|
|
96
112
|
|
|
97
113
|
components: {
|
|
114
|
+
CnTimelineManage,
|
|
98
115
|
NcLoadingIcon,
|
|
99
116
|
NcNoteCard,
|
|
100
117
|
},
|
|
@@ -123,7 +140,7 @@ export default {
|
|
|
123
140
|
object: { type: Object, default: null },
|
|
124
141
|
/** Card title override. */
|
|
125
142
|
title: { type: String, default: '' },
|
|
126
|
-
/** Stored widget content: `{ title, fields, related, auditTrail, timeline, order }`. */
|
|
143
|
+
/** Stored widget content: `{ title, fields, related, auditTrail, timeline, order, manage, entryActions, ... }`. */
|
|
127
144
|
content: { type: Object, default: () => ({}) },
|
|
128
145
|
/** OpenRegister API base. */
|
|
129
146
|
apiBase: { type: String, default: '/apps/openregister/api' },
|
|
@@ -172,6 +189,15 @@ export default {
|
|
|
172
189
|
return this.object || this.ctx.object || this.ctx.objectData || this.fetchedObject || null
|
|
173
190
|
},
|
|
174
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Whether the manage view renders instead of the read view.
|
|
194
|
+
*
|
|
195
|
+
* @return {boolean}
|
|
196
|
+
*/
|
|
197
|
+
manage() {
|
|
198
|
+
return this.content.manage === true
|
|
199
|
+
},
|
|
200
|
+
|
|
175
201
|
resolvedTitle() {
|
|
176
202
|
const raw = this.title || this.content.title || ''
|
|
177
203
|
return raw ? this.translate(raw) : ''
|
|
@@ -313,7 +339,7 @@ export default {
|
|
|
313
339
|
const register = this.resolvedRegister
|
|
314
340
|
const schema = this.resolvedSchema
|
|
315
341
|
const seq = ++this.loadSeq
|
|
316
|
-
if (!id || !register || !schema) {
|
|
342
|
+
if (!id || !register || !schema || this.manage) {
|
|
317
343
|
return
|
|
318
344
|
}
|
|
319
345
|
const [{ default: axios }, { generateUrl }] = await Promise.all([
|
|
@@ -26,6 +26,7 @@ import CnNoticeWidget from '../CnNoticeWidget/CnNoticeWidget.vue'
|
|
|
26
26
|
import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
|
|
27
27
|
import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
|
|
28
28
|
import CnObjectMetadataWidget from '../CnObjectMetadataWidget/CnObjectMetadataWidget.vue'
|
|
29
|
+
import CnObjectTranslationsWidget from '../CnObjectTranslationsWidget/CnObjectTranslationsWidget.vue'
|
|
29
30
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
30
31
|
import CnSavedViewAlertWidget from '../CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue'
|
|
31
32
|
import CnTextWidget from '../CnTextWidget/CnTextWidget.vue'
|
|
@@ -74,6 +75,10 @@ import CnWidgetObjectTable from '../CnWidgetObjectTable/CnWidgetObjectTable.vue'
|
|
|
74
75
|
* data — the intended remedy for a deep menu group collapsed into one
|
|
75
76
|
* top-level entry via `menu-layout.json` relocations.
|
|
76
77
|
*
|
|
78
|
+
* The `object-translations` key places CnObjectTranslationsWidget: the
|
|
79
|
+
* object's translatable properties per register language, with completeness
|
|
80
|
+
* chips and one save (register / schema / objectId via the detail-context merge).
|
|
81
|
+
*
|
|
77
82
|
* @type {Record<string, import('vue').Component>}
|
|
78
83
|
*/
|
|
79
84
|
export const BUILT_IN_WIDGETS = {
|
|
@@ -97,4 +102,5 @@ export const BUILT_IN_WIDGETS = {
|
|
|
97
102
|
divider: CnDividerWidget,
|
|
98
103
|
'saved-view-alert': CnSavedViewAlertWidget,
|
|
99
104
|
exports: CnExportsWidget,
|
|
105
|
+
'object-translations': CnObjectTranslationsWidget,
|
|
100
106
|
}
|
|
@@ -37,6 +37,7 @@ import CnRelatedObjectsWidgetForm from '../CnRelatedObjectsWidgetForm/CnRelatedO
|
|
|
37
37
|
import CnRetentionWidget from '../CnRetentionWidget/CnRetentionWidget.vue'
|
|
38
38
|
import CnSavedViewWidget2 from '../CnSavedViewWidget/CnSavedViewWidget.vue'
|
|
39
39
|
import CnSavedViewWidgetForm2 from '../CnSavedViewWidgetForm/CnSavedViewWidgetForm.vue'
|
|
40
|
+
import CnSchemaRulesWidget from '../CnSchemaRulesWidget/CnSchemaRulesWidget.vue'
|
|
40
41
|
import CnStackedBarWidget from '../CnStackedBarWidget/CnStackedBarWidget.vue'
|
|
41
42
|
import CnStackedBarWidgetForm from '../CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue'
|
|
42
43
|
import CnStagesWidget from '../CnStagesWidget/CnStagesWidget.vue'
|
|
@@ -311,6 +312,15 @@ registerDashboardWidget('retention', {
|
|
|
311
312
|
ownsTitle: true,
|
|
312
313
|
})
|
|
313
314
|
|
|
315
|
+
registerDashboardWidget('schema-rules', {
|
|
316
|
+
renderer: CnSchemaRulesWidget,
|
|
317
|
+
form: null,
|
|
318
|
+
defaultContent: { register: '', schema: '', objectId: '' },
|
|
319
|
+
displayName: 'Schema rules',
|
|
320
|
+
icon: 'ScaleBalance',
|
|
321
|
+
surfaces: ['detail-page'],
|
|
322
|
+
})
|
|
323
|
+
|
|
314
324
|
registerDashboardWidget('plan', {
|
|
315
325
|
renderer: CnPlanWidget,
|
|
316
326
|
form: null,
|
package/src/components/index.js
CHANGED
|
@@ -77,6 +77,7 @@ export { CnRegisterSchemaSelect } from './CnRegisterSchemaSelect/index.js'
|
|
|
77
77
|
export { CnThemePreview } from './CnThemePreview/index.js'
|
|
78
78
|
export { CnRelationshipGraph } from './CnRelationshipGraph/index.js'
|
|
79
79
|
export { CnGraphCanvas } from './CnGraphCanvas/index.js'
|
|
80
|
+
export { CnStateMachineEditor } from './CnStateMachineEditor/index.js'
|
|
80
81
|
export { CnFlowDetail, CnFlowIndexPage, CnFlowSidebar } from './CnFlowDetail/index.js'
|
|
81
82
|
export { CnIcon, ICON_MAP, registerIcons } from './CnIcon/index.js'
|
|
82
83
|
export {
|
|
@@ -167,7 +168,10 @@ export { CnJourneyDialog } from './CnJourneyDialog/index.js'
|
|
|
167
168
|
export { CnProcessSteps } from './CnProcessSteps/index.js'
|
|
168
169
|
export { CnSubObjectsField } from './CnSubObjectsField/index.js'
|
|
169
170
|
export { CnIntegrationTab } from './CnIntegrationTab/index.js'
|
|
171
|
+
export { CnAiFeaturesWidget } from './CnAiFeaturesWidget/index.js'
|
|
170
172
|
export { CnObjectAccessTab } from './CnObjectAccessTab/index.js'
|
|
173
|
+
export { CnObjectPermissionsTab } from './CnObjectPermissionsTab/index.js'
|
|
174
|
+
export { CnObjectTranslationsWidget } from './CnObjectTranslationsWidget/index.js'
|
|
171
175
|
export { CnIntegrationCard } from './CnIntegrationCard/index.js'
|
|
172
176
|
export { CnIntegrationWidgetGrid } from './CnIntegrationWidgetGrid/index.js'
|
|
173
177
|
export { CnIntegrationWidget } from './CnIntegrationWidget/index.js'
|
|
@@ -460,3 +464,7 @@ export { CnBrandStripe } from './CnBrandStripe/index.js'
|
|
|
460
464
|
export { CnDuplicateWarning } from './CnDuplicateWarning/index.js'
|
|
461
465
|
export { CnEndpointNotice } from './CnEndpointNotice/index.js'
|
|
462
466
|
export { CnCountTablesReport } from './CnCountTablesReport/index.js'
|
|
467
|
+
export { CnShippedConfigurationSection } from './CnShippedConfigurationSection/index.js'
|
|
468
|
+
export { CnSearchIndexStatus } from './CnSearchIndexStatus/index.js'
|
|
469
|
+
export { CnSchemaRulesWidget } from './CnSchemaRulesWidget/index.js'
|
|
470
|
+
export { CnReadinessChecklist } from './CnReadinessChecklist/index.js'
|
|
@@ -737,6 +737,14 @@ function readRowsPath(body, path) {
|
|
|
737
737
|
return String(path).split('.').reduce((value, key) => (value && typeof value === 'object' ? value[key] : undefined), body)
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
+
/**
|
|
741
|
+
* The `sourceConfig` keys the endpoint source reads itself; every other key
|
|
742
|
+
* goes along as a query parameter.
|
|
743
|
+
*
|
|
744
|
+
* @type {Array<string>}
|
|
745
|
+
*/
|
|
746
|
+
const ENDPOINT_SOURCE_KEYS = ['url', 'rowsPath', 'totalPath', 'searchParam', 'searchFields', 'notePath', 'noteText', 'refusedText']
|
|
747
|
+
|
|
740
748
|
/**
|
|
741
749
|
* The endpoint source: rows from an app's own endpoint
|
|
742
750
|
* (`entitySource: "endpoint"`), for a list that is not OpenRegister objects,
|
|
@@ -759,25 +767,46 @@ function readRowsPath(body, path) {
|
|
|
759
767
|
* request with that tab's filter, its total read at `sourceConfig.totalPath`
|
|
760
768
|
* (`total` by default), else the number of rows the answer holds.
|
|
761
769
|
*
|
|
770
|
+
* Three more keys shape what the page says around the rows:
|
|
771
|
+
* - `searchParam`: the page's search box sends its term as this parameter
|
|
772
|
+
* (`sender`), so the endpoint does the searching.
|
|
773
|
+
* - `notePath` and `noteText`: a value read off the answer (`filterOrder`) and
|
|
774
|
+
* the sentence it is said in ("Filters run in this order: {order}", its one
|
|
775
|
+
* placeholder taking the value); a list
|
|
776
|
+
* is joined with commas. Shown above the rows.
|
|
777
|
+
* - `refusedText`: what a refused load (403) says when the endpoint gave no
|
|
778
|
+
* `message` of its own. A refusal is not an empty list, so the page shows it
|
|
779
|
+
* instead of the rows, and a failed load is not one either.
|
|
780
|
+
* - `searchFields`: the filters the endpoint takes, in the same shape a
|
|
781
|
+
* registered source declares them (`{ status: { param: 'status', single:
|
|
782
|
+
* true } }`), so a filter strip or sidebar on the page narrows the
|
|
783
|
+
* endpoint's query.
|
|
784
|
+
*
|
|
785
|
+
* @param {object} [declared] The page's `sourceConfig`, for `searchParam` and `searchFields`.
|
|
786
|
+
* @param {object} [context] The page's helpers; `translate` is the app's catalogue.
|
|
762
787
|
* @return {object} The source adapter.
|
|
763
788
|
* @spec openspec/changes/index-endpoint-source/specs/index-page/spec.md#requirement-an-index-page-can-list-rows-from-an-app-endpoint
|
|
764
789
|
*/
|
|
765
|
-
function endpointSource() {
|
|
766
|
-
const state = reactive({ rows: [], loading: false })
|
|
790
|
+
function endpointSource(declared = {}, context = {}) {
|
|
791
|
+
const state = reactive({ rows: [], loading: false, refusal: '', failed: false, note: '' })
|
|
792
|
+
const translate = typeof context.translate === 'function' ? context.translate : (text) => text
|
|
793
|
+
const searchParam = typeof declared.searchParam === 'string' && declared.searchParam !== '' ? declared.searchParam : null
|
|
794
|
+
const searchFields = declared.searchFields && typeof declared.searchFields === 'object' && !Array.isArray(declared.searchFields) ? declared.searchFields : null
|
|
767
795
|
let seq = 0
|
|
768
796
|
let warned = false
|
|
769
797
|
|
|
770
798
|
/**
|
|
771
|
-
* The query parameters for a config: every key but the
|
|
772
|
-
* empty values left out, a list joined by
|
|
799
|
+
* The query parameters for a config: every key but the source's own
|
|
800
|
+
* (`url`, the paths, the texts), empty values left out, a list joined by
|
|
801
|
+
* commas.
|
|
773
802
|
*
|
|
774
|
-
* @param {object}
|
|
803
|
+
* @param {object} config The config.
|
|
775
804
|
* @return {object} The parameters.
|
|
776
805
|
*/
|
|
777
|
-
const paramsOf = (
|
|
806
|
+
const paramsOf = (config) => {
|
|
778
807
|
const params = {}
|
|
779
|
-
for (const [key, value] of Object.entries(
|
|
780
|
-
if (value === undefined || value === null || value === '') {
|
|
808
|
+
for (const [key, value] of Object.entries(config)) {
|
|
809
|
+
if (ENDPOINT_SOURCE_KEYS.includes(key) || value === undefined || value === null || value === '') {
|
|
781
810
|
continue
|
|
782
811
|
}
|
|
783
812
|
params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value)
|
|
@@ -797,15 +826,61 @@ function endpointSource() {
|
|
|
797
826
|
return typeof url === 'string' && url !== '' && !Object.hasOwn(config, 'entitySource')
|
|
798
827
|
}
|
|
799
828
|
|
|
829
|
+
/**
|
|
830
|
+
* The note read off an answer, in the page's sentence.
|
|
831
|
+
*
|
|
832
|
+
* @param {object} body The response body.
|
|
833
|
+
* @param {string} [path] Where the value sits.
|
|
834
|
+
* @param {string} [text] The sentence, with one `{placeholder}` where the value goes.
|
|
835
|
+
* @return {string} The note, or '' when there is nothing to say.
|
|
836
|
+
*/
|
|
837
|
+
const noteOf = (body, path, text) => {
|
|
838
|
+
if (!path || !body || typeof body !== 'object' || Array.isArray(body)) {
|
|
839
|
+
return ''
|
|
840
|
+
}
|
|
841
|
+
const raw = readRowsPath(body, path)
|
|
842
|
+
const value = Array.isArray(raw) ? raw.filter((v) => v !== null && v !== undefined && v !== '').join(', ') : (raw === null || raw === undefined ? '' : String(raw))
|
|
843
|
+
if (value === '') {
|
|
844
|
+
return ''
|
|
845
|
+
}
|
|
846
|
+
return text ? translate(text).replace(/\{\w+\}/, value) : value
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* The sentence a refused load is said in: the endpoint's own, else the
|
|
851
|
+
* page's, else the library's.
|
|
852
|
+
*
|
|
853
|
+
* @param {object} body The refusal's body.
|
|
854
|
+
* @param {string} [text] The page's `refusedText`.
|
|
855
|
+
* @return {string} The sentence.
|
|
856
|
+
*/
|
|
857
|
+
const refusalOf = (body, text) => {
|
|
858
|
+
if (body && typeof body.message === 'string' && body.message.trim() !== '') {
|
|
859
|
+
return body.message
|
|
860
|
+
}
|
|
861
|
+
return text ? translate(text) : t('nextcloud-vue', 'You may not read this list.')
|
|
862
|
+
}
|
|
863
|
+
|
|
800
864
|
return {
|
|
865
|
+
/**
|
|
866
|
+
* The parameter the page's search term is sent as, or null when this
|
|
867
|
+
* endpoint takes no search.
|
|
868
|
+
*/
|
|
869
|
+
searchParam,
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* The page filters this endpoint takes, and the parameter each becomes.
|
|
873
|
+
*/
|
|
874
|
+
searchFields,
|
|
875
|
+
|
|
801
876
|
/**
|
|
802
877
|
* Fetch the rows.
|
|
803
878
|
*
|
|
804
879
|
* @param {object} config The page's source config with the tab filter merged in.
|
|
805
|
-
* @return {Promise<void>} Resolves when loaded; a failed request leaves no rows and rethrows.
|
|
880
|
+
* @return {Promise<void>} Resolves when loaded or refused; a failed request leaves no rows and rethrows.
|
|
806
881
|
*/
|
|
807
882
|
load: async (config) => {
|
|
808
|
-
const { url, rowsPath,
|
|
883
|
+
const { url, rowsPath, notePath, noteText, refusedText } = config || {}
|
|
809
884
|
const mine = ++seq
|
|
810
885
|
if (!requestable(config)) {
|
|
811
886
|
state.rows = []
|
|
@@ -817,7 +892,7 @@ function endpointSource() {
|
|
|
817
892
|
}
|
|
818
893
|
return
|
|
819
894
|
}
|
|
820
|
-
const params = paramsOf(
|
|
895
|
+
const params = paramsOf(config)
|
|
821
896
|
state.loading = true
|
|
822
897
|
try {
|
|
823
898
|
const { default: axios } = await import('@nextcloud/axios')
|
|
@@ -826,12 +901,22 @@ function endpointSource() {
|
|
|
826
901
|
const rows = Array.isArray(body) ? body : readRowsPath(body, rowsPath || 'results')
|
|
827
902
|
if (mine === seq) {
|
|
828
903
|
state.rows = Array.isArray(rows) ? rows : []
|
|
904
|
+
state.refusal = ''
|
|
905
|
+
state.failed = false
|
|
906
|
+
state.note = noteOf(body, notePath, noteText)
|
|
829
907
|
}
|
|
830
908
|
} catch (error) {
|
|
831
|
-
if (mine
|
|
832
|
-
|
|
909
|
+
if (mine !== seq) {
|
|
910
|
+
return
|
|
911
|
+
}
|
|
912
|
+
state.rows = []
|
|
913
|
+
state.note = ''
|
|
914
|
+
const refused = error && error.response && error.response.status === 403
|
|
915
|
+
state.refusal = refused ? refusalOf(error.response.data, refusedText) : ''
|
|
916
|
+
state.failed = !refused
|
|
917
|
+
if (!refused) {
|
|
918
|
+
throw error
|
|
833
919
|
}
|
|
834
|
-
throw error
|
|
835
920
|
} finally {
|
|
836
921
|
if (mine === seq) {
|
|
837
922
|
state.loading = false
|
|
@@ -850,9 +935,9 @@ function endpointSource() {
|
|
|
850
935
|
if (!requestable(config)) {
|
|
851
936
|
return Number.NaN
|
|
852
937
|
}
|
|
853
|
-
const { url, rowsPath, totalPath
|
|
938
|
+
const { url, rowsPath, totalPath } = config
|
|
854
939
|
const { default: axios } = await import('@nextcloud/axios')
|
|
855
|
-
const res = await axios.get(generateUrl(url), { params: paramsOf(
|
|
940
|
+
const res = await axios.get(generateUrl(url), { params: paramsOf(config) })
|
|
856
941
|
const body = res ? res.data : null
|
|
857
942
|
const total = body && typeof body === 'object' && !Array.isArray(body) ? readRowsPath(body, totalPath || 'total') : undefined
|
|
858
943
|
if (typeof total === 'number' && Number.isFinite(total)) {
|
|
@@ -871,6 +956,21 @@ function endpointSource() {
|
|
|
871
956
|
* @return {boolean} Whether a load is in flight.
|
|
872
957
|
*/
|
|
873
958
|
loading: () => state.loading,
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* @return {string} The sentence the last load was refused with, or ''.
|
|
962
|
+
*/
|
|
963
|
+
refusal: () => state.refusal,
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* @return {boolean} Whether the last load failed for another reason than a refusal.
|
|
967
|
+
*/
|
|
968
|
+
failed: () => state.failed,
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* @return {string} The note read off the last answer, or ''.
|
|
972
|
+
*/
|
|
973
|
+
note: () => state.note,
|
|
874
974
|
}
|
|
875
975
|
}
|
|
876
976
|
|
package/src/css/index-page.css
CHANGED
|
@@ -222,6 +222,13 @@
|
|
|
222
222
|
color: var(--color-text-maxcontrast);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/* A note the named source read off its answer, above the rows. */
|
|
226
|
+
.cn-index-page__source-note {
|
|
227
|
+
margin: 0 0 8px;
|
|
228
|
+
color: var(--color-text-maxcontrast);
|
|
229
|
+
font-size: 0.9em;
|
|
230
|
+
}
|
|
231
|
+
|
|
225
232
|
.cn-index-page__scope-heading {
|
|
226
233
|
display: flex;
|
|
227
234
|
flex-direction: column;
|
package/src/index.js
CHANGED
|
@@ -40,6 +40,7 @@ export {
|
|
|
40
40
|
CnAdminActionCard,
|
|
41
41
|
CnAdminSettingsShell,
|
|
42
42
|
CnAdvancedFormDialog,
|
|
43
|
+
CnAiFeaturesWidget,
|
|
43
44
|
CnApiReference,
|
|
44
45
|
CnAppLoading,
|
|
45
46
|
CnAppNav,
|
|
@@ -249,9 +250,11 @@ export {
|
|
|
249
250
|
CnObjectMetadataModal,
|
|
250
251
|
CnObjectMetadataWidget,
|
|
251
252
|
CnObjectPartiesWidget,
|
|
253
|
+
CnObjectPermissionsTab,
|
|
252
254
|
CnObjectPresenceWidget,
|
|
253
255
|
CnObjectRow,
|
|
254
256
|
CnObjectSidebar,
|
|
257
|
+
CnObjectTranslationsWidget,
|
|
255
258
|
CnOfflineQueue,
|
|
256
259
|
CnOrderedListField,
|
|
257
260
|
CnPageHeader,
|
|
@@ -272,6 +275,7 @@ export {
|
|
|
272
275
|
CnQuicklinksWidget,
|
|
273
276
|
CnQuicklinksWidgetForm,
|
|
274
277
|
CnRadioCardsWidget,
|
|
278
|
+
CnReadinessChecklist,
|
|
275
279
|
CnRecordListWidget,
|
|
276
280
|
CnRedactionEditor,
|
|
277
281
|
CnReferencePreview,
|
|
@@ -301,6 +305,8 @@ export {
|
|
|
301
305
|
CnSavedViewWidgetForm,
|
|
302
306
|
CnSaveViewDialog,
|
|
303
307
|
CnSchemaFormDialog,
|
|
308
|
+
CnSchemaRulesWidget,
|
|
309
|
+
CnSearchIndexStatus,
|
|
304
310
|
CnSearchPage,
|
|
305
311
|
CnSearchSelect,
|
|
306
312
|
CnSectionsWidget,
|
|
@@ -312,11 +318,13 @@ export {
|
|
|
312
318
|
CnSettingsPage,
|
|
313
319
|
CnSettingsSection,
|
|
314
320
|
CnSetupWizard,
|
|
321
|
+
CnShippedConfigurationSection,
|
|
315
322
|
CnSignatureCapture,
|
|
316
323
|
CnSpendAnalyticsWidget,
|
|
317
324
|
CnSpendAnalyticsWidgetForm,
|
|
318
325
|
CnStagesWidget,
|
|
319
326
|
CnStagesWidgetForm,
|
|
327
|
+
CnStateMachineEditor,
|
|
320
328
|
CnStatsBlock,
|
|
321
329
|
CnStatsBlockWidget,
|
|
322
330
|
CnStatsBlockWidgetForm,
|
|
@@ -618,6 +626,7 @@ export { DYNAMIC_KEY_PREFIX, EXTENDS_FORM_KEY, PREFILL_KEY, splitDynamicFormData
|
|
|
618
626
|
// drift on what a 409 means (breaking change / schema still has objects).
|
|
619
627
|
export { deleteSchema, describeSchemaChange, saveSchema, SchemaBreakingChangeError, SchemaHasObjectsError } from './utils/index.js'
|
|
620
628
|
export { validateManifest, validateManifestV2 } from './utils/validateManifest.js'
|
|
629
|
+
export { validateStateMachine } from './utils/stateMachineValidation.js'
|
|
621
630
|
export { clearResolveCache, resolveManifestSentinels } from './utils/resolveManifestSentinels.js'
|
|
622
631
|
export { clearRouteSentinelWarnings, resolveRouteSentinels } from './utils/resolveRouteSentinels.js'
|
|
623
632
|
export {
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* The reads and the run behind CnAiFeaturesWidget: the AI features a record's
|
|
6
|
+
* TYPE object places on a surface, where hermiq runs each one, a run on one of
|
|
7
|
+
* the record's documents, and the sentence a refusal is shown as.
|
|
8
|
+
*
|
|
9
|
+
* An undeclared type asks nobody: the declaration is read first and the
|
|
10
|
+
* features endpoint is called only when something is placed on the surface.
|
|
11
|
+
* Every read answers null when it failed, so the widget can say it is
|
|
12
|
+
* incomplete instead of drawing an empty list.
|
|
13
|
+
*
|
|
14
|
+
* The run goes to hermiq in the reader's own session, so their own access
|
|
15
|
+
* decides which document can be read. A request without a usable document
|
|
16
|
+
* reference is refused before it is sent: hermiq's redaction gate sits on the
|
|
17
|
+
* document, and a request without one would run unchecked.
|
|
18
|
+
*/
|
|
19
|
+
import axios from '@nextcloud/axios'
|
|
20
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
21
|
+
import { prefixUrl } from './headers.js'
|
|
22
|
+
|
|
23
|
+
/** The gate name for a run refused before it was sent. */
|
|
24
|
+
export const MISSING_REFERENCE = 'missing-reference'
|
|
25
|
+
|
|
26
|
+
/** The hermiq endpoint a feature runs on a document through; `{slug}` is filled in. */
|
|
27
|
+
export const DEFAULT_RUN_ENDPOINT = '/apps/hermiq/api/ai-features/{slug}/run-on-document'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A run that was refused, before sending or by one of hermiq's gates.
|
|
31
|
+
*/
|
|
32
|
+
export class AiFeatureRefusal extends Error {
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} feature The feature slug.
|
|
35
|
+
* @param {string} reason The refusing side's own sentence.
|
|
36
|
+
* @param {string|null} gate The gate that refused, when named.
|
|
37
|
+
*/
|
|
38
|
+
constructor(feature, reason, gate) {
|
|
39
|
+
super(reason)
|
|
40
|
+
this.name = 'AiFeatureRefusal'
|
|
41
|
+
this.feature = feature
|
|
42
|
+
this.reason = reason
|
|
43
|
+
this.gate = gate
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The id inside a reference that may be a plain id or an object carrying one.
|
|
49
|
+
*
|
|
50
|
+
* @param {string|object|null|undefined} value The reference.
|
|
51
|
+
* @return {string} The id, or ''.
|
|
52
|
+
*/
|
|
53
|
+
export function referenceId(value) {
|
|
54
|
+
if (value !== null && typeof value === 'object') {
|
|
55
|
+
return String(value.id ?? value.uuid ?? '').trim()
|
|
56
|
+
}
|
|
57
|
+
return String(value ?? '').trim()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The slugs a type object places on one surface. The declaration is a map of
|
|
62
|
+
* feature slug to surface name.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} typeObject The type object.
|
|
65
|
+
* @param {string} field The declaration field.
|
|
66
|
+
* @param {string} surface The surface.
|
|
67
|
+
* @return {Array<string>} The slugs, in declaration order.
|
|
68
|
+
*/
|
|
69
|
+
export function surfaceSlugs(typeObject, field, surface) {
|
|
70
|
+
const declared = typeObject?.[field]
|
|
71
|
+
if (declared === null || typeof declared !== 'object' || Array.isArray(declared)) {
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
return Object.keys(declared).filter((slug) => declared[slug] === surface)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Read one OpenRegister object.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} apiBase API root.
|
|
81
|
+
* @param {string} register Register.
|
|
82
|
+
* @param {string} schema Schema.
|
|
83
|
+
* @param {string} id Object id.
|
|
84
|
+
* @return {Promise<object>} The object.
|
|
85
|
+
*/
|
|
86
|
+
async function readObject(apiBase, register, schema, id) {
|
|
87
|
+
const { data } = await axios.get(prefixUrl(`${apiBase}/objects/${register}/${schema}/${encodeURIComponent(id)}`))
|
|
88
|
+
return data?.object ?? data ?? {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The AI features the record's type places on the surface, each with where it
|
|
93
|
+
* runs as the features endpoint answers it.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} options The options.
|
|
96
|
+
* @param {string} options.apiBase OpenRegister API root.
|
|
97
|
+
* @param {string} options.register Register.
|
|
98
|
+
* @param {string} options.schema Schema of the record.
|
|
99
|
+
* @param {string} options.objectId The record id.
|
|
100
|
+
* @param {object} options.declaration `{typeField, typeSchema, field, surface}`.
|
|
101
|
+
* @param {string} options.featuresEndpoint The endpoint that asks hermiq.
|
|
102
|
+
* @param {string} options.featuresQueryKey Query key the slugs are sent under.
|
|
103
|
+
* @return {Promise<Array<object>|null>} The features, or null when they could not be read.
|
|
104
|
+
*/
|
|
105
|
+
export async function fetchFeatures({ apiBase, register, schema, objectId, declaration, featuresEndpoint, featuresQueryKey }) {
|
|
106
|
+
try {
|
|
107
|
+
const record = await readObject(apiBase, register, schema, objectId)
|
|
108
|
+
const typeId = referenceId(record[declaration.typeField])
|
|
109
|
+
if (typeId === '') {
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
const typeObject = await readObject(apiBase, register, declaration.typeSchema, typeId)
|
|
113
|
+
const slugs = surfaceSlugs(typeObject, declaration.field || 'aiFeatures', declaration.surface)
|
|
114
|
+
if (slugs.length === 0) {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const query = new URLSearchParams({ surface: declaration.surface })
|
|
119
|
+
for (const slug of slugs) {
|
|
120
|
+
query.append(`${featuresQueryKey}[${slug}]`, declaration.surface)
|
|
121
|
+
}
|
|
122
|
+
const { data } = await axios.get(`${prefixUrl(featuresEndpoint)}?${query.toString()}`)
|
|
123
|
+
return Array.isArray(data?.features) ? data.features : []
|
|
124
|
+
} catch {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The record's documents a feature can be run on.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} url The files endpoint.
|
|
133
|
+
* @return {Promise<Array<{id: string, title: string}>|null>} The documents, or null.
|
|
134
|
+
*/
|
|
135
|
+
export async function fetchDocuments(url) {
|
|
136
|
+
try {
|
|
137
|
+
const { data } = await axios.get(prefixUrl(url))
|
|
138
|
+
const rows = Array.isArray(data?.results) ? data.results : (Array.isArray(data) ? data : [])
|
|
139
|
+
return rows
|
|
140
|
+
.filter((row) => row && row.id !== undefined && row.id !== null)
|
|
141
|
+
.map((row) => ({ id: String(row.id), title: String(row.title ?? row.name ?? row.id) }))
|
|
142
|
+
} catch {
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The prompts offered on the record, as the endpoint holds them.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} url The prompts endpoint.
|
|
151
|
+
* @return {Promise<Array<{id: string, label: string, prompt: string}>|null>} The prompts, or null.
|
|
152
|
+
*/
|
|
153
|
+
export async function fetchPrompts(url) {
|
|
154
|
+
try {
|
|
155
|
+
const { data } = await axios.get(prefixUrl(url))
|
|
156
|
+
return Array.isArray(data?.prompts) ? data.prompts : []
|
|
157
|
+
} catch {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Ask one prompt about the record.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} url The ask endpoint.
|
|
166
|
+
* @param {string} objectKey Body key naming the record.
|
|
167
|
+
* @param {string} objectId The record id.
|
|
168
|
+
* @param {string} message The prompt.
|
|
169
|
+
* @return {Promise<object>} `{reply, model?}`.
|
|
170
|
+
*/
|
|
171
|
+
export async function askAbout(url, objectKey, objectId, message) {
|
|
172
|
+
const { data } = await axios.post(prefixUrl(url), { [objectKey]: objectId, message })
|
|
173
|
+
return data
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Where a feature runs, in words. A feature hermiq does not offer reads
|
|
178
|
+
* unavailable, never local.
|
|
179
|
+
*
|
|
180
|
+
* @param {object} feature One feature row.
|
|
181
|
+
* @return {string} The sentence.
|
|
182
|
+
*/
|
|
183
|
+
export function whereItRuns(feature) {
|
|
184
|
+
if (feature?.available !== true) {
|
|
185
|
+
return t('nextcloud-vue', 'Unavailable: hermiq does not offer this feature here.')
|
|
186
|
+
}
|
|
187
|
+
const provider = String(feature.provider ?? '').trim()
|
|
188
|
+
const residency = String(feature.residency ?? '').trim()
|
|
189
|
+
if (provider === '' || residency === '') {
|
|
190
|
+
return t('nextcloud-vue', 'hermiq did not say which provider runs this, or where.')
|
|
191
|
+
}
|
|
192
|
+
return t('nextcloud-vue', '{provider}, runs in {residency}', { provider, residency })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Run one AI feature on one document, through hermiq.
|
|
197
|
+
*
|
|
198
|
+
* @param {object} request The run.
|
|
199
|
+
* @param {string} request.feature The feature slug.
|
|
200
|
+
* @param {string|number} request.documentReference The document's file id.
|
|
201
|
+
* @param {string} request.instruction What to do with it.
|
|
202
|
+
* @param {string} [request.runEndpoint] Endpoint template with `{slug}`.
|
|
203
|
+
* @return {Promise<object>} hermiq's answer.
|
|
204
|
+
* @throws {AiFeatureRefusal} When the reference is missing, or hermiq refuses.
|
|
205
|
+
*/
|
|
206
|
+
export async function runFeatureOnDocument({ feature, documentReference, instruction, runEndpoint = DEFAULT_RUN_ENDPOINT }) {
|
|
207
|
+
const reference = String(documentReference ?? '').trim()
|
|
208
|
+
if (/^\d+$/.test(reference) === false) {
|
|
209
|
+
throw new AiFeatureRefusal(feature, t('nextcloud-vue', 'No document was chosen.'), MISSING_REFERENCE)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const url = prefixUrl(runEndpoint.replace('{slug}', encodeURIComponent(feature)))
|
|
213
|
+
try {
|
|
214
|
+
const response = await axios.post(url, { documentReference: reference, instruction })
|
|
215
|
+
return response.data
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const status = error?.response?.status
|
|
218
|
+
const data = error?.response?.data ?? {}
|
|
219
|
+
if ([400, 403, 404, 422].includes(status) === true) {
|
|
220
|
+
throw new AiFeatureRefusal(feature, String(data.error ?? ''), data.gate ?? null)
|
|
221
|
+
}
|
|
222
|
+
throw error
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The sentence a reader sees for a refusal, naming the feature and the reason.
|
|
228
|
+
*
|
|
229
|
+
* @param {AiFeatureRefusal} refusal The refusal.
|
|
230
|
+
* @param {string} featureLabel The feature's name.
|
|
231
|
+
* @return {string} The sentence.
|
|
232
|
+
*/
|
|
233
|
+
export function refusalSentence(refusal, featureLabel) {
|
|
234
|
+
if (refusal.gate === MISSING_REFERENCE) {
|
|
235
|
+
return t('nextcloud-vue', '{feature} reads a document, and no document was chosen.', { feature: featureLabel })
|
|
236
|
+
}
|
|
237
|
+
if (refusal.gate === 'redaction') {
|
|
238
|
+
return t('nextcloud-vue', '{feature} will not read a document that has not been redacted.', { feature: featureLabel })
|
|
239
|
+
}
|
|
240
|
+
return t('nextcloud-vue', '{feature} did not run: {reason}', { feature: featureLabel, reason: refusal.reason })
|
|
241
|
+
}
|