@conduction/nextcloud-vue 2.119.0 → 2.120.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/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/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/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +166 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +166 -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 +889 -101
- 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 +1 -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 +20052 -13138
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +889 -101
- package/l10n/en.json +166 -1
- package/l10n/nl.json +166 -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/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/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/index.js +4 -0
- package/src/index.js +5 -0
- package/src/utils/aiFeatures.js +241 -0
- package/src/utils/aiSuggestionTracker.js +178 -0
- package/src/utils/libraryWidgetKeys.js +1 -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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
3
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
4
|
+
*
|
|
5
|
+
* CnAiFeaturesWidget: a refusal is shown naming the feature and hermiq's gate,
|
|
6
|
+
* a transport failure apart from it, a feature hermiq does not offer gets no
|
|
7
|
+
* run, a failed read says the panel is incomplete, prompts are offered and
|
|
8
|
+
* answered, and every suggestion ends in one oversight report.
|
|
9
|
+
*/
|
|
10
|
+
import axios from '@nextcloud/axios'
|
|
11
|
+
import { mount } from '@vue/test-utils'
|
|
12
|
+
import CnAiFeaturesWidget from '../CnAiFeaturesWidget.vue'
|
|
13
|
+
|
|
14
|
+
jest.mock('@nextcloud/axios', () => ({ __esModule: true, default: { get: jest.fn(), post: jest.fn() } }))
|
|
15
|
+
jest.mock('@nextcloud/auth', () => ({ __esModule: true, getRequestToken: () => 'token' }))
|
|
16
|
+
jest.mock('@nextcloud/router', () => ({ __esModule: true, generateUrl: (path) => path }))
|
|
17
|
+
|
|
18
|
+
const flush = () => new Promise((resolve) => setTimeout(resolve, 0))
|
|
19
|
+
|
|
20
|
+
const SUMMARY = { slug: 'document-summary', available: true, provider: 'Hermiq', residency: 'on-premise' }
|
|
21
|
+
|
|
22
|
+
const PROPS = {
|
|
23
|
+
objectId: 'c-1',
|
|
24
|
+
register: 'reg',
|
|
25
|
+
schema: 'case',
|
|
26
|
+
declaration: { typeField: 'caseType', typeSchema: 'caseType', field: 'aiFeatures', surface: 'case' },
|
|
27
|
+
featuresEndpoint: '/apps/app/api/assistant/ai-features',
|
|
28
|
+
featuresQueryKey: 'caseType[aiFeatures]',
|
|
29
|
+
promptsEndpoint: '/apps/app/api/assistant/prompts',
|
|
30
|
+
askEndpoint: '/apps/app/api/assistant/converse',
|
|
31
|
+
decisionEndpoint: '/apps/app/api/ai/record-action',
|
|
32
|
+
objectKey: 'caseId',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const stubs = {
|
|
36
|
+
NcLoadingIcon: true,
|
|
37
|
+
NcButton: { props: ['disabled', 'type', 'variant'], template: '<button v-bind="$attrs" :type="type || \'button\'" :disabled="disabled"><slot /></button>' },
|
|
38
|
+
NcNoteCard: { props: ['type'], template: '<div class="note" v-bind="$attrs"><slot /></div>' },
|
|
39
|
+
NcSelect: {
|
|
40
|
+
props: ['options', 'modelValue', 'inputLabel', 'inputId'],
|
|
41
|
+
emits: ['update:modelValue'],
|
|
42
|
+
template: '<label><span>{{ inputLabel }}</span><select v-bind="$attrs" :id="inputId" @change="$emit(\'update:modelValue\', options.find((o) => o.id === $event.target.value) || null)"><option value="" /><option v-for="o in options" :key="o.id" :value="o.id">{{ o.label }}</option></select></label>',
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Answer the reads: features and prompts as given, documents as one file.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} answers `{features, prompts, featuresFail}`.
|
|
50
|
+
*/
|
|
51
|
+
function reads({ features = [SUMMARY], prompts = [], featuresFail = false } = {}) {
|
|
52
|
+
axios.get.mockImplementation((url) => {
|
|
53
|
+
if (url.endsWith('/objects/reg/case/c-1')) {
|
|
54
|
+
return featuresFail ? Promise.reject(new Error('down')) : Promise.resolve({ data: { caseType: 'ct-1' } })
|
|
55
|
+
}
|
|
56
|
+
if (url.endsWith('/objects/reg/caseType/ct-1')) {
|
|
57
|
+
return Promise.resolve({ data: { aiFeatures: Object.fromEntries(features.map((f) => [f.slug, 'case'])) } })
|
|
58
|
+
}
|
|
59
|
+
if (url.includes('/assistant/ai-features')) {
|
|
60
|
+
return Promise.resolve({ data: { features } })
|
|
61
|
+
}
|
|
62
|
+
if (url.endsWith('/assistant/prompts')) {
|
|
63
|
+
return Promise.resolve({ data: { prompts } })
|
|
64
|
+
}
|
|
65
|
+
if (url.endsWith('/files')) {
|
|
66
|
+
return Promise.resolve({ data: { results: [{ id: 42, title: 'besluit.pdf' }] } })
|
|
67
|
+
}
|
|
68
|
+
return Promise.reject(new Error(`unexpected ${url}`))
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function mountWidget(props = {}) {
|
|
73
|
+
const wrapper = mount(CnAiFeaturesWidget, { props: { ...PROPS, ...props }, global: { stubs } })
|
|
74
|
+
await flush()
|
|
75
|
+
await flush()
|
|
76
|
+
await flush()
|
|
77
|
+
await wrapper.vm.$nextTick()
|
|
78
|
+
return wrapper
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const decisions = () => axios.post.mock.calls.filter(([url]) => url === '/apps/app/api/ai/record-action').map(([, body]) => body)
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
axios.get.mockReset()
|
|
85
|
+
axios.post.mockReset()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('CnAiFeaturesWidget', () => {
|
|
89
|
+
it("renders a redaction refusal naming the feature and hermiq's gate", async () => {
|
|
90
|
+
reads()
|
|
91
|
+
axios.post.mockRejectedValue({ response: { status: 422, data: { error: 'The document is not redacted.', gate: 'redaction' } } })
|
|
92
|
+
const wrapper = await mountWidget()
|
|
93
|
+
await wrapper.find('select').setValue('42')
|
|
94
|
+
await wrapper.find('form').trigger('submit')
|
|
95
|
+
await flush()
|
|
96
|
+
const note = wrapper.find('[data-testid="cn-ai-feature-refusal-document-summary"]')
|
|
97
|
+
expect(note.text()).toContain('document-summary will not read a document that has not been redacted.')
|
|
98
|
+
expect(note.text()).toContain('Gate: redaction')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('labels the document picker', async () => {
|
|
102
|
+
reads()
|
|
103
|
+
const wrapper = await mountWidget()
|
|
104
|
+
expect(wrapper.find('label').text()).toContain('Document')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('refuses a run with no document before anything is sent', async () => {
|
|
108
|
+
reads()
|
|
109
|
+
const wrapper = await mountWidget()
|
|
110
|
+
await wrapper.find('form').trigger('submit')
|
|
111
|
+
await flush()
|
|
112
|
+
expect(axios.post).not.toHaveBeenCalled()
|
|
113
|
+
expect(wrapper.find('[data-testid="cn-ai-feature-refusal-document-summary"]').text()).toContain('no document was chosen')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('shows a transport failure apart from a refusal', async () => {
|
|
117
|
+
reads()
|
|
118
|
+
axios.post.mockRejectedValue(new Error('Network Error'))
|
|
119
|
+
const wrapper = await mountWidget()
|
|
120
|
+
await wrapper.find('select').setValue('42')
|
|
121
|
+
await wrapper.find('form').trigger('submit')
|
|
122
|
+
await flush()
|
|
123
|
+
expect(wrapper.find('[data-testid="cn-ai-feature-failed-document-summary"]').exists()).toBe(true)
|
|
124
|
+
expect(wrapper.find('[data-testid="cn-ai-feature-refusal-document-summary"]').exists()).toBe(false)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('offers no run for a feature hermiq does not offer, and reads no documents', async () => {
|
|
128
|
+
reads({ features: [{ slug: 'triage', available: false }] })
|
|
129
|
+
const wrapper = await mountWidget()
|
|
130
|
+
expect(wrapper.find('form').exists()).toBe(false)
|
|
131
|
+
expect(wrapper.text()).toContain('Unavailable: hermiq does not offer this feature here.')
|
|
132
|
+
expect(axios.get.mock.calls.some(([url]) => url.endsWith('/files'))).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('says the panel is incomplete when the features could not be read', async () => {
|
|
136
|
+
reads({ featuresFail: true })
|
|
137
|
+
const wrapper = await mountWidget()
|
|
138
|
+
expect(wrapper.find('[data-testid="cn-ai-features-unreadable"]').exists()).toBe(true)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it("offers the prompts and shows the answer to one, or the assistant's refusal", async () => {
|
|
142
|
+
reads({ features: [], prompts: [{ id: 'p-1', label: 'Vraag', prompt: 'Vraag?' }] })
|
|
143
|
+
axios.post.mockResolvedValueOnce({ data: { reply: 'Ja.' } })
|
|
144
|
+
const wrapper = await mountWidget()
|
|
145
|
+
await wrapper.find('[data-testid="cn-ai-question-p-1"]').trigger('click')
|
|
146
|
+
await flush()
|
|
147
|
+
expect(axios.post.mock.calls[0]).toEqual(['/apps/app/api/assistant/converse', { caseId: 'c-1', message: 'Vraag?' }])
|
|
148
|
+
expect(wrapper.find('[data-testid="cn-ai-question-answer"]').text()).toBe('Ja.')
|
|
149
|
+
|
|
150
|
+
// The open answer is reported superseded first, then the question is asked.
|
|
151
|
+
axios.post.mockResolvedValueOnce({ data: {} }).mockRejectedValueOnce({ response: { data: { error: 'Not now.' } } })
|
|
152
|
+
await wrapper.find('[data-testid="cn-ai-question-p-1"]').trigger('click')
|
|
153
|
+
await flush()
|
|
154
|
+
expect(wrapper.find('[data-testid="cn-ai-question-failed"]').text()).toBe('Not now.')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('renders no questions when none are offered', async () => {
|
|
158
|
+
reads({ features: [] })
|
|
159
|
+
const wrapper = await mountWidget()
|
|
160
|
+
expect(wrapper.find('[data-testid="cn-ai-questions"]').exists()).toBe(false)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('reports a used run output to the oversight trail', async () => {
|
|
164
|
+
reads()
|
|
165
|
+
axios.post.mockResolvedValue({ data: { output: 'Verleend.' } })
|
|
166
|
+
const wrapper = await mountWidget()
|
|
167
|
+
await wrapper.find('select').setValue('42')
|
|
168
|
+
await wrapper.find('form').trigger('submit')
|
|
169
|
+
await flush()
|
|
170
|
+
expect(wrapper.find('[data-testid="cn-ai-feature-output-document-summary"]').text()).toBe('Verleend.')
|
|
171
|
+
await wrapper.find('[data-testid="cn-ai-feature-decision-document-summary-use"]').trigger('click')
|
|
172
|
+
await flush()
|
|
173
|
+
expect(decisions()).toHaveLength(1)
|
|
174
|
+
expect(decisions()[0]).toMatchObject({ caseId: 'c-1', type: 'decision_support', userAction: 'accepted', suggestion: 'Verleend.', documentId: '42' })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('reports an adapted answer with the version that was used', async () => {
|
|
178
|
+
reads({ features: [], prompts: [{ id: 'p-1', label: 'Vraag', prompt: 'Vraag?' }] })
|
|
179
|
+
axios.post.mockResolvedValue({ data: { reply: 'Ja.' } })
|
|
180
|
+
const wrapper = await mountWidget()
|
|
181
|
+
await wrapper.find('[data-testid="cn-ai-question-p-1"]').trigger('click')
|
|
182
|
+
await flush()
|
|
183
|
+
await wrapper.find('[data-testid="cn-ai-question-decision-adapt"]').trigger('click')
|
|
184
|
+
await wrapper.find('[data-testid="cn-ai-question-decision-draft"]').setValue('Ja, mits.')
|
|
185
|
+
await wrapper.find('[data-testid="cn-ai-question-decision-use-adapted"]').trigger('click')
|
|
186
|
+
await flush()
|
|
187
|
+
expect(decisions()[0]).toMatchObject({ type: 'qa', userAction: 'modified', suggestion: 'Ja.', actualValue: 'Ja, mits.', prompt: 'Vraag?' })
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('reports an answer nobody acted on as superseded by the next question, then closed', async () => {
|
|
191
|
+
reads({ features: [], prompts: [{ id: 'p-1', label: 'Vraag', prompt: 'Vraag?' }] })
|
|
192
|
+
axios.post.mockResolvedValueOnce({ data: { reply: 'Eerste.' } }).mockResolvedValueOnce({ data: {} }).mockResolvedValue({ data: { reply: 'Tweede.' } })
|
|
193
|
+
const wrapper = await mountWidget()
|
|
194
|
+
await wrapper.find('[data-testid="cn-ai-question-p-1"]').trigger('click')
|
|
195
|
+
await flush()
|
|
196
|
+
await wrapper.find('[data-testid="cn-ai-question-p-1"]').trigger('click')
|
|
197
|
+
await flush()
|
|
198
|
+
wrapper.unmount()
|
|
199
|
+
await flush()
|
|
200
|
+
expect(decisions()).toHaveLength(2)
|
|
201
|
+
expect(decisions()[0]).toMatchObject({ userAction: 'ignored', unusedBecause: 'superseded', suggestion: 'Eerste.' })
|
|
202
|
+
expect(decisions()[1]).toMatchObject({ userAction: 'ignored', unusedBecause: 'closed' })
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('CnObjectSidebar built-in', () => {
|
|
207
|
+
it('resolves the ai-features widget type', () => {
|
|
208
|
+
const fs = require('fs')
|
|
209
|
+
const path = require('path')
|
|
210
|
+
const source = fs.readFileSync(path.resolve(__dirname, '../../CnObjectSidebar/CnObjectSidebar.vue'), 'utf8')
|
|
211
|
+
expect(source).toMatch(/'ai-features': CnAiFeaturesWidget/)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* CnAiFeaturesWidget — the hermiq AI features a record's type switches on,
|
|
6
|
+
* with where each runs, a run on a record document, refusals in words and an
|
|
7
|
+
* oversight decision on every suggestion. The CnObjectSidebar built-in
|
|
8
|
+
* `ai-features`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import CnAiFeaturesWidget from './CnAiFeaturesWidget.vue'
|
|
12
|
+
|
|
13
|
+
export { CnAiFeaturesWidget }
|
|
14
|
+
export default CnAiFeaturesWidget
|