Skip to content

Commit b8d3220

Browse files
authored
Merge pull request #124 from nextcloud/fix/context-chat-sources-ui
fix: render context chat's the referenced source items
2 parents 1f93e05 + ba827da commit b8d3220

File tree

3 files changed

+255
-62
lines changed

3 files changed

+255
-62
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<template>
2+
<div class="output">
3+
<ContextChatOutputForm v-if="selectedTaskTypeId === 'context_chat:context_chat'"
4+
class="output-fields"
5+
:output-shape="selectedTaskType.outputShape"
6+
:output="outputs" />
7+
<TaskTypeFields v-else
8+
class="output-fields"
9+
:is-output="true"
10+
:shape="selectedTaskType.outputShape"
11+
:optional-shape="selectedTaskType.optionalOutputShape ?? null"
12+
:shape-options="selectedTaskType.outputShapeEnumValues ?? null"
13+
:optional-shape-options="selectedTaskType.optionalOutputShapeEnumValues ?? null"
14+
:values="outputs"
15+
:show-advanced="showAdvanced"
16+
@update:outputs="$emit('update:outputs', $event)"
17+
@update:show-advanced="$emit('update:show-advanced', $event)" />
18+
<NcNoteCard v-if="outputEqualsInput"
19+
class="warning-note"
20+
type="warning">
21+
{{ t('assistant', 'The task ran successfully but the result is identical to the input.') }}
22+
</NcNoteCard>
23+
<NcNoteCard v-else-if="hasInitialOutput"
24+
class="warning-note"
25+
type="warning">
26+
{{ t('assistant', 'This output was generated by AI. Make sure to double-check and adjust.') }}
27+
</NcNoteCard>
28+
</div>
29+
</template>
30+
31+
<script>
32+
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
33+
34+
import ContextChatOutputForm from './ContextChat/ContextChatOutputForm.vue'
35+
import TaskTypeFields from './fields/TaskTypeFields.vue'
36+
37+
export default {
38+
name: 'AssistantFormOutputs',
39+
40+
components: {
41+
ContextChatOutputForm,
42+
TaskTypeFields,
43+
NcNoteCard,
44+
},
45+
46+
props: {
47+
inputs: {
48+
type: Object,
49+
default: () => {},
50+
},
51+
outputs: {
52+
type: Object,
53+
default: () => {},
54+
},
55+
selectedTaskType: {
56+
type: [Object, null],
57+
default: null,
58+
},
59+
showAdvanced: {
60+
type: Boolean,
61+
default: false,
62+
},
63+
},
64+
65+
computed: {
66+
selectedTaskTypeId() {
67+
return this.selectedTaskType?.id ?? null
68+
},
69+
outputEqualsInput() {
70+
if (typeof this.inputs?.input === 'string' && typeof this.outputs?.output === 'string') {
71+
return this.hasInitialOutput && this.outputs.output?.trim() === this.inputs.input?.trim()
72+
}
73+
return false
74+
},
75+
hasInitialOutput() {
76+
return !!this.outputs.output?.trim()
77+
},
78+
},
79+
}
80+
81+
</script>
82+
83+
<style lang="scss" scoped>
84+
.output {
85+
margin-top: 24px;
86+
display: flex;
87+
flex-direction: column;
88+
align-items: start;
89+
justify-content: center;
90+
91+
.warning-note {
92+
align-self: normal;
93+
}
94+
95+
hr {
96+
width: 100%;
97+
}
98+
99+
.input-label {
100+
align-self: start;
101+
font-weight: bold;
102+
}
103+
.output-fields {
104+
width: 100%;
105+
}
106+
}
107+
</style>

src/components/AssistantTextProcessingForm.vue

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,11 @@
4242
:selected-task-id="selectedTaskId"
4343
:selected-task-type="selectedTaskType"
4444
:show-advanced.sync="showAdvanced" />
45-
<div v-if="hasOutput"
46-
class="output">
47-
<TaskTypeFields
48-
class="output-fields"
49-
:is-output="true"
50-
:shape="selectedTaskType.outputShape"
51-
:optional-shape="selectedTaskType.optionalOutputShape ?? null"
52-
:shape-options="selectedTaskType.outputShapeEnumValues ?? null"
53-
:optional-shape-options="selectedTaskType.optionalOutputShapeEnumValues ?? null"
54-
:values.sync="myOutputs"
55-
:show-advanced.sync="showAdvanced" />
56-
<NcNoteCard v-if="outputEqualsInput"
57-
class="warning-note"
58-
type="warning">
59-
{{ t('assistant', 'The task ran successfully but the result is identical to the input.') }}
60-
</NcNoteCard>
61-
<NcNoteCard v-else-if="hasInitialOutput"
62-
class="warning-note"
63-
type="warning">
64-
{{ t('assistant', 'This output was generated by AI. Make sure to double-check and adjust.') }}
65-
</NcNoteCard>
66-
</div>
45+
<AssistantFormOutputs v-if="hasOutput"
46+
:inputs="myInputs"
47+
:outputs.sync="myOutputs"
48+
:selected-task-type="selectedTaskType"
49+
:show-advanced.sync="showAdvanced" />
6750
</div>
6851
<!-- hide the footer for chatty-llm -->
6952
<div v-if="!showHistory && mySelectedTaskTypeId !== 'chatty-llm'" class="footer">
@@ -120,24 +103,23 @@
120103
</template>
121104

122105
<script>
123-
import UnfoldLessHorizontalIcon from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
124-
import UnfoldMoreHorizontalIcon from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
125106
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
126107
import CreationIcon from 'vue-material-design-icons/Creation.vue'
127108
import HistoryIcon from 'vue-material-design-icons/History.vue'
109+
import UnfoldLessHorizontalIcon from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
110+
import UnfoldMoreHorizontalIcon from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
128111
129-
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
130112
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
113+
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
131114
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
132115
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
133116
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
134-
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
135117
136118
import AssistantFormInputs from './AssistantFormInputs.vue'
119+
import AssistantFormOutputs from './AssistantFormOutputs.vue'
137120
import NoProviderEmptyContent from './NoProviderEmptyContent.vue'
138121
import TaskList from './TaskList.vue'
139122
import TaskTypeSelect from './TaskTypeSelect.vue'
140-
import TaskTypeFields from './fields/TaskTypeFields.vue'
141123
142124
import { SHAPE_TYPE_NAMES } from '../constants.js'
143125
@@ -153,7 +135,6 @@ const TEXT2TEXT_TASK_TYPE_ID = 'core:text2text'
153135
export default {
154136
name: 'AssistantTextProcessingForm',
155137
components: {
156-
TaskTypeFields,
157138
NoProviderEmptyContent,
158139
TaskList,
159140
TaskTypeSelect,
@@ -167,8 +148,8 @@ export default {
167148
UnfoldMoreHorizontalIcon,
168149
HistoryIcon,
169150
ArrowLeftIcon,
170-
NcNoteCard,
171151
AssistantFormInputs,
152+
AssistantFormOutputs,
172153
},
173154
provide() {
174155
return {
@@ -302,20 +283,11 @@ export default {
302283
? t('assistant', 'Launch this task again')
303284
: t('assistant', 'Launch a task')
304285
},
305-
hasInitialOutput() {
306-
return !!this.outputs?.output?.trim()
307-
},
308286
hasOutput() {
309287
return this.myOutputs
310288
&& Object.keys(this.myOutputs).length > 0
311289
&& Object.values(this.myOutputs).every(v => v !== null)
312290
},
313-
outputEqualsInput() {
314-
if (typeof this.inputs.input === 'string' && typeof this.outputs.output === 'string') {
315-
return this.hasInitialOutput && this.outputs?.output?.trim() === this.inputs.input?.trim()
316-
}
317-
return false
318-
},
319291
formattedOutput() {
320292
if (this.mySelectedTaskTypeId === 'OCP\\TextToImage\\Task') {
321293
return window.location.protocol + '//' + window.location.host + generateUrl('/apps/assistant/i/{imageGenId}', { imageGenId: this.myOutput })
@@ -437,30 +409,6 @@ export default {
437409
}
438410
}
439411
440-
.output {
441-
margin-top: 24px;
442-
display: flex;
443-
flex-direction: column;
444-
align-items: start;
445-
justify-content: center;
446-
447-
.warning-note {
448-
align-self: normal;
449-
}
450-
451-
hr {
452-
width: 100%;
453-
}
454-
455-
.input-label {
456-
align-self: start;
457-
font-weight: bold;
458-
}
459-
.output-fields {
460-
width: 100%;
461-
}
462-
}
463-
464412
.assistant-bubble {
465413
align-self: start;
466414
display: flex;
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<template>
2+
<div class="cc-output">
3+
<div class="cc-output__text">
4+
<TextField
5+
field-key="cc-output-text"
6+
:value="output.output"
7+
:field="outputShape.output"
8+
:is-output="true" />
9+
</div>
10+
<div class="cc-output__sources">
11+
<label for="v-select" class="cc-output__sources__label">
12+
{{ outputShape.sources.description }}
13+
</label>
14+
<NcSelect
15+
:value="sources"
16+
:placeholder="t('assistant', 'No sources referenced')"
17+
:multiple="true"
18+
:close-on-select="false"
19+
:no-wrap="false"
20+
:label-outside="true"
21+
:append-to-body="false"
22+
:dropdown-should-open="() => false">
23+
<template #option="option">
24+
<a class="select-option" :href="option.url">
25+
<NcAvatar
26+
:size="24"
27+
:url="option.icon"
28+
:display-name="option.label" />
29+
<span class="multiselect-name">
30+
{{ option.label }}
31+
</span>
32+
</a>
33+
</template>
34+
<template #selected-option="option">
35+
<a class="select-option" :href="option.url">
36+
<NcAvatar
37+
:size="24"
38+
:url="option.icon"
39+
:display-name="option.label" />
40+
<span class="multiselect-name">
41+
{{ option.label }}
42+
</span>
43+
</a>
44+
</template>
45+
</NcSelect>
46+
</div>
47+
</div>
48+
</template>
49+
50+
<script>
51+
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
52+
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
53+
54+
import TextField from '../fields/TextField.vue'
55+
56+
export default {
57+
name: 'ContextChatOutputForm',
58+
59+
components: {
60+
NcAvatar,
61+
NcSelect,
62+
TextField,
63+
},
64+
65+
props: {
66+
outputShape: {
67+
type: Object,
68+
required: true,
69+
},
70+
output: {
71+
type: Object,
72+
required: true,
73+
},
74+
},
75+
76+
computed: {
77+
sources() {
78+
try {
79+
return JSON.parse(this.output.sources)
80+
} catch (e) {
81+
console.error('Failed to parse sources', e)
82+
return []
83+
}
84+
},
85+
},
86+
}
87+
</script>
88+
89+
<style lang="scss" scoped>
90+
.cc-output {
91+
display: flex;
92+
flex-direction: column;
93+
align-items: start;
94+
gap: 8px;
95+
96+
.advanced {
97+
width: 100%;
98+
}
99+
100+
&__text {
101+
width: 100%;
102+
}
103+
104+
&__sources {
105+
display: flex;
106+
flex-direction: column;
107+
108+
:deep .v-select {
109+
min-width: 400px !important;
110+
111+
> div {
112+
border: 2px solid var(--color-primary-element) !important;
113+
}
114+
115+
.avatardiv {
116+
border-radius: 50%;
117+
118+
&> img {
119+
border-radius: 0 !important;
120+
}
121+
}
122+
123+
.vs__actions {
124+
display: none !important;
125+
}
126+
}
127+
128+
.select-option {
129+
display: flex;
130+
align-items: center;
131+
}
132+
133+
.multiselect-name {
134+
margin-left: 8px;
135+
}
136+
}
137+
}
138+
</style>

0 commit comments

Comments
 (0)