feat(ui): 极简模式展开高度限制开关与个人空间持久化
This commit is contained in:
parent
af56de6812
commit
cc8d5817aa
@ -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"))
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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();
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user