feat(records): make chat record drawer resizable

This commit is contained in:
KokerZhou authored and digua committed 2026-08-28 10:34:54 +08:00
1 parent 9009edfeb9
commit 997598259c
7 files changed
+123 -6

No files matched your search

@@ -3,7 +3,7 @@
* Chat record viewer drawer.
* Keeps the quick-view shell while ChatRecordWorkspace owns shared orchestration.
*/
import { ref, onMounted } from 'vue'
import { onBeforeUnmount, ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import ChatRecordWorkspace from './ChatRecordWorkspace.vue'
import { useLayoutStore } from '@/stores/layout'
@@ -13,9 +13,59 @@ const layoutStore = useLayoutStore()
// 平台检测
const isWindows = ref(false)
const MIN_DRAWER_WIDTH = 480
const VIEWPORT_MARGIN = 32
let dragStartX = 0
let dragStartWidth = 0
const maxDrawerWidth = ref(1280 - VIEWPORT_MARGIN)
function updateViewportLimit() {
maxDrawerWidth.value = Math.max(MIN_DRAWER_WIDTH, window.innerWidth - VIEWPORT_MARGIN)
}
function clampDrawerWidth(width: number) {
return Math.min(maxDrawerWidth.value, Math.max(MIN_DRAWER_WIDTH, width))
}
function stopResize() {
window.removeEventListener('pointermove', handleResize)
window.removeEventListener('pointerup', stopResize)
document.body.style.cursor = ''
document.body.style.userSelect = ''
}
function handleResize(event: PointerEvent) {
layoutStore.chatRecordDrawerWidth = clampDrawerWidth(dragStartWidth + dragStartX - event.clientX)
}
function startResize(event: PointerEvent) {
if (event.button !== 0) return
dragStartX = event.clientX
dragStartWidth = layoutStore.chatRecordDrawerWidth
window.addEventListener('pointermove', handleResize)
window.addEventListener('pointerup', stopResize)
document.body.style.cursor = 'col-resize'
document.body.style.userSelect = 'none'
}
function resizeWithKeyboard(event: KeyboardEvent) {
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
event.preventDefault()
const delta = event.key === 'ArrowLeft' ? 24 : -24
layoutStore.chatRecordDrawerWidth = clampDrawerWidth(layoutStore.chatRecordDrawerWidth + delta)
}
onMounted(() => {
isWindows.value = navigator.platform.toLowerCase().includes('win')
updateViewportLimit()
layoutStore.chatRecordDrawerWidth = clampDrawerWidth(layoutStore.chatRecordDrawerWidth)
window.addEventListener('resize', updateViewportLimit)
})
onBeforeUnmount(() => {
stopResize()
window.removeEventListener('resize', updateViewportLimit)
})
</script>
@@ -30,9 +80,26 @@ onMounted(() => {
<template #content>
<div
data-vaul-no-drag
class="chat-record-drawer-content flex h-full w-[750px] flex-col bg-white dark:bg-page-dark"
class="chat-record-drawer-content relative flex h-full flex-col bg-white dark:bg-page-dark"
:style="{ width: `${layoutStore.chatRecordDrawerWidth}px`, maxWidth: `calc(100vw - ${VIEWPORT_MARGIN}px)` }"
style="-webkit-app-region: no-drag"
>
<div
class="group absolute inset-y-0 left-0 z-10 w-2 -translate-x-1/2 cursor-col-resize touch-none"
role="separator"
aria-orientation="vertical"
:aria-label="t('records.drawer.resize')"
:aria-valuemin="MIN_DRAWER_WIDTH"
:aria-valuemax="maxDrawerWidth"
:aria-valuenow="layoutStore.chatRecordDrawerWidth"
tabindex="0"
@pointerdown.prevent="startResize"
@keydown="resizeWithKeyboard"
>
<div
class="mx-auto h-full w-px bg-transparent transition-colors group-hover:bg-primary-400 group-focus:bg-primary-500"
/>
</div>
<!-- 头部 -->
<div
class="flex items-center justify-between border-b border-gray-200 px-4 dark:border-gray-800"
+2 -1
View File
@@ -1,6 +1,7 @@
{
"drawer": {
"title": "Chat Record Viewer"
"title": "Chat Record Viewer",
"resize": "Resize chat record panel"
},
"workspace": {
"indexMissingTitle": "No conversation summary index yet",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"drawer": {
"title": "チャット履歴ビューア"
"title": "チャット履歴ビューア",
"resize": "チャット履歴パネルの幅を変更"
},
"workspace": {
"indexMissingTitle": "会話要約インデックスがまだありません",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"drawer": {
"title": "聊天记录查看器"
"title": "聊天记录查看器",
"resize": "调整聊天记录面板宽度"
},
"workspace": {
"indexMissingTitle": "还没有可用的会话摘要索引",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"drawer": {
"title": "聊天紀錄檢視器"
"title": "聊天紀錄檢視器",
"resize": "調整聊天紀錄面板寬度"
},
"workspace": {
"indexMissingTitle": "尚未建立可用的會話摘要索引",
+43
View File
@@ -0,0 +1,43 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { createApp } from 'vue'
import { createPinia, setActivePinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
function createMemoryStorage(initial: Record<string, string> = {}): Storage {
const values = new Map(Object.entries(initial))
return {
get length() {
return values.size
},
clear: () => values.clear(),
getItem: (key) => values.get(key) ?? null,
key: (index) => [...values.keys()][index] ?? null,
removeItem: (key) => values.delete(key),
setItem: (key, value) => void values.set(key, value),
}
}
function createPersistedPinia(storage: Storage) {
Object.defineProperty(globalThis, 'localStorage', { configurable: true, value: storage })
const pinia = createPinia().use(piniaPluginPersistedstate)
createApp({}).use(pinia)
setActivePinia(pinia)
return pinia
}
test('layout persistence upgrades older state and restores a saved drawer width', async () => {
const storage = createMemoryStorage({ layout: JSON.stringify({ isSidebarCollapsed: true }) })
const legacyPinia = createPersistedPinia(storage)
const { useLayoutStore } = await import('./layout')
const legacyStore = useLayoutStore(legacyPinia)
assert.equal(legacyStore.isSidebarCollapsed, true)
assert.equal(legacyStore.chatRecordDrawerWidth, 750)
storage.setItem('layout', JSON.stringify({ chatRecordDrawerWidth: 936 }))
const restoredPinia = createPersistedPinia(storage)
const restoredStore = useLayoutStore(restoredPinia)
assert.equal(restoredStore.chatRecordDrawerWidth, 936)
})
+3
View File
@@ -16,6 +16,7 @@ export const useLayoutStore = defineStore(
const screenCaptureImage = ref<string | null>(null)
const showChatRecordDrawer = ref(false)
const chatRecordQuery = ref<ChatRecordQuery | null>(null)
const chatRecordDrawerWidth = ref(750)
const isToolsPanelLocked = ref(false)
const isToolsPanelMini = ref(false)
@@ -117,6 +118,7 @@ export const useLayoutStore = defineStore(
screenCaptureImage,
showChatRecordDrawer,
chatRecordQuery,
chatRecordDrawerWidth,
showSettings,
settingsTab,
settingsSubTab,
@@ -142,6 +144,7 @@ export const useLayoutStore = defineStore(
'isToolsPanelLocked',
'isToolsPanelMini',
'toolsPanelPosition',
'chatRecordDrawerWidth',
],
storage: localStorage,
},