diff --git a/packages/frontend/editor-ui/src/features/ai/instanceAi/InstanceAiThreadView.vue b/packages/frontend/editor-ui/src/features/ai/instanceAi/InstanceAiThreadView.vue index 0f8f4a50b01..5feb488b0f1 100644 --- a/packages/frontend/editor-ui/src/features/ai/instanceAi/InstanceAiThreadView.vue +++ b/packages/frontend/editor-ui/src/features/ai/instanceAi/InstanceAiThreadView.vue @@ -13,14 +13,18 @@ import { import { storeToRefs } from 'pinia'; import { useRouter } from 'vue-router'; import { - N8nHeading, + N8nButton, + N8nDropdownMenu, + N8nIcon, N8nIconButton, N8nResizeWrapper, N8nScrollArea, N8nText, N8nTooltip, TOOLTIP_DELAY_MS, + useDropdownSearch, } from '@n8n/design-system'; +import type { DropdownMenuItemProps } from '@n8n/design-system'; import { onClickOutside, useElementSize, useScroll, useWindowSize } from '@vueuse/core'; import { useI18n } from '@n8n/i18n'; import type { @@ -31,6 +35,7 @@ import type { import { useRootStore } from '@n8n/stores/useRootStore'; import { useDocumentTitle } from '@/app/composables/useDocumentTitle'; import { usePageRedirectionHelper } from '@/app/composables/usePageRedirectionHelper'; +import { getRelativeDate } from '@/features/ai/chatHub/chat.utils'; import { COLLAPSED_MAIN_SIDEBAR_WIDTH, useSidebarLayout } from '@/app/composables/useSidebarLayout'; import { useTelemetry } from '@n8n/composables/useTelemetry'; import { provideThread, useInstanceAiStore } from './instanceAi.store'; @@ -47,7 +52,7 @@ import { getPendingAgentAttachment, } from './composables/useInstanceAiHandoff'; import { useTransitionGate } from './useTransitionGate'; -import { INSTANCE_AI_VIEW, NEW_CONVERSATION_TITLE } from './constants'; +import { INSTANCE_AI_THREAD_VIEW, INSTANCE_AI_VIEW, NEW_CONVERSATION_TITLE } from './constants'; import { agentPreviewContextIcon, formatAgentPreviewContextLabel, @@ -246,6 +251,53 @@ const currentThreadTitle = computed(() => { return undefined; }); +const threadDateGroupLabels: Record = { + Today: i18n.baseText('userActivity.today'), + Yesterday: i18n.baseText('userActivity.yesterday'), + 'This week': i18n.baseText('instanceAi.sidebar.group.thisWeek'), + Older: i18n.baseText('instanceAi.sidebar.group.older'), +}; +const threadDateGroupOrder = ['Today', 'Yesterday', 'This week', 'Older'] as const; + +const threadDropdownItems = computed>>(() => { + const now = new Date(); + const groups = new Map(); + + for (const threadSummary of store.threads) { + const group = getRelativeDate(now, threadSummary.updatedAt ?? threadSummary.createdAt); + const groupedThreads = groups.get(group) ?? []; + groupedThreads.push(threadSummary); + groups.set(group, groupedThreads); + } + + return threadDateGroupOrder.flatMap((group) => { + const groupedThreads = groups.get(group) ?? []; + if (groupedThreads.length === 0) return []; + + return [ + { + id: `date-group-${group}`, + label: threadDateGroupLabels[group] ?? group, + header: true, + }, + ...groupedThreads.map((threadSummary) => ({ + id: threadSummary.id, + label: threadSummary.title, + checked: threadSummary.id === props.threadId, + })), + ]; + }); +}); +const { filteredItems: filteredThreadDropdownItems, handleSearch: handleThreadDropdownSearch } = + useDropdownSearch(threadDropdownItems, { + isSearchable: (item) => !item.header, + }); + +function selectThread(threadId: string) { + if (threadId === props.threadId) return; + void router.push({ name: INSTANCE_AI_THREAD_VIEW, params: { threadId } }); +} + // The tab names the conversation, not the workflow previewed inside it — the // parent view claims the title so the embedded canvas can't overwrite this. const documentTitle = useDocumentTitle(); @@ -417,7 +469,7 @@ watch(preview.activeTabId, (activeTabId, previousActiveTabId) => { } }); -const previewMaxWidth = computed(() => Math.round(threadAreaWidth.value / 2)); +const previewMaxWidth = computed(() => Math.round(threadAreaWidth.value * 0.7)); // Keep the artifact at its current width and split the remaining space evenly // between the Instance AI chat and the agent preview chat. const agentPreviewChatColumnWidth = computed(() => @@ -936,17 +988,32 @@ async function dismissComposerContextChip() {