diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index 816b52b..43b60a6 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -35,132 +35,280 @@ -
-
-
-
-
- - - -
- {{ action.streaming ? '正在思考...' : '思考过程' }} -
-
+ + +
@@ -184,8 +332,10 @@ diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 9f4c451..01090ae 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -207,6 +207,29 @@
+
+
+ 堆叠块显示 +

使用新版堆叠动画展示思考/工具块,超过 6 条自动收纳为“更多”。默认开启。

+
+ +
自动生成对话标题 @@ -218,6 +241,15 @@ :checked="form.auto_generate_title" @change="personalization.updateField({ key: 'auto_generate_title', value: $event.target.checked })" /> + 使用快速模型为新对话生成含 emoji 的简短标题
@@ -469,7 +501,6 @@ const swipeState = ref<{ startY: number; active: boolean }>({ startY: 0, active: type RunModeValue = 'fast' | 'thinking' | 'deep' | null; const runModeOptions: Array<{ id: string; label: string; desc: string; value: RunModeValue; badge?: string }> = [ - { id: 'auto', label: '跟随系统', desc: '沿用工作区默认设置', value: null }, { id: 'fast', label: '快速模式', desc: '追求响应速度,跳过思考模型', value: 'fast' }, { id: 'thinking', label: '思考模式', desc: '首轮回复会先输出思考过程', value: 'thinking', badge: '推荐' }, { id: 'deep', label: '深度思考', desc: '整轮对话都使用思考模型', value: 'deep' } @@ -562,6 +593,11 @@ const handleLiquidGlassToggle = (event: Event) => { personalization.setLiquidGlassExperimentEnabled(!!target?.checked); }; +const handleStackedBlocksToggle = (event: Event) => { + const target = event.target as HTMLInputElement | null; + personalization.setStackedBlocksEnabled(!!target?.checked); +}; + const openAdminPanel = () => { window.open('/admin/monitor', '_blank', 'noopener'); personalization.closeDrawer(); diff --git a/static/src/stores/personalization.ts b/static/src/stores/personalization.ts index f6811f3..bed9d9c 100644 --- a/static/src/stores/personalization.ts +++ b/static/src/stores/personalization.ts @@ -23,6 +23,7 @@ interface LiquidGlassPosition { interface ExperimentState { liquidGlassEnabled: boolean; liquidGlassPosition: LiquidGlassPosition | null; + stackedBlocksEnabled: boolean; } interface PersonalizationState { @@ -65,7 +66,8 @@ const defaultForm = (): PersonalForm => ({ const defaultExperimentState = (): ExperimentState => ({ liquidGlassEnabled: false, - liquidGlassPosition: null + liquidGlassPosition: null, + stackedBlocksEnabled: true }); const isValidPosition = (value: any): value is LiquidGlassPosition => { @@ -91,7 +93,9 @@ const loadExperimentState = (): ExperimentState => { const parsed = JSON.parse(raw); return { liquidGlassEnabled: Boolean(parsed?.liquidGlassEnabled), - liquidGlassPosition: isValidPosition(parsed?.liquidGlassPosition) ? parsed?.liquidGlassPosition : null + liquidGlassPosition: isValidPosition(parsed?.liquidGlassPosition) ? parsed?.liquidGlassPosition : null, + stackedBlocksEnabled: + typeof parsed?.stackedBlocksEnabled === 'boolean' ? parsed.stackedBlocksEnabled : defaultExperimentState().stackedBlocksEnabled }; } catch (error) { console.warn('无法读取实验功能设置:', error); @@ -452,6 +456,16 @@ export const usePersonalizationStore = defineStore('personalization', { toggleLiquidGlassExperiment() { this.setLiquidGlassExperimentEnabled(!this.experiments.liquidGlassEnabled); }, + setStackedBlocksEnabled(enabled: boolean) { + this.experiments = { + ...this.experiments, + stackedBlocksEnabled: !!enabled + }; + this.persistExperiments(); + }, + toggleStackedBlocks() { + this.setStackedBlocksEnabled(!this.experiments.stackedBlocksEnabled); + }, updateLiquidGlassPosition(position: LiquidGlassPosition | null) { this.experiments = { ...this.experiments, diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss index 772b85c..396c768 100644 --- a/static/src/styles/components/chat/_chat-area.scss +++ b/static/src/styles/components/chat/_chat-area.scss @@ -265,7 +265,9 @@ max-height: 0; overflow: hidden; opacity: 0; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + transition: + max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.26s cubic-bezier(0.4, 0, 0.2, 1); } .collapsible-block.expanded .collapsible-content { @@ -279,6 +281,11 @@ font-size: 14px; line-height: 1.6; color: var(--claude-text-secondary); + scrollbar-width: none; +} + +.content-inner::-webkit-scrollbar { + display: none; } .action-item { @@ -299,6 +306,131 @@ animation: none; } +.stacked-blocks-wrapper { + margin: 12px 0 8px; +} + +.stacked-shell { + position: relative; + border: 1px solid var(--claude-border); + border-radius: 16px; + background: var(--claude-card); + box-shadow: var(--claude-shadow); + overflow: hidden; + transition: + height 280ms cubic-bezier(0.25, 0.9, 0.3, 1), + padding-top 280ms cubic-bezier(0.25, 0.9, 0.3, 1); + min-height: 0; +} + +.stacked-inner { + position: relative; + width: 100%; + transition: transform 280ms cubic-bezier(0.25, 0.9, 0.3, 1); +} + +.stacked-viewport { + overflow: hidden; + position: relative; + width: 100%; +} + +.stacked-item { + border-bottom: 1px solid var(--claude-border); +} + +.stacked-item:last-child { + border-bottom: none; +} + +.stacked-block { + margin: 0; + border: none; + border-radius: 0; + box-shadow: none; + background: transparent; +} + +.stacked-more-block { + position: absolute; + inset: 0 0 auto 0; + background: var(--claude-card); + border-bottom: 0 solid var(--claude-border); + display: flex; + align-items: center; + gap: 10px; + padding: 0 22px; + height: 0; + opacity: 0; + overflow: hidden; + cursor: pointer; + z-index: 2; + transition: + height 280ms cubic-bezier(0.25, 0.9, 0.3, 1), + padding 280ms cubic-bezier(0.25, 0.9, 0.3, 1), + border-bottom-width 280ms cubic-bezier(0.25, 0.9, 0.3, 1), + opacity 160ms ease; +} + +.stacked-more-block.visible { + opacity: 1; + padding: 14px 22px; + border-bottom-width: 1px; +} + +.more-icon { + width: 18px; + height: 18px; + display: inline-block; + object-fit: contain; +} + +.more-copy { + display: flex; + flex-direction: column; + gap: 2px; +} + +.more-title { + font-weight: 700; + color: var(--claude-text); +} + +.more-desc { + color: var(--claude-text-secondary); + font-size: 12px; +} + +.stacked-enter-from { + opacity: 0; + transform: translateY(14px); +} + +.stacked-enter-active { + transition: all 220ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.stacked-leave-active { + position: absolute; + width: 100%; + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.stacked-leave-to { + opacity: 0; + transform: translateY(-12px); +} + +.stacked-move { + transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.stacked-block .collapsible-content { + transition: + max-height 280ms cubic-bezier(0.25, 0.9, 0.3, 1), + opacity 220ms ease; +} + .progress-indicator { position: absolute; bottom: 0; diff --git a/static/src/styles/components/overlays/_overlays.scss b/static/src/styles/components/overlays/_overlays.scss index 34a9c01..c17cfd4 100644 --- a/static/src/styles/components/overlays/_overlays.scss +++ b/static/src/styles/components/overlays/_overlays.scss @@ -170,6 +170,81 @@ margin-bottom: 18px; } +.toggle-row { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + border-radius: 14px; + border: 1px solid var(--theme-control-border); + background: var(--theme-surface-muted); + cursor: pointer; + transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; + position: relative; +} + +.toggle-row:hover { + border-color: var(--theme-control-border); + box-shadow: none; + background: var(--theme-surface-muted); +} + +.toggle-row:focus-within { + border-color: var(--theme-control-border); + box-shadow: none; +} + +.toggle-row input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.toggle-row .fancy-check { + width: 28px; + height: 28px; + border-radius: 6px; + background: transparent; + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: none; + transition: opacity 0.2s ease; +} + +.toggle-row .fancy-check svg { + width: 22px; + height: 22px; + overflow: visible; +} + +.fancy-path { + fill: none; + stroke: var(--claude-text-secondary); + stroke-width: 5; + stroke-linecap: round; + stroke-linejoin: round; + transition: stroke-dasharray 0.5s ease, stroke-dashoffset 0.5s ease, stroke 0.2s ease; + stroke-dasharray: 241 9999999; + stroke-dashoffset: 0; +} + +.toggle-row input:checked + .fancy-check { + background: transparent; + box-shadow: none; +} + +.toggle-row input:checked + .fancy-check .fancy-path { + stroke: var(--claude-accent); + stroke-dasharray: 70.5096664428711 9999999; + stroke-dashoffset: -262.2723388671875; +} + +.toggle-row span { + color: var(--claude-text); + font-weight: 500; +} + .personalization-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr);