Skip to content
Merged
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
6 changes: 5 additions & 1 deletion apps/desktop/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin />
<!-- Variable-axis weight range lets us use 450/550 (mid-weights) used in
a few UI components. Without the axis syntax Bunny Fonts serves only
the discrete weights listed, and browsers would round 450/550 to the
nearest defined weight. -->
<link
href="https://fonts.bunny.net/css?family=instrument-serif:400,400i|plus-jakarta-sans:400,500,600,700|geist-mono:400,500&display=swap"
href="https://fonts.bunny.net/css?family=instrument-serif:400,400i|plus-jakarta-sans:wght@200..800|geist-mono:wght@100..900&display=swap"
rel="stylesheet"
/>
<title>Notebook LM</title>
Expand Down
26 changes: 23 additions & 3 deletions apps/desktop/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function ChatView({ pendingSuggest, onSuggestConsumed, onCitationClick, o
const setCrossNotebookMode = useAppStore((s) => s.setCrossNotebookMode);

const activeNotebook = notebooks.find((nb) => nb.notebook_id === activeNotebookId) ?? null;
const documents = useAppStore((s) => s.documents);
const hasDocuments = documents.length > 0 || crossNotebookMode;

const [input, setInput] = useState('');
const [isScrolledUp, setIsScrolledUp] = useState(false);
Expand Down Expand Up @@ -125,9 +127,16 @@ export function ChatView({ pendingSuggest, onSuggestConsumed, onCitationClick, o
}
};

// BibTeX is available whenever the visible conversation has at least one
// message with sources. Sticking to activeSources alone hid the action
// after any context reset, even when the persisted messages still had
// sources we could export.
const hasExportableSources =
activeSources.length > 0 || messages.some((m) => m.role === 'assistant');

const overflowItems = [
{ label: 'Export conversation', onClick: handleExport, disabled: messages.length === 0 },
{ label: 'Export BibTeX', onClick: handleExportBibtex, disabled: activeSources.length === 0 },
{ label: 'Export BibTeX', onClick: handleExportBibtex, disabled: !hasExportableSources },
{ label: 'Toggle sources', onClick: toggleSourcePanel },
{ label: 'Clear chat', onClick: clearChat, disabled: messages.length === 0 },
];
Expand Down Expand Up @@ -175,8 +184,19 @@ export function ChatView({ pendingSuggest, onSuggestConsumed, onCitationClick, o
<div className="chat-messages">
{messages.length === 0 && (
<div className="chat-empty">
<p>What would you like to know?</p>
<QuickChips chips={EMPTY_CHIPS} onSelect={handleSend} />
{hasDocuments ? (
<>
<p>What would you like to know?</p>
<QuickChips chips={EMPTY_CHIPS} onSelect={handleSend} />
</>
) : (
<>
<p>No documents here yet.</p>
<p className="chat-empty-hint">
Drop a PDF, DOCX, or Markdown file anywhere on this window — or use the <strong>+ Add</strong> button in the sidebar.
</p>
</>
)}
</div>
)}
{messages.map((msg, i) => (
Expand Down
66 changes: 0 additions & 66 deletions apps/desktop/src/components/documents/DropZone.tsx

This file was deleted.

43 changes: 40 additions & 3 deletions apps/desktop/src/components/layout/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { KeyboardShortcutsOverlay } from '../ui/KeyboardShortcuts';
import { ZoteroImportDialog } from '../ui/ZoteroImport';
import type { SourceChunk } from '../../types';
import { humanizeError } from '../../utils/errorMessages';
import { usePaneResize } from '../../hooks/usePaneResize';
import './layout.css';

function isWizardComplete(): boolean {
Expand All @@ -41,6 +42,21 @@ export function AppShell() {
// the two-way link between a sentence and its source card is visible.
const [hoveredSourceIndex, setHoveredSourceIndex] = useState<number | null>(null);

const sidebarResize = usePaneResize({
storageKey: 'notebook-lm-sidebar-width',
min: 200,
max: 420,
from: 'left',
initial: 240,
});
const sourcePanelResize = usePaneResize({
storageKey: 'notebook-lm-source-panel-width',
min: 240,
max: 520,
from: 'right',
initial: 280,
});

// Global keyboard shortcuts
useEffect(() => {
const handler = (e: KeyboardEvent) => {
Expand Down Expand Up @@ -71,8 +87,17 @@ export function AppShell() {
useAppStore.getState().newChat();
return;
}
// ? — keyboard shortcuts (only when not typing in an input)
if (e.key === '?' && !paletteOpen) {
// ⌘-Shift-/ — keyboard shortcuts overlay. The bare `?` form used to
// be blocked whenever the chat textarea had focus (which is almost
// always), so the shortcut was effectively unreachable. A modifier
// shortcut is always reachable. The bare ? still works when no text
// input is focused.
if (e.metaKey && e.shiftKey && e.key === '?') {
e.preventDefault();
setShortcutsOpen((v) => !v);
return;
}
if (e.key === '?' && !paletteOpen && !e.metaKey && !e.ctrlKey) {
const tag = (e.target as HTMLElement)?.tagName;
if (tag !== 'INPUT' && tag !== 'TEXTAREA') {
e.preventDefault();
Expand Down Expand Up @@ -238,8 +263,15 @@ export function AppShell() {

return (
<>
<div className="app-shell">
<div
className="app-shell"
style={{
['--sidebar-width' as string]: `${sidebarResize.width}px`,
['--source-panel-width' as string]: `${sourcePanelResize.width}px`,
}}
>
<Sidebar onOpenZotero={() => setZoteroOpen(true)} />
<div className="pane-resizer" aria-label="Resize sidebar" {...sidebarResize.handleProps} />
<ChatView
pendingSuggest={pendingSuggest}
onSuggestConsumed={() => setPendingSuggest(null)}
Expand All @@ -249,6 +281,11 @@ export function AppShell() {
}}
onCitationHover={setHoveredSourceIndex}
/>
<div
className="pane-resizer"
aria-label="Resize sources panel"
{...sourcePanelResize.handleProps}
/>
<SourcePanel
onSourceClick={(source) => openSource(source)}
hoveredIndex={hoveredSourceIndex}
Expand Down
27 changes: 26 additions & 1 deletion apps/desktop/src/components/layout/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@
background: var(--color-bg-primary);
}

/* Drag handle between panes. Invisible until hover so the default look
* matches the pre-resize app. Expands to 3px with a sage tint on hover so
* the affordance is obvious once the user moves the cursor there. */
.pane-resizer {
flex-shrink: 0;
width: 3px;
margin: 0 -1px;
cursor: col-resize;
background: transparent;
-webkit-app-region: no-drag;
transition: background var(--duration-fast) var(--ease-out);
position: relative;
z-index: 1;
}

.pane-resizer:hover,
.pane-resizer:focus-visible {
background: var(--color-accent-subtle);
}

.pane-resizer:focus-visible {
outline: none;
box-shadow: 0 0 0 1px var(--color-accent);
}

/* ---- Sidebar ---- */

.sidebar {
Expand Down Expand Up @@ -755,7 +780,7 @@
z-index: 1000;
background: var(--color-bg-elevated);
border: 1px solid var(--color-border);
border-radius: var(--radius-card);
border-radius: var(--radius-modal);
box-shadow: var(--shadow-lg);
padding: var(--space-1);
min-width: 160px;
Expand Down
Loading
Loading