From fefc547823cd1182d5610bdf6943eebb68260f01 Mon Sep 17 00:00:00 2001 From: niv Date: Wed, 13 May 2026 14:41:25 +0200 Subject: [PATCH 1/3] 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 92ff91dd..792857ff 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) * 3 + 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; From 742481f38074ef8fb40550e82b8797071cb76464 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Fri, 10 Jul 2026 10:33:28 +0200 Subject: [PATCH 2/3] fix: change the min height of NcRichText in Signed-off-by: Julien Veyssier --- src/components/fields/TextInput.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/fields/TextInput.vue b/src/components/fields/TextInput.vue index 792857ff..00befb48 100644 --- a/src/components/fields/TextInput.vue +++ b/src/components/fields/TextInput.vue @@ -300,6 +300,7 @@ body[dir="rtl"] .choose-file-button { border-radius: var(--border-radius-large) !important; padding: 8px !important; padding-bottom: 42px !important; + min-height: calc(var(--default-clickable-area) * 3 + 4px); max-height: 35vh !important; overflow-y: auto !important; .rendered-output, .rendered-output * { From 3ea0caf0520883a754550cdd335ca1518e46b372 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Fri, 10 Jul 2026 10:39:41 +0200 Subject: [PATCH 3/3] fix: make NcRichText scroll while streaming in Signed-off-by: Julien Veyssier --- src/components/fields/TextInput.vue | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/fields/TextInput.vue b/src/components/fields/TextInput.vue index 00befb48..c7f4963e 100644 --- a/src/components/fields/TextInput.vue +++ b/src/components/fields/TextInput.vue @@ -11,6 +11,7 @@ { + const scrollableArea = this.$refs.input?.$el?.querySelector('#' + this.id) + if (scrollableArea) { + scrollableArea.scrollTo(0, scrollableArea.scrollHeight) + } + const richText = this.$refs.richText?.$el + if (richText) { + richText.scrollTo(0, richText.scrollHeight) + } + }) }, },