Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/components/AssistantTextProcessingForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
:is-assignment="mySelectedTaskTypeId === 'assignments'"
class="chatty-inputs"
@open-chat="onOpenChatFromAssignment" />
<div v-else class="container chatty-inputs">
<NcAppNavigation>
<div v-else ref="container" class="container chatty-inputs">
<NcAppNavigation ref="appNav">
<NcAppNavigationList>
<NcAppNavigationNew :text="t('assistant', 'New task')"
variant="secondary"
Expand Down Expand Up @@ -204,6 +204,8 @@ import TaskList from './TaskList.vue'
import TaskTypeSelect from './TaskTypeSelect.vue'
import TranslateForm from './Translate/TranslateForm.vue'

import navAutoCollapse from '../mixins/navAutoCollapse.js'

import { SHAPE_TYPE_NAMES, MAX_TEXT_INPUT_LENGTH, TASK_STATUS_STRING } from '../constants.js'

import axios from '@nextcloud/axios'
Expand Down Expand Up @@ -249,6 +251,7 @@ export default {
ChattyLLMInputForm,
EditableTextField,
},
mixins: [navAutoCollapse],
provide() {
return {
providedCurrentTaskId: () => this.selectedTaskId,
Expand Down Expand Up @@ -473,6 +476,9 @@ export default {
},
mySelectedTaskTypeId(newVal) {
this.myOutputs = {}
// the navigation lives in a conditionally-rendered branch, so
// re-attach the resize observer once that branch is in the DOM
this.$nextTick(() => this.setupNavObserver())
},
},
mounted() {
Expand Down Expand Up @@ -790,13 +796,13 @@ export default {
}
}

:deep(.app-navigation--close) {
:deep(.app-navigation--closed) {
.app-navigation-toggle-wrapper {
margin-right: -33px !important;
}
}

:deep(.app-navigation--close ~ .session-area) {
:deep(.app-navigation--closed ~ .session-area) {
.session-area__chat-area, .session-area__input-area {
padding-left: 0 !important;
}
Expand Down
36 changes: 33 additions & 3 deletions src/components/AssistantTextProcessingModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
:closable="false"
:dismissable-mask="false"
:close-on-escape="false"
:draggable="true"
:draggable="!isSmallMobile"
append-to="self"
:base-z-index="isInsideViewer ? 9998 : 5000"
class="assistant-modal">
:class="['assistant-modal', { 'assistant-modal--fullscreen': isSmallMobile }]">
<div ref="modal_content"
class="assistant-modal--wrapper">
<div class="assistant-modal--content">
Expand Down Expand Up @@ -59,6 +59,7 @@
import PrimeDialog from 'primevue/dialog'

import NcButton from '@nextcloud/vue/components/NcButton'
import { useIsSmallMobile } from '@nextcloud/vue/composables/useIsMobile'

import AssistantTextProcessingForm from './AssistantTextProcessingForm.vue'

Expand All @@ -72,7 +73,10 @@
NcButton,
CloseIcon,
},
setup() {
return { isSmallMobile: useIsSmallMobile() }
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block should go after the emits block.

props: {

Check warning on line 79 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

The "props" property should be above the "setup" property on line 76

Check warning on line 79 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

The "props" property should be above the "setup" property on line 76

Check warning on line 79 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

The "props" property should be above the "setup" property on line 76

Check warning on line 79 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

The "props" property should be above the "setup" property on line 76
/**
* If true, add the modal content to the Viewer trap elements via the event-bus
*/
Expand Down Expand Up @@ -105,7 +109,7 @@
default: null,
},
},
emits: [

Check warning on line 112 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

The "emits" property should be above the "setup" property on line 76

Check warning on line 112 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

The "emits" property should be above the "setup" property on line 76

Check warning on line 112 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

The "emits" property should be above the "setup" property on line 76

Check warning on line 112 in src/components/AssistantTextProcessingModal.vue

View workflow job for this annotation

GitHub Actions / NPM lint

The "emits" property should be above the "setup" property on line 76
'cancel',
'cancel-task',
'background-notify',
Expand Down Expand Up @@ -221,7 +225,7 @@
height: calc(100vh - 32px);
max-height: calc(100vh - 32px);
height: 80%;
width: 70%;
width: min(1220px, 90vw);
resize: both;
overflow: hidden;
filter: drop-shadow(0 0 15px rgba(77, 77, 77, 0.5));
Expand All @@ -230,6 +234,24 @@
border: 0;
}

.assistant-modal.p-dialog.assistant-modal--fullscreen,
.assistant-modal.p-dialog.p-dialog-maximized {
inset: 0;
width: 100vw;
max-width: none;
height: 100vh;
max-height: none;
margin: 0;
border-radius: 0;
resize: none;
filter: none;
transform: none;
}

.assistant-modal.p-dialog.assistant-modal--fullscreen .p-dialog-header .p-dialog-maximize-button {
display: none;
}

.assistant-modal .p-dialog-header {
position: absolute;
top: 0;
Expand All @@ -245,10 +267,18 @@
cursor: grabbing;
}
.p-dialog-maximize-button {
position: absolute;
top: 10px;
left: 10px;
z-index: 3;
margin: 0 !important;
color: var(--color-main-text);
background-color: var(--color-main-background);
border-radius: var(--border-radius-element);
width: var(--default-clickable-area);
height: var(--default-clickable-area);
&:hover {
color: var(--color-main-text) !important;
background-color: var(--color-background-hover) !important;
border: none !important;
}
Expand Down
12 changes: 8 additions & 4 deletions src/components/ChattyLLM/ChattyLLMInputForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="container">
<NcAppNavigation>
<div ref="container" class="container">
<NcAppNavigation ref="appNav">
<NcAppNavigationList>
<NcAppNavigationNew v-if="!isAssignment"
:text="t('assistant', 'New conversation')"
Expand Down Expand Up @@ -260,6 +260,8 @@ import ICAL from 'ical.js'
import formatRecurrenceRule from './recurrenceRule.js'
import { getLanguage } from '@nextcloud/l10n'

import navAutoCollapse from '../../mixins/navAutoCollapse.js'

// future: type (text, image, file, etc), attachments, etc support

const getChatURL = (endpoint) => generateOcsUrl('/apps/assistant/chat' + endpoint)
Expand Down Expand Up @@ -300,6 +302,8 @@ export default {
NoSession,
},

mixins: [navAutoCollapse],

props: {
isAssignment: {
type: Boolean,
Expand Down Expand Up @@ -1227,13 +1231,13 @@ export default {
}
}

:deep(.app-navigation--close) {
:deep(.app-navigation--closed) {
.app-navigation-toggle-wrapper {
margin-right: -33px !important;
}
}

:deep(.app-navigation--close ~ .session-area) {
:deep(.app-navigation--closed ~ .session-area) {
.session-area__chat-area, .session-area__input-area {
padding-left: 0 !important;
}
Expand Down
75 changes: 75 additions & 0 deletions src/mixins/navAutoCollapse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

// The assistant lives in a resizable/draggable dialog, so the navigation
// visibility follows the dialog/container width rather than the viewport.
const NAV_COLLAPSE_BREAKPOINT = 900

/**
* Collapses the `NcAppNavigation` sidebar when the surrounding container gets
* narrower than NAV_COLLAPSE_BREAKPOINT and reopens it when it grows back.
*
* Unlike hiding the navigation, this drives the component's built-in open/close
* (by clicking its own toggle button) so the toggle stays visible and the
* sidebar remains reachable on a narrow dialog.
*
* The consuming component must set `ref="container"` on the observed element
* and `ref="appNav"` on its `<NcAppNavigation>`. If the container is rendered
* conditionally, call `setupNavObserver()` again once it appears.
*/
export default {
mounted() {
this.setupNavObserver()
},
beforeUnmount() {
this.teardownNavObserver()
},
methods: {
setupNavObserver() {
this.teardownNavObserver()
const container = this.$refs.container
if (!container || typeof ResizeObserver === 'undefined') {
return
}
// null = not evaluated yet, so the first observation always applies
this.navCollapsed = null
this.navResizeObserver = new ResizeObserver(this.onNavContainerResize)
this.navResizeObserver.observe(container)
},
teardownNavObserver() {
this.navResizeObserver?.disconnect()
this.navResizeObserver = null
},
onNavContainerResize(entries) {
const width = entries[0]?.contentRect.width
if (width === undefined) {
return
}
const shouldCollapse = width < NAV_COLLAPSE_BREAKPOINT
if (shouldCollapse === this.navCollapsed) {
return
}
this.navCollapsed = shouldCollapse
// defer to the next frame to avoid "ResizeObserver loop" warnings
window.requestAnimationFrame(() => this.setNavOpen(!shouldCollapse))
},
setNavOpen(open) {
// We deliberately drive NcAppNavigation through its own toggle button
// rather than emit('toggle-navigation') from @nextcloud/event-bus: that
// bus is global, so it would also collapse the host app's sidebar when
// resizing the assistant modal. NcAppNavigation exposes no `open` prop
// and doesn't expose toggleNavigation(), so its toggle button is the
// only instance-scoped lever.
const toggle = this.$refs.appNav?.$el?.querySelector('button.app-navigation-toggle')
if (!toggle) {
return
}
const isOpen = toggle.getAttribute('aria-expanded') === 'true'
if (isOpen !== open) {
toggle.click()
}
},
},
}
Loading