Skip to content

Commit

Permalink
Merge pull request #124 from nextcloud/fix/context-chat-sources-ui
Browse files Browse the repository at this point in the history
fix: render context chat's the referenced source items
  • Loading branch information
kyteinsky authored Sep 17, 2024
2 parents 1f93e05 + ba827da commit b8d3220
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 62 deletions.
107 changes: 107 additions & 0 deletions src/components/AssistantFormOutputs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<template>
<div class="output">
<ContextChatOutputForm v-if="selectedTaskTypeId === 'context_chat:context_chat'"
class="output-fields"
:output-shape="selectedTaskType.outputShape"
:output="outputs" />
<TaskTypeFields v-else
class="output-fields"
:is-output="true"
:shape="selectedTaskType.outputShape"
:optional-shape="selectedTaskType.optionalOutputShape ?? null"
:shape-options="selectedTaskType.outputShapeEnumValues ?? null"
:optional-shape-options="selectedTaskType.optionalOutputShapeEnumValues ?? null"
:values="outputs"
:show-advanced="showAdvanced"
@update:outputs="$emit('update:outputs', $event)"
@update:show-advanced="$emit('update:show-advanced', $event)" />
<NcNoteCard v-if="outputEqualsInput"
class="warning-note"
type="warning">
{{ t('assistant', 'The task ran successfully but the result is identical to the input.') }}
</NcNoteCard>
<NcNoteCard v-else-if="hasInitialOutput"
class="warning-note"
type="warning">
{{ t('assistant', 'This output was generated by AI. Make sure to double-check and adjust.') }}
</NcNoteCard>
</div>
</template>
<script>
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import ContextChatOutputForm from './ContextChat/ContextChatOutputForm.vue'
import TaskTypeFields from './fields/TaskTypeFields.vue'
export default {
name: 'AssistantFormOutputs',
components: {
ContextChatOutputForm,
TaskTypeFields,
NcNoteCard,
},
props: {
inputs: {
type: Object,
default: () => {},
},
outputs: {
type: Object,
default: () => {},
},
selectedTaskType: {
type: [Object, null],
default: null,
},
showAdvanced: {
type: Boolean,
default: false,
},
},
computed: {
selectedTaskTypeId() {
return this.selectedTaskType?.id ?? null
},
outputEqualsInput() {
if (typeof this.inputs?.input === 'string' && typeof this.outputs?.output === 'string') {
return this.hasInitialOutput && this.outputs.output?.trim() === this.inputs.input?.trim()
}
return false
},
hasInitialOutput() {
return !!this.outputs.output?.trim()
},
},
}
</script>
<style lang="scss" scoped>
.output {
margin-top: 24px;
display: flex;
flex-direction: column;
align-items: start;
justify-content: center;
.warning-note {
align-self: normal;
}
hr {
width: 100%;
}
.input-label {
align-self: start;
font-weight: bold;
}
.output-fields {
width: 100%;
}
}
</style>
72 changes: 10 additions & 62 deletions src/components/AssistantTextProcessingForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,11 @@
:selected-task-id="selectedTaskId"
:selected-task-type="selectedTaskType"
:show-advanced.sync="showAdvanced" />
<div v-if="hasOutput"
class="output">
<TaskTypeFields
class="output-fields"
:is-output="true"
:shape="selectedTaskType.outputShape"
:optional-shape="selectedTaskType.optionalOutputShape ?? null"
:shape-options="selectedTaskType.outputShapeEnumValues ?? null"
:optional-shape-options="selectedTaskType.optionalOutputShapeEnumValues ?? null"
:values.sync="myOutputs"
:show-advanced.sync="showAdvanced" />
<NcNoteCard v-if="outputEqualsInput"
class="warning-note"
type="warning">
{{ t('assistant', 'The task ran successfully but the result is identical to the input.') }}
</NcNoteCard>
<NcNoteCard v-else-if="hasInitialOutput"
class="warning-note"
type="warning">
{{ t('assistant', 'This output was generated by AI. Make sure to double-check and adjust.') }}
</NcNoteCard>
</div>
<AssistantFormOutputs v-if="hasOutput"
:inputs="myInputs"
:outputs.sync="myOutputs"
:selected-task-type="selectedTaskType"
:show-advanced.sync="showAdvanced" />
</div>
<!-- hide the footer for chatty-llm -->
<div v-if="!showHistory && mySelectedTaskTypeId !== 'chatty-llm'" class="footer">
Expand Down Expand Up @@ -120,24 +103,23 @@
</template>
<script>
import UnfoldLessHorizontalIcon from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
import UnfoldMoreHorizontalIcon from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import CreationIcon from 'vue-material-design-icons/Creation.vue'
import HistoryIcon from 'vue-material-design-icons/History.vue'
import UnfoldLessHorizontalIcon from 'vue-material-design-icons/UnfoldLessHorizontal.vue'
import UnfoldMoreHorizontalIcon from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import AssistantFormInputs from './AssistantFormInputs.vue'
import AssistantFormOutputs from './AssistantFormOutputs.vue'
import NoProviderEmptyContent from './NoProviderEmptyContent.vue'
import TaskList from './TaskList.vue'
import TaskTypeSelect from './TaskTypeSelect.vue'
import TaskTypeFields from './fields/TaskTypeFields.vue'
import { SHAPE_TYPE_NAMES } from '../constants.js'
Expand All @@ -153,7 +135,6 @@ const TEXT2TEXT_TASK_TYPE_ID = 'core:text2text'
export default {
name: 'AssistantTextProcessingForm',
components: {
TaskTypeFields,
NoProviderEmptyContent,
TaskList,
TaskTypeSelect,
Expand All @@ -167,8 +148,8 @@ export default {
UnfoldMoreHorizontalIcon,
HistoryIcon,
ArrowLeftIcon,
NcNoteCard,
AssistantFormInputs,
AssistantFormOutputs,
},
provide() {
return {
Expand Down Expand Up @@ -302,20 +283,11 @@ export default {
? t('assistant', 'Launch this task again')
: t('assistant', 'Launch a task')
},
hasInitialOutput() {
return !!this.outputs?.output?.trim()
},
hasOutput() {
return this.myOutputs
&& Object.keys(this.myOutputs).length > 0
&& Object.values(this.myOutputs).every(v => v !== null)
},
outputEqualsInput() {
if (typeof this.inputs.input === 'string' && typeof this.outputs.output === 'string') {
return this.hasInitialOutput && this.outputs?.output?.trim() === this.inputs.input?.trim()
}
return false
},
formattedOutput() {
if (this.mySelectedTaskTypeId === 'OCP\\TextToImage\\Task') {
return window.location.protocol + '//' + window.location.host + generateUrl('/apps/assistant/i/{imageGenId}', { imageGenId: this.myOutput })
Expand Down Expand Up @@ -437,30 +409,6 @@ export default {
}
}
.output {
margin-top: 24px;
display: flex;
flex-direction: column;
align-items: start;
justify-content: center;
.warning-note {
align-self: normal;
}
hr {
width: 100%;
}
.input-label {
align-self: start;
font-weight: bold;
}
.output-fields {
width: 100%;
}
}
.assistant-bubble {
align-self: start;
display: flex;
Expand Down
138 changes: 138 additions & 0 deletions src/components/ContextChat/ContextChatOutputForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<template>
<div class="cc-output">
<div class="cc-output__text">
<TextField
field-key="cc-output-text"
:value="output.output"
:field="outputShape.output"
:is-output="true" />
</div>
<div class="cc-output__sources">
<label for="v-select" class="cc-output__sources__label">
{{ outputShape.sources.description }}
</label>
<NcSelect
:value="sources"
:placeholder="t('assistant', 'No sources referenced')"
:multiple="true"
:close-on-select="false"
:no-wrap="false"
:label-outside="true"
:append-to-body="false"
:dropdown-should-open="() => false">
<template #option="option">
<a class="select-option" :href="option.url">
<NcAvatar
:size="24"
:url="option.icon"
:display-name="option.label" />
<span class="multiselect-name">
{{ option.label }}
</span>
</a>
</template>
<template #selected-option="option">
<a class="select-option" :href="option.url">
<NcAvatar
:size="24"
:url="option.icon"
:display-name="option.label" />
<span class="multiselect-name">
{{ option.label }}
</span>
</a>
</template>
</NcSelect>
</div>
</div>
</template>

<script>
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import TextField from '../fields/TextField.vue'
export default {
name: 'ContextChatOutputForm',
components: {
NcAvatar,
NcSelect,
TextField,
},
props: {
outputShape: {
type: Object,
required: true,
},
output: {
type: Object,
required: true,
},
},
computed: {
sources() {
try {
return JSON.parse(this.output.sources)
} catch (e) {
console.error('Failed to parse sources', e)
return []
}
},
},
}
</script>

<style lang="scss" scoped>
.cc-output {
display: flex;
flex-direction: column;
align-items: start;
gap: 8px;
.advanced {
width: 100%;
}
&__text {
width: 100%;
}
&__sources {
display: flex;
flex-direction: column;
:deep .v-select {
min-width: 400px !important;
> div {
border: 2px solid var(--color-primary-element) !important;
}
.avatardiv {
border-radius: 50%;
&> img {
border-radius: 0 !important;
}
}
.vs__actions {
display: none !important;
}
}
.select-option {
display: flex;
align-items: center;
}
.multiselect-name {
margin-left: 8px;
}
}
}
</style>

0 comments on commit b8d3220

Please sign in to comment.