diff --git a/frontend/src/views/ChatView.vue b/frontend/src/views/ChatView.vue index 9a4da16..c6aaf9a 100644 --- a/frontend/src/views/ChatView.vue +++ b/frontend/src/views/ChatView.vue @@ -10,6 +10,9 @@ const store = useChatStore(); const summarizing = ref(false); const sidebarOpen = ref(false); +const convSearchQuery = ref(""); +const headerKebabOpen = ref(false); +const sidebarKebabOpen = ref(false); let prevConvId: number | null = null; @@ -46,7 +49,12 @@ const groupedConversations = computed((): ConvGroup[] => { const buckets: Record = {}; const order: string[] = []; - for (const conv of store.conversations) { + const q = convSearchQuery.value.trim().toLowerCase(); + const filtered = q + ? store.conversations.filter((c) => (c.title || "").toLowerCase().includes(q)) + : store.conversations; + + for (const conv of filtered) { const d = new Date(conv.updated_at); let key: string; if (d >= startOfToday) { @@ -69,6 +77,7 @@ const groupedConversations = computed((): ConvGroup[] => { onMounted(async () => { document.addEventListener("keydown", onGlobalKeydown); + document.addEventListener("mousedown", onDocumentMousedown); await store.fetchConversations(); if (convId.value) { if (store.currentConversation?.id !== convId.value) { @@ -187,36 +196,25 @@ async function handleSummarize() { } } -// ── Research modal ──────────────────────────────────────────────────────────── -const showResearchModal = ref(false); -const researchTopic = ref(""); const chatPanelRef = ref | null>(null); -function toggleResearchModal() { - showResearchModal.value = !showResearchModal.value; - if (showResearchModal.value) { - researchTopic.value = ""; - nextTick(() => { - const el = document.querySelector(".research-topic-input") as HTMLInputElement; - el?.focus(); - }); +// ── Kebab dismissal ─────────────────────────────────────────────────────────── +function onDocumentMousedown(e: MouseEvent) { + const target = e.target as HTMLElement | null; + if (headerKebabOpen.value && !target?.closest(".header-kebab-wrapper")) { + headerKebabOpen.value = false; + } + if (sidebarKebabOpen.value && !target?.closest(".sidebar-kebab-wrapper")) { + sidebarKebabOpen.value = false; } -} - -function submitResearch() { - const topic = researchTopic.value.trim(); - if (!topic) return; - showResearchModal.value = false; - researchTopic.value = ""; - // Prefill sends via ChatPanel — user sees it in the input, then it auto-submits - chatPanelRef.value?.send(`Research: ${topic}`); } // ── Keyboard ────────────────────────────────────────────────────────────────── function onGlobalKeydown(e: KeyboardEvent) { if (e.key !== "Escape") return; - if (showResearchModal.value) { - showResearchModal.value = false; + if (headerKebabOpen.value || sidebarKebabOpen.value) { + headerKebabOpen.value = false; + sidebarKebabOpen.value = false; return; } if (sidebarOpen.value) { @@ -228,6 +226,7 @@ function onGlobalKeydown(e: KeyboardEvent) { onUnmounted(() => { document.removeEventListener("keydown", onGlobalKeydown); + document.removeEventListener("mousedown", onDocumentMousedown); if (prevConvId) { const conv = store.conversations.find((c) => c.id === prevConvId); if (conv && conv.message_count === 0) { @@ -246,13 +245,35 @@ onUnmounted(() => { > @@ -319,40 +344,26 @@ onUnmounted(() => {

{{ store.currentConversation.title || "New Chat" }}

- -
+
-
-
Research topic
- -
- - -
+
+
- -
{ .sidebar-top-bar { display: flex; + flex-direction: column; gap: 0.5rem; padding: 0.75rem; } .btn-new-conv { - flex: 1; padding: 0.5rem; background: var(--color-primary); color: #fff; @@ -412,23 +423,31 @@ onUnmounted(() => { font-size: 0.9rem; transition: box-shadow 0.15s, opacity 0.15s; } +.btn-new-conv--full { width: 100%; } .btn-new-conv:hover { opacity: 0.9; box-shadow: 0 0 14px rgba(129, 140, 248, 0.35); } -.btn-select-mode { - background: none; +.sidebar-search-row { + display: flex; + align-items: center; + gap: 0.35rem; +} +.conv-search-input { + flex: 1; + min-width: 0; + padding: 0.4rem 0.6rem; border: 1px solid var(--color-border); border-radius: var(--radius-sm); - color: var(--color-text-muted); - font-size: 0.82rem; - padding: 0.4rem 0.6rem; - cursor: pointer; - white-space: nowrap; + background: var(--color-bg); + color: var(--color-text); + font-size: 0.85rem; + outline: none; + font-family: inherit; } -.btn-select-mode:hover, -.btn-select-mode.active { border-color: var(--color-primary); color: var(--color-primary); } +.conv-search-input:focus { border-color: var(--color-primary); } +.conv-search-input::placeholder { color: var(--color-text-muted); } .bulk-bar { display: flex; @@ -548,87 +567,54 @@ onUnmounted(() => { min-width: 0; } -.btn-summarize { - padding: 0.3rem 0.75rem; - background: var(--color-bg-secondary); - color: var(--color-text); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - cursor: pointer; - font-size: 0.85rem; - white-space: nowrap; -} -.btn-summarize:hover:not(:disabled) { - background: var(--color-primary); color: #fff; border-color: var(--color-primary); -} -.btn-summarize:disabled { opacity: 0.6; cursor: default; } - -.btn-attach { +/* Kebab button + dropdown menu — shared by header and sidebar */ +.btn-kebab { background: none; border: none; cursor: pointer; color: var(--color-text-muted); - opacity: 0.6; - padding: 0.2rem; + padding: 0.25rem; display: flex; align-items: center; + justify-content: center; + border-radius: var(--radius-sm); + flex-shrink: 0; } -.btn-attach:hover { opacity: 1; } -.btn-attach:disabled { opacity: 0.25; cursor: default; } +.btn-kebab:hover { color: var(--color-text); background: var(--color-bg-secondary); } +.btn-kebab.active { color: var(--color-primary); } -.research-wrapper { position: relative; } -.research-modal { +.header-kebab-wrapper, +.sidebar-kebab-wrapper { + position: relative; +} + +.kebab-menu { position: absolute; - top: calc(100% + 8px); - right: 0; + top: calc(100% + 4px); + left: 0; + min-width: 180px; background: var(--color-bg-card); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: 0 4px 20px var(--color-shadow); - padding: 0.75rem; - min-width: 280px; + padding: 0.25rem; z-index: 20; } -.research-modal-header { - font-size: 0.8rem; - font-weight: 600; - color: var(--color-text-muted); - margin-bottom: 0.5rem; - text-transform: uppercase; - letter-spacing: 0.05em; -} -.research-topic-input { +.kebab-menu--right { left: auto; right: 0; } +.kebab-item { + display: block; width: 100%; - padding: 0.45rem 0.65rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-bg); + text-align: left; + background: none; + border: none; color: var(--color-text); - font-size: 0.9rem; - outline: none; - font-family: inherit; - box-sizing: border-box; + font-size: 0.85rem; + padding: 0.45rem 0.65rem; + border-radius: var(--radius-sm); + cursor: pointer; } -.research-topic-input:focus { border-color: var(--color-primary); } -.research-modal-actions { - display: flex; - gap: 0.5rem; - margin-top: 0.5rem; - justify-content: flex-end; -} -.btn-research-cancel { - background: none; border: 1px solid var(--color-border); - border-radius: var(--radius-sm); padding: 0.3rem 0.65rem; - font-size: 0.85rem; cursor: pointer; color: var(--color-text-muted); -} -.btn-research-cancel:hover { border-color: var(--color-text-muted); } -.btn-research-go { - background: var(--color-primary); color: #fff; border: none; - border-radius: var(--radius-sm); padding: 0.3rem 0.75rem; - font-size: 0.85rem; cursor: pointer; font-weight: 600; -} -.btn-research-go:disabled { opacity: 0.4; cursor: default; } -.btn-research-go:not(:disabled):hover { opacity: 0.9; } +.kebab-item:hover:not(:disabled) { background: var(--color-bg-secondary); color: var(--color-primary); } +.kebab-item:disabled { opacity: 0.4; cursor: default; } .no-conversation { flex: 1;