From 41c083a0179d1a5c73c8c122aab923f146d233cf Mon Sep 17 00:00:00 2001 From: niv Date: Wed, 13 May 2026 14:41:25 +0200 Subject: [PATCH] fix(assistant): render markdown in TextInput output Wrap the output field in NcRichText when isOutput is true, falling back to NcRichContenteditable for the input case. Headings, lists, bold, italic, code blocks and links are now rendered. Tables are not rendered due to the markdown-it configuration of NcRichText in @nextcloud/vue, which does not enable the table plugin by default. The copy still functions and keeps the markdown as expected. Signed-off-by: niv --- src/components/TaskTypeSelect.vue | 50 +++++++++++++++++++---- src/components/fields/TextInput.vue | 63 ++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/components/TaskTypeSelect.vue b/src/components/TaskTypeSelect.vue index 6831ff2d..ab361df7 100644 --- a/src/components/TaskTypeSelect.vue +++ b/src/components/TaskTypeSelect.vue @@ -141,7 +141,7 @@ export default { buttonTypes() { const taskTypes = {} for (const task of this.options) { - const type = task.category.id + const type = this.getTaskCategory(task.id) if (!taskTypes[type]) { taskTypes[type] = [] } @@ -154,7 +154,7 @@ export default { } result.push({ id: entry[0], - text: entry[1][0].category.name, + text: this.getTextForCategory(entry[0]), icon: this.getCategoryIcon(entry[0]), tasks: entry[1], }) @@ -163,7 +163,7 @@ export default { if (taskTypes.other) { result.push({ id: 'other', - text: taskTypes.other[0].category.name, + text: this.getTextForCategory('other'), icon: this.getCategoryIcon('other'), tasks: taskTypes.other, }) @@ -208,11 +208,7 @@ export default { return taskType.id === this.modelValue }, isCategorySelected(category) { - if (!this.modelValue) { - return false - } - const selectedTask = this.options.find(task => task.id === this.modelValue) - return selectedTask && category.id === selectedTask.category.id + return category.id === this.getTaskCategory(this.modelValue || '') }, onTaskSelected(taskType) { this.$emit('update:model-value', taskType.id) @@ -228,6 +224,44 @@ export default { this.categorySubmenu = null } }, + getTaskCategory(id) { + if (id.startsWith('chatty')) { + return 'chat' + } else if (id.startsWith('context_chat')) { + return 'context' + } else if (id.includes('translate')) { + return 'translate' + } else if (id.startsWith('richdocuments')) { + return 'generate' + } else if (id.includes('image') || id.includes('sticker')) { + return 'image' + } else if (id.includes('audio') || id.includes('speech')) { + return 'audio' + } else if (id.includes('text')) { + return 'text' + } + return 'other' + }, + getTextForCategory(category) { + switch (category) { + case 'chat': + return t('assistant', 'Chat with AI') + case 'context': + return t('assistant', 'Context Chat') + case 'text': + return t('assistant', 'Work with text') + case 'image': + return t('assistant', 'Work with images') + case 'translate': + return t('assistant', 'Translate') + case 'audio': + return t('assistant', 'Work with audio') + case 'generate': + return t('assistant', 'Generate file') + default: + return t('assistant', 'Other') + } + }, getCategoryIcon(category) { switch (category) { case 'chat': diff --git a/src/components/fields/TextInput.vue b/src/components/fields/TextInput.vue index 8372ce86..0d0d63ab 100644 --- a/src/components/fields/TextInput.vue +++ b/src/components/fields/TextInput.vue @@ -9,7 +9,16 @@
{{ limitLabel ?? '' }} + + @update:model-value="$emit('update:value', $event)" + @blur="onEditableBlur" /> { + const ref = this.$refs.input + if (!ref) { + return + } + if (typeof ref.focus === 'function') { + ref.focus() + return + } + const el = ref.$el + if (!el) { + return + } + if (typeof el.focus === 'function') { + el.focus() + return + } + const editable = el.querySelector?.('[contenteditable]') + if (editable && typeof editable.focus === 'function') { + editable.focus() + } + }) + }, + onEditableBlur() { + if (this.isOutput && this.isEditing) { + this.isEditing = false + } + }, }, } @@ -247,6 +293,20 @@ body[dir="rtl"] .choose-file-button { right: 4px; } + .output-wrapper { + display: block !important; + box-sizing: border-box !important; + border: 2px solid var(--color-primary-element) !important; + border-radius: var(--border-radius-large) !important; + padding: 8px !important; + padding-bottom: 42px !important; + max-height: 35vh !important; + overflow-y: auto !important; + .rendered-output, .rendered-output * { + cursor: text; + } + } + .rich-contenteditable__input { min-height: calc(var(--default-clickable-area) + 4px); padding-top: 5px !important; @@ -255,6 +315,7 @@ body[dir="rtl"] .choose-file-button { .shadowed .rich-contenteditable__input { border: 2px solid var(--color-primary-element); padding-bottom: 38px !important; + max-height: 35vh !important; } .shadowed.streaming .rich-contenteditable__input { animation: pulse 2s infinite;