|
164 | 164 | let topicCache = $state<Record<string, unknown>>({}); |
165 | 165 | let activeConversationId = $state(untrack(() => data.conversation?.id ?? '')); |
166 | 166 | let isHistoryDrawerOpen = $state(false); |
| 167 | + let isNotebookOpen = $state(false); |
167 | 168 | let conversationHistory = $state<AssistantMessageInput[]>( |
168 | 169 | untrack(() => savedMessagesToHistory(data.messages ?? [])) |
169 | 170 | ); |
|
262 | 263 | onMount(() => { |
263 | 264 | didMount = true; |
264 | 265 |
|
| 266 | + function handleKeydown(event: KeyboardEvent) { |
| 267 | + if (event.key === 'Escape') isNotebookOpen = false; |
| 268 | + } |
| 269 | + window.addEventListener('keydown', handleKeydown); |
| 270 | +
|
265 | 271 | return () => { |
266 | 272 | cleanupComposerModeAnimation?.(); |
| 273 | + window.removeEventListener('keydown', handleKeydown); |
267 | 274 | }; |
268 | 275 | }); |
269 | 276 |
|
|
1017 | 1024 | </div> |
1018 | 1025 |
|
1019 | 1026 | <div class="chat-shell"> |
1020 | | - <aside class="history-rail" aria-label="Recent conversations"> |
1021 | | - <div class="history-rail__head"> |
| 1027 | + {#if isNotebookOpen} |
| 1028 | + <div |
| 1029 | + class="notebook-backdrop" |
| 1030 | + role="presentation" |
| 1031 | + aria-hidden="true" |
| 1032 | + onclick={() => (isNotebookOpen = false)} |
| 1033 | + ></div> |
| 1034 | + {/if} |
| 1035 | + |
| 1036 | + <aside |
| 1037 | + id="notebook-overlay" |
| 1038 | + class="notebook-overlay" |
| 1039 | + class:notebook-overlay--open={isNotebookOpen} |
| 1040 | + aria-label="Notebook index" |
| 1041 | + aria-hidden={!isNotebookOpen} |
| 1042 | + inert={!isNotebookOpen} |
| 1043 | + > |
| 1044 | + <div class="notebook-overlay__head"> |
1022 | 1045 | <div> |
1023 | 1046 | <p>Notebook index</p> |
1024 | 1047 | <h2>Recent</h2> |
1025 | 1048 | </div> |
1026 | | - <a class="history-new" href="/chat" aria-label="Start a new chat"> |
1027 | | - <span aria-hidden="true">+</span> |
1028 | | - </a> |
| 1049 | + <div class="notebook-overlay__head-controls"> |
| 1050 | + <a class="history-new" href="/chat" aria-label="Start a new chat"> |
| 1051 | + <span aria-hidden="true">+</span> |
| 1052 | + </a> |
| 1053 | + <button |
| 1054 | + type="button" |
| 1055 | + class="notebook-close-btn" |
| 1056 | + onclick={() => (isNotebookOpen = false)} |
| 1057 | + aria-label="Close notebook index" |
| 1058 | + >✕</button> |
| 1059 | + </div> |
1029 | 1060 | </div> |
1030 | 1061 |
|
1031 | 1062 | {#if hasSavedConversations} |
|
1036 | 1067 | class:history-item--active={conversation.id === activeConversationId} |
1037 | 1068 | href={`/chat/${conversation.id}`} |
1038 | 1069 | aria-current={conversation.id === activeConversationId ? 'page' : undefined} |
| 1070 | + onclick={() => (isNotebookOpen = false)} |
1039 | 1071 | > |
1040 | 1072 | <span class="history-item__title">{conversationTitle(conversation)}</span> |
1041 | 1073 | <span class="history-item__time">{formatRelativeTime(conversation.updatedAt)}</span> |
|
1048 | 1080 | </aside> |
1049 | 1081 |
|
1050 | 1082 | <section class="chat-main" aria-label="Active conversation"> |
| 1083 | + <div class="chat-topbar"> |
| 1084 | + <button |
| 1085 | + type="button" |
| 1086 | + class="notebook-toggle-btn" |
| 1087 | + aria-expanded={isNotebookOpen} |
| 1088 | + aria-controls="notebook-overlay" |
| 1089 | + onclick={() => (isNotebookOpen = !isNotebookOpen)} |
| 1090 | + > |
| 1091 | + <span class="notebook-toggle-btn__icon" aria-hidden="true">≡</span> |
| 1092 | + <span>Notebook</span> |
| 1093 | + </button> |
| 1094 | + </div> |
1051 | 1095 | <div class="conversation-stream" class:conversation-stream--empty={displayMessages.length === 0} bind:this={conversationEl} aria-label="Conversation"> |
1052 | 1096 | {#if displayMessages.length === 0} |
1053 | 1097 | <div class="empty-stage"> |
|
1637 | 1681 | } |
1638 | 1682 |
|
1639 | 1683 | .chat-shell { |
| 1684 | + position: relative; |
1640 | 1685 | flex: 1; |
1641 | 1686 | min-height: 0; |
1642 | | - display: grid; |
1643 | | - grid-template-columns: minmax(10.5rem, 13.75rem) minmax(0, 1fr); |
1644 | | - gap: clamp(1rem, 3vw, 2.4rem); |
1645 | | - align-items: stretch; |
| 1687 | + display: flex; |
| 1688 | + flex-direction: column; |
1646 | 1689 | width: 100%; |
1647 | | - max-width: 78rem; |
| 1690 | + max-width: 54rem; |
1648 | 1691 | margin: 0 auto; |
1649 | 1692 | } |
1650 | 1693 |
|
|
1653 | 1696 | min-height: 0; |
1654 | 1697 | display: flex; |
1655 | 1698 | flex-direction: column; |
| 1699 | + flex: 1; |
1656 | 1700 | gap: 1.25rem; |
1657 | 1701 | } |
1658 | 1702 |
|
1659 | 1703 | .history-mobile { |
1660 | 1704 | display: none; |
1661 | 1705 | } |
1662 | 1706 |
|
1663 | | - .history-rail { |
1664 | | - align-self: start; |
1665 | | - position: sticky; |
1666 | | - top: 0.75rem; |
| 1707 | + /* Notebook overlay (replaces permanent history rail) */ |
| 1708 | + .notebook-backdrop { |
| 1709 | + position: absolute; |
| 1710 | + inset: 0; |
| 1711 | + z-index: 18; |
| 1712 | + background: color-mix(in srgb, var(--bg-base) 20%, transparent); |
| 1713 | + } |
| 1714 | +
|
| 1715 | + .notebook-overlay { |
| 1716 | + position: absolute; |
| 1717 | + top: 0; |
| 1718 | + left: 0; |
| 1719 | + bottom: 0; |
| 1720 | + width: 13.75rem; |
| 1721 | + z-index: 20; |
1667 | 1722 | display: grid; |
| 1723 | + grid-template-rows: auto 1fr; |
1668 | 1724 | gap: 0.85rem; |
1669 | | - max-height: calc(100vh - 6rem); |
1670 | 1725 | overflow: hidden; |
1671 | 1726 | padding: 0.6rem; |
1672 | 1727 | border: 1px solid color-mix(in srgb, var(--border-soft) 78%, transparent); |
1673 | 1728 | border-radius: 0.5rem; |
1674 | | - background: color-mix(in srgb, var(--bg-surface) 68%, transparent); |
| 1729 | + background: color-mix(in srgb, var(--bg-surface) 94%, transparent); |
| 1730 | + backdrop-filter: blur(14px); |
| 1731 | + box-shadow: 0 8px 32px rgb(0 0 0 / 0.16); |
| 1732 | + transform: translateX(calc(-100% - 1rem)); |
| 1733 | + transition: |
| 1734 | + transform 240ms cubic-bezier(0.25, 0.46, 0.45, 0.94), |
| 1735 | + visibility 240ms; |
| 1736 | + visibility: hidden; |
| 1737 | + pointer-events: none; |
| 1738 | + } |
| 1739 | +
|
| 1740 | + .notebook-overlay--open { |
| 1741 | + transform: translateX(0); |
| 1742 | + visibility: visible; |
| 1743 | + pointer-events: auto; |
1675 | 1744 | } |
1676 | 1745 |
|
1677 | | - .history-rail__head { |
| 1746 | + .notebook-overlay__head { |
1678 | 1747 | display: flex; |
1679 | 1748 | align-items: center; |
1680 | 1749 | justify-content: space-between; |
|
1683 | 1752 | border-bottom: 1px solid color-mix(in srgb, var(--border-soft) 72%, transparent); |
1684 | 1753 | } |
1685 | 1754 |
|
1686 | | - .history-rail__head p { |
| 1755 | + .notebook-overlay__head p { |
1687 | 1756 | margin: 0; |
1688 | 1757 | color: var(--text-subtle); |
1689 | 1758 | font-size: 0.64rem; |
|
1692 | 1761 | text-transform: uppercase; |
1693 | 1762 | } |
1694 | 1763 |
|
1695 | | - .history-rail__head h2 { |
| 1764 | + .notebook-overlay__head h2 { |
1696 | 1765 | margin: 0.12rem 0 0; |
1697 | 1766 | color: var(--text-secondary); |
1698 | 1767 | font-size: 0.92rem; |
1699 | 1768 | line-height: 1.2; |
1700 | 1769 | } |
1701 | 1770 |
|
| 1771 | + .notebook-overlay__head-controls { |
| 1772 | + display: flex; |
| 1773 | + align-items: center; |
| 1774 | + gap: 0.3rem; |
| 1775 | + } |
| 1776 | +
|
| 1777 | + .notebook-close-btn { |
| 1778 | + display: inline-flex; |
| 1779 | + align-items: center; |
| 1780 | + justify-content: center; |
| 1781 | + width: 1.75rem; |
| 1782 | + height: 1.75rem; |
| 1783 | + padding: 0; |
| 1784 | + border: 1px solid var(--border-soft); |
| 1785 | + border-radius: 999px; |
| 1786 | + background: transparent; |
| 1787 | + color: var(--text-muted); |
| 1788 | + font: inherit; |
| 1789 | + font-size: 0.75rem; |
| 1790 | + cursor: pointer; |
| 1791 | + transition: |
| 1792 | + border-color 150ms ease, |
| 1793 | + background 150ms ease, |
| 1794 | + color 150ms ease; |
| 1795 | + } |
| 1796 | +
|
| 1797 | + .notebook-close-btn:hover { |
| 1798 | + border-color: var(--border-strong); |
| 1799 | + background: color-mix(in srgb, var(--bg-raised) 60%, transparent); |
| 1800 | + color: var(--text-primary); |
| 1801 | + } |
| 1802 | +
|
| 1803 | + /* Chat topbar with notebook toggle */ |
| 1804 | + .chat-topbar { |
| 1805 | + display: flex; |
| 1806 | + align-items: center; |
| 1807 | + gap: 0.75rem; |
| 1808 | + padding: 0.1rem 0 0.25rem; |
| 1809 | + } |
| 1810 | +
|
| 1811 | + .notebook-toggle-btn { |
| 1812 | + display: inline-flex; |
| 1813 | + align-items: center; |
| 1814 | + gap: 0.42rem; |
| 1815 | + padding: 0.4rem 0.72rem; |
| 1816 | + border: 1px solid var(--border-soft); |
| 1817 | + border-radius: 999px; |
| 1818 | + background: transparent; |
| 1819 | + color: var(--text-muted); |
| 1820 | + font: inherit; |
| 1821 | + font-size: 0.75rem; |
| 1822 | + font-weight: 700; |
| 1823 | + cursor: pointer; |
| 1824 | + transition: |
| 1825 | + border-color 150ms ease, |
| 1826 | + background 150ms ease, |
| 1827 | + color 150ms ease; |
| 1828 | + } |
| 1829 | +
|
| 1830 | + .notebook-toggle-btn:hover, |
| 1831 | + .notebook-toggle-btn[aria-expanded='true'] { |
| 1832 | + border-color: color-mix(in srgb, var(--accent-strong) 40%, var(--border-soft)); |
| 1833 | + background: color-mix(in srgb, var(--accent-soft) 16%, var(--bg-raised)); |
| 1834 | + color: var(--accent-primary); |
| 1835 | + } |
| 1836 | +
|
| 1837 | + .notebook-toggle-btn__icon { |
| 1838 | + font-size: 0.88rem; |
| 1839 | + line-height: 1; |
| 1840 | + } |
| 1841 | +
|
1702 | 1842 | .history-new { |
1703 | 1843 | display: inline-flex; |
1704 | 1844 | align-items: center; |
|
2852 | 2992 | } |
2853 | 2993 |
|
2854 | 2994 | @media (max-width: 960px) { |
2855 | | - .chat-shell { |
2856 | | - display: block; |
2857 | | - max-width: 54rem; |
2858 | | - } |
2859 | | -
|
2860 | | - .history-rail { |
| 2995 | + .chat-topbar, |
| 2996 | + .notebook-overlay, |
| 2997 | + .notebook-backdrop { |
2861 | 2998 | display: none; |
2862 | 2999 | } |
2863 | 3000 |
|
|
2962 | 3099 | .history-drawer summary::after, |
2963 | 3100 | .history-new, |
2964 | 3101 | .history-item, |
| 3102 | + .notebook-overlay, |
| 3103 | + .notebook-toggle-btn, |
| 3104 | + .notebook-close-btn, |
2965 | 3105 | .loading-dots span { |
2966 | 3106 | transition: none; |
2967 | 3107 | } |
|
0 commit comments