feat(ui): 极简模式展开高度限制开关与个人空间持久化

This commit is contained in:
JOJO 2026-07-11 19:58:35 +08:00
parent af56de6812
commit cc8d5817aa
4 changed files with 140 additions and 2 deletions

View File

@ -98,6 +98,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"block_display_mode": "stacked", # 堆叠块显示模式traditional-传统列表 / stacked-堆叠动画 / minimal-极简模式
"show_status_avatar": True, # 是否显示助手状态形象
"stacked_hide_borders": False, # 堆叠块隐藏边线
"minimal_expand_height_limited": True, # 极简模式展开摘要行时限制最大高度
"show_git_status_bar": True, # 是否显示输入栏上方 Git 状态栏
"versioning_enabled_by_default": True, # 新对话是否默认开启版本控制
"versioning_backup_mode": "shallow", # 文件备份方式shallow-浅备份只备份AI编辑的文件/ full-完全备份(整个工作区)
@ -467,6 +468,12 @@ def sanitize_personalization_payload(
else:
base["stacked_hide_borders"] = bool(base.get("stacked_hide_borders", False))
# 极简模式展开高度限制
if "minimal_expand_height_limited" in data:
base["minimal_expand_height_limited"] = bool(data.get("minimal_expand_height_limited"))
else:
base["minimal_expand_height_limited"] = bool(base.get("minimal_expand_height_limited", True))
# 助手状态形象显示开关
if "show_status_avatar" in data:
base["show_status_avatar"] = bool(data.get("show_status_avatar"))

View File

@ -74,7 +74,11 @@
<!-- 步骤容器展开时显示所有步骤 -->
<div class="steps-container" :class="{ show: expandedGroups.has(group.id) }">
<div class="steps-wrapper">
<div
class="steps-wrapper"
:class="{ 'height-limited': heightLimited }"
:ref="(el) => registerStepsWrapper(group.id, el)"
>
<div v-for="step in getSummarySteps(group.actions)" :key="step.id" class="step-item">
<div class="step-timeline">
<span
@ -150,6 +154,9 @@
import { ref, reactive, computed, watch, nextTick, onBeforeUnmount, Component } from 'vue';
import { usePersonalizationStore } from '@/stores/personalization';
import { renderEnhancedToolResult } from './actions/toolRenderers';
const personalizationStore = usePersonalizationStore();
const heightLimited = computed(() => personalizationStore.form.minimal_expand_height_limited);
import { getRandomLoader } from './loaders/index';
import MarkdownRenderer from './MarkdownRenderer.vue';
@ -206,9 +213,9 @@ const emit = defineEmits<{
(event: 'group-toggle', payload: { groupId: string; expanded: boolean }): void;
}>();
const personalizationStore = usePersonalizationStore();
const expandedGroups = ref(new Set<string>());
const thinkingRefs = new Map<string, HTMLElement>();
const stepsWrapperRefs = new Map<string, HTMLElement>();
const summaryLoaders = new Map<string, Component>();
const TOOL_REEL_ITEM_HEIGHT = 26;
const TOOL_REEL_INTERVAL_MS = 1450;
@ -804,6 +811,21 @@ const getSummarySteps = (actions: Action[]) => {
}));
};
const registerStepsWrapper = (groupId: string, el: Element | null) => {
if (el instanceof HTMLElement) {
stepsWrapperRefs.set(groupId, el);
} else {
stepsWrapperRefs.delete(groupId);
}
};
const scrollStepsWrapperToBottom = (groupId: string) => {
const wrapper = stepsWrapperRefs.get(groupId);
if (wrapper) {
wrapper.scrollTop = wrapper.scrollHeight;
}
};
const toggleExpand = (groupId: string) => {
if (expandedGroups.value.has(groupId)) {
expandedGroups.value.delete(groupId);
@ -831,6 +853,10 @@ const toggleExpand = (groupId: string) => {
nextTick(() => {
const group = blockGroups.value.find((g) => g.id === groupId);
if (group && group.actions) {
//
if (isSummaryRunning(group.actions, groupId)) {
scrollStepsWrapperToBottom(groupId);
}
group.actions.forEach((action) => {
if (action.type === 'thinking') {
const blockId = action.id || action.blockId;
@ -954,6 +980,19 @@ watch(
() => props.actions,
() => {
syncToolReels();
// running
nextTick(() => {
blockGroups.value.forEach((group) => {
if (
group.type === 'summary' &&
group.actions &&
expandedGroups.value.has(group.id) &&
isSummaryRunning(group.actions, group.id)
) {
scrollStepsWrapperToBottom(group.id);
}
});
});
},
{ deep: true, immediate: true }
);
@ -967,6 +1006,7 @@ watch(
onBeforeUnmount(() => {
Object.keys(toolReelStates).forEach(clearToolReelTimers);
stepsWrapperRefs.clear();
});
</script>
@ -1143,6 +1183,17 @@ onBeforeUnmount(() => {
min-height: 0;
}
.steps-wrapper.height-limited {
max-height: min(450px, 70vh);
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.steps-wrapper.height-limited::-webkit-scrollbar {
display: none;
}
.steps-container.show {
grid-template-rows: 1fr;
}

View File

@ -305,6 +305,28 @@
></path></svg
></span>
</label>
<label
v-if="currentBlockDisplayMode === 'minimal'"
class="settings-toggle-row"
>
<span class="settings-row-copy">
<span class="settings-row-title">限制展开高度</span>
<span class="settings-row-desc">极简模式展开摘要行时限制最大高度超出可在内部滚动</span>
</span>
<input
type="checkbox"
:checked="minimalExpandHeightLimited"
@change="handleMinimalExpandHeightLimitedChange($event)"
/>
<span class="fancy-check" aria-hidden="true"
><svg viewBox="0 0 64 64">
<path
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
pathLength="575.0541381835938"
class="fancy-path"
></path></svg
></span>
</label>
<div class="settings-select-row">
<span class="settings-row-copy">
<span class="settings-row-title">智能体交流风格</span>
@ -802,6 +824,28 @@
></path></svg
></span>
</label>
<label
v-if="currentBlockDisplayMode === 'minimal'"
class="settings-toggle-row"
>
<span class="settings-row-copy">
<span class="settings-row-title">限制展开高度</span>
<span class="settings-row-desc">极简模式展开摘要行时限制最大高度超出可在内部滚动</span>
</span>
<input
type="checkbox"
:checked="minimalExpandHeightLimited"
@change="handleMinimalExpandHeightLimitedChange($event)"
/>
<span class="fancy-check" aria-hidden="true"
><svg viewBox="0 0 64 64">
<path
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
pathLength="575.0541381835938"
class="fancy-path"
></path></svg
></span>
</label>
<div class="settings-select-row">
<span class="settings-row-copy"
><span class="settings-row-title">简略消息显示</span
@ -2152,6 +2196,7 @@ const conversationContinuityLabel = computed(() => {
const currentBlockDisplayMode = computed(() => experiments.value.blockDisplayMode);
const stackedHideBorders = computed(() => form.value.stacked_hide_borders);
const minimalExpandHeightLimited = computed(() => form.value.minimal_expand_height_limited);
const blockDisplayLabel = computed(() => {
return (
@ -2700,6 +2745,14 @@ const handleStackedHideBordersChange = (event: Event) => {
});
};
const handleMinimalExpandHeightLimitedChange = (event: Event) => {
const target = event.target as HTMLInputElement;
personalization.updateField({
key: 'minimal_expand_height_limited',
value: target.checked
});
};
const compactMessageDisplayOptions = [
{
id: 'full',

View File

@ -31,6 +31,7 @@ interface PersonalForm {
show_git_status_bar: boolean;
auto_open_terminal_panel: boolean;
stacked_hide_borders: boolean;
minimal_expand_height_limited: boolean;
enhanced_tool_display_categories: string[];
enabled_skills: string[];
self_identify: string;
@ -104,6 +105,7 @@ const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking', 'deep'];
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
const THEME_STORAGE_KEY = 'agents_ui_theme';
const STACKED_HIDE_BORDERS_STORAGE_KEY = 'agents_stacked_hide_borders';
const MINIMAL_EXPAND_HEIGHT_LIMITED_STORAGE_KEY = 'agents_minimal_expand_height_limited';
const loadCachedTheme = (): PersonalForm['theme'] => {
if (typeof window === 'undefined' || !window.localStorage) {
@ -153,6 +155,25 @@ const persistStackedHideBorders = (value: boolean) => {
}
};
const loadCachedMinimalExpandHeightLimited = (): boolean => {
if (typeof window === 'undefined' || !window.localStorage) {
return true;
}
const saved = window.localStorage.getItem(MINIMAL_EXPAND_HEIGHT_LIMITED_STORAGE_KEY);
return saved === null ? true : saved === 'true';
};
const persistMinimalExpandHeightLimited = (value: boolean) => {
if (typeof window === 'undefined' || !window.localStorage) {
return;
}
try {
window.localStorage.setItem(MINIMAL_EXPAND_HEIGHT_LIMITED_STORAGE_KEY, value ? 'true' : 'false');
} catch (error) {
console.warn('写入极简展开高度限制设置失败:', error);
}
};
const defaultForm = (): PersonalForm => ({
enabled: false,
communication_style: 'default',
@ -174,6 +195,7 @@ const defaultForm = (): PersonalForm => ({
show_git_status_bar: true,
auto_open_terminal_panel: true,
stacked_hide_borders: loadCachedStackedHideBorders(),
minimal_expand_height_limited: loadCachedMinimalExpandHeightLimited(),
enhanced_tool_display_categories: [],
enabled_skills: [],
self_identify: '',
@ -372,6 +394,7 @@ export const usePersonalizationStore = defineStore('personalization', {
show_git_status_bar: data.show_git_status_bar !== false,
auto_open_terminal_panel: data.auto_open_terminal_panel !== false,
stacked_hide_borders: !!data.stacked_hide_borders,
minimal_expand_height_limited: data.minimal_expand_height_limited !== false,
enhanced_tool_display_categories: Array.isArray(data.enhanced_tool_display_categories)
? data.enhanced_tool_display_categories.filter(
(item: unknown) => typeof item === 'string'
@ -469,6 +492,7 @@ export const usePersonalizationStore = defineStore('personalization', {
}
persistDefaultHideWorkspace(this.form.default_hide_workspace);
persistStackedHideBorders(this.form.stacked_hide_borders);
persistMinimalExpandHeightLimited(this.form.minimal_expand_height_limited);
if (this.form.default_hide_workspace) {
useUiStore().setWorkspaceCollapsed(true);
}
@ -673,6 +697,9 @@ export const usePersonalizationStore = defineStore('personalization', {
if (payload.key === 'stacked_hide_borders') {
persistStackedHideBorders(!!payload.value);
}
if (payload.key === 'minimal_expand_height_limited') {
persistMinimalExpandHeightLimited(!!payload.value);
}
this.clearFeedback();
this.scheduleAutoSave();
},