@@ -59,6 +59,7 @@ import CloseIcon from 'vue-material-design-icons/Close.vue'
import PrimeDialog from 'primevue/dialog'
import NcButton from '@nextcloud/vue/components/NcButton'
+import { useIsSmallMobile } from '@nextcloud/vue/composables/useIsMobile'
import AssistantTextProcessingForm from './AssistantTextProcessingForm.vue'
@@ -72,6 +73,9 @@ export default {
NcButton,
CloseIcon,
},
+ setup() {
+ return { isSmallMobile: useIsSmallMobile() }
+ },
props: {
/**
* If true, add the modal content to the Viewer trap elements via the event-bus
@@ -221,7 +225,7 @@ export default {
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));
@@ -230,6 +234,24 @@ export default {
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;
@@ -245,10 +267,18 @@ export default {
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;
}
diff --git a/src/components/ChattyLLM/ChattyLLMInputForm.vue b/src/components/ChattyLLM/ChattyLLMInputForm.vue
index d407e703d..bc23841d8 100644
--- a/src/components/ChattyLLM/ChattyLLMInputForm.vue
+++ b/src/components/ChattyLLM/ChattyLLMInputForm.vue
@@ -3,8 +3,8 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
-
-
+
+
generateOcsUrl('/apps/assistant/chat' + endpoint)
@@ -300,6 +302,8 @@ export default {
NoSession,
},
+ mixins: [navAutoCollapse],
+
props: {
isAssignment: {
type: Boolean,
@@ -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;
}
diff --git a/src/mixins/navAutoCollapse.js b/src/mixins/navAutoCollapse.js
new file mode 100644
index 000000000..158a175cd
--- /dev/null
+++ b/src/mixins/navAutoCollapse.js
@@ -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 ``. 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()
+ }
+ },
+ },
+}