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-极简模式
|
"block_display_mode": "stacked", # 堆叠块显示模式:traditional-传统列表 / stacked-堆叠动画 / minimal-极简模式
|
||||||
"show_status_avatar": True, # 是否显示助手状态形象
|
"show_status_avatar": True, # 是否显示助手状态形象
|
||||||
"stacked_hide_borders": False, # 堆叠块隐藏边线
|
"stacked_hide_borders": False, # 堆叠块隐藏边线
|
||||||
|
"minimal_expand_height_limited": True, # 极简模式展开摘要行时限制最大高度
|
||||||
"show_git_status_bar": True, # 是否显示输入栏上方 Git 状态栏
|
"show_git_status_bar": True, # 是否显示输入栏上方 Git 状态栏
|
||||||
"versioning_enabled_by_default": True, # 新对话是否默认开启版本控制
|
"versioning_enabled_by_default": True, # 新对话是否默认开启版本控制
|
||||||
"versioning_backup_mode": "shallow", # 文件备份方式:shallow-浅备份(只备份AI编辑的文件)/ full-完全备份(整个工作区)
|
"versioning_backup_mode": "shallow", # 文件备份方式:shallow-浅备份(只备份AI编辑的文件)/ full-完全备份(整个工作区)
|
||||||
@ -467,6 +468,12 @@ def sanitize_personalization_payload(
|
|||||||
else:
|
else:
|
||||||
base["stacked_hide_borders"] = bool(base.get("stacked_hide_borders", False))
|
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:
|
if "show_status_avatar" in data:
|
||||||
base["show_status_avatar"] = bool(data.get("show_status_avatar"))
|
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-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 v-for="step in getSummarySteps(group.actions)" :key="step.id" class="step-item">
|
||||||
<div class="step-timeline">
|
<div class="step-timeline">
|
||||||
<span
|
<span
|
||||||
@ -150,6 +154,9 @@
|
|||||||
import { ref, reactive, computed, watch, nextTick, onBeforeUnmount, Component } from 'vue';
|
import { ref, reactive, computed, watch, nextTick, onBeforeUnmount, Component } from 'vue';
|
||||||
import { usePersonalizationStore } from '@/stores/personalization';
|
import { usePersonalizationStore } from '@/stores/personalization';
|
||||||
import { renderEnhancedToolResult } from './actions/toolRenderers';
|
import { renderEnhancedToolResult } from './actions/toolRenderers';
|
||||||
|
|
||||||
|
const personalizationStore = usePersonalizationStore();
|
||||||
|
const heightLimited = computed(() => personalizationStore.form.minimal_expand_height_limited);
|
||||||
import { getRandomLoader } from './loaders/index';
|
import { getRandomLoader } from './loaders/index';
|
||||||
import MarkdownRenderer from './MarkdownRenderer.vue';
|
import MarkdownRenderer from './MarkdownRenderer.vue';
|
||||||
|
|
||||||
@ -206,9 +213,9 @@ const emit = defineEmits<{
|
|||||||
(event: 'group-toggle', payload: { groupId: string; expanded: boolean }): void;
|
(event: 'group-toggle', payload: { groupId: string; expanded: boolean }): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const personalizationStore = usePersonalizationStore();
|
|
||||||
const expandedGroups = ref(new Set<string>());
|
const expandedGroups = ref(new Set<string>());
|
||||||
const thinkingRefs = new Map<string, HTMLElement>();
|
const thinkingRefs = new Map<string, HTMLElement>();
|
||||||
|
const stepsWrapperRefs = new Map<string, HTMLElement>();
|
||||||
const summaryLoaders = new Map<string, Component>();
|
const summaryLoaders = new Map<string, Component>();
|
||||||
const TOOL_REEL_ITEM_HEIGHT = 26;
|
const TOOL_REEL_ITEM_HEIGHT = 26;
|
||||||
const TOOL_REEL_INTERVAL_MS = 1450;
|
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) => {
|
const toggleExpand = (groupId: string) => {
|
||||||
if (expandedGroups.value.has(groupId)) {
|
if (expandedGroups.value.has(groupId)) {
|
||||||
expandedGroups.value.delete(groupId);
|
expandedGroups.value.delete(groupId);
|
||||||
@ -831,6 +853,10 @@ const toggleExpand = (groupId: string) => {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const group = blockGroups.value.find((g) => g.id === groupId);
|
const group = blockGroups.value.find((g) => g.id === groupId);
|
||||||
if (group && group.actions) {
|
if (group && group.actions) {
|
||||||
|
// 运行中展开时,将展开区滚动到底部
|
||||||
|
if (isSummaryRunning(group.actions, groupId)) {
|
||||||
|
scrollStepsWrapperToBottom(groupId);
|
||||||
|
}
|
||||||
group.actions.forEach((action) => {
|
group.actions.forEach((action) => {
|
||||||
if (action.type === 'thinking') {
|
if (action.type === 'thinking') {
|
||||||
const blockId = action.id || action.blockId;
|
const blockId = action.id || action.blockId;
|
||||||
@ -954,6 +980,19 @@ watch(
|
|||||||
() => props.actions,
|
() => props.actions,
|
||||||
() => {
|
() => {
|
||||||
syncToolReels();
|
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 }
|
{ deep: true, immediate: true }
|
||||||
);
|
);
|
||||||
@ -967,6 +1006,7 @@ watch(
|
|||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
Object.keys(toolReelStates).forEach(clearToolReelTimers);
|
Object.keys(toolReelStates).forEach(clearToolReelTimers);
|
||||||
|
stepsWrapperRefs.clear();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -1143,6 +1183,17 @@ onBeforeUnmount(() => {
|
|||||||
min-height: 0;
|
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 {
|
.steps-container.show {
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -305,6 +305,28 @@
|
|||||||
></path></svg
|
></path></svg
|
||||||
></span>
|
></span>
|
||||||
</label>
|
</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">
|
<div class="settings-select-row">
|
||||||
<span class="settings-row-copy">
|
<span class="settings-row-copy">
|
||||||
<span class="settings-row-title">智能体交流风格</span>
|
<span class="settings-row-title">智能体交流风格</span>
|
||||||
@ -802,6 +824,28 @@
|
|||||||
></path></svg
|
></path></svg
|
||||||
></span>
|
></span>
|
||||||
</label>
|
</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">
|
<div class="settings-select-row">
|
||||||
<span class="settings-row-copy"
|
<span class="settings-row-copy"
|
||||||
><span class="settings-row-title">简略消息显示</span
|
><span class="settings-row-title">简略消息显示</span
|
||||||
@ -2152,6 +2196,7 @@ const conversationContinuityLabel = computed(() => {
|
|||||||
const currentBlockDisplayMode = computed(() => experiments.value.blockDisplayMode);
|
const currentBlockDisplayMode = computed(() => experiments.value.blockDisplayMode);
|
||||||
|
|
||||||
const stackedHideBorders = computed(() => form.value.stacked_hide_borders);
|
const stackedHideBorders = computed(() => form.value.stacked_hide_borders);
|
||||||
|
const minimalExpandHeightLimited = computed(() => form.value.minimal_expand_height_limited);
|
||||||
|
|
||||||
const blockDisplayLabel = computed(() => {
|
const blockDisplayLabel = computed(() => {
|
||||||
return (
|
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 = [
|
const compactMessageDisplayOptions = [
|
||||||
{
|
{
|
||||||
id: 'full',
|
id: 'full',
|
||||||
|
|||||||
@ -31,6 +31,7 @@ interface PersonalForm {
|
|||||||
show_git_status_bar: boolean;
|
show_git_status_bar: boolean;
|
||||||
auto_open_terminal_panel: boolean;
|
auto_open_terminal_panel: boolean;
|
||||||
stacked_hide_borders: boolean;
|
stacked_hide_borders: boolean;
|
||||||
|
minimal_expand_height_limited: boolean;
|
||||||
enhanced_tool_display_categories: string[];
|
enhanced_tool_display_categories: string[];
|
||||||
enabled_skills: string[];
|
enabled_skills: string[];
|
||||||
self_identify: string;
|
self_identify: string;
|
||||||
@ -104,6 +105,7 @@ const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking', 'deep'];
|
|||||||
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
|
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
|
||||||
const THEME_STORAGE_KEY = 'agents_ui_theme';
|
const THEME_STORAGE_KEY = 'agents_ui_theme';
|
||||||
const STACKED_HIDE_BORDERS_STORAGE_KEY = 'agents_stacked_hide_borders';
|
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'] => {
|
const loadCachedTheme = (): PersonalForm['theme'] => {
|
||||||
if (typeof window === 'undefined' || !window.localStorage) {
|
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 => ({
|
const defaultForm = (): PersonalForm => ({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
communication_style: 'default',
|
communication_style: 'default',
|
||||||
@ -174,6 +195,7 @@ const defaultForm = (): PersonalForm => ({
|
|||||||
show_git_status_bar: true,
|
show_git_status_bar: true,
|
||||||
auto_open_terminal_panel: true,
|
auto_open_terminal_panel: true,
|
||||||
stacked_hide_borders: loadCachedStackedHideBorders(),
|
stacked_hide_borders: loadCachedStackedHideBorders(),
|
||||||
|
minimal_expand_height_limited: loadCachedMinimalExpandHeightLimited(),
|
||||||
enhanced_tool_display_categories: [],
|
enhanced_tool_display_categories: [],
|
||||||
enabled_skills: [],
|
enabled_skills: [],
|
||||||
self_identify: '',
|
self_identify: '',
|
||||||
@ -372,6 +394,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
show_git_status_bar: data.show_git_status_bar !== false,
|
show_git_status_bar: data.show_git_status_bar !== false,
|
||||||
auto_open_terminal_panel: data.auto_open_terminal_panel !== false,
|
auto_open_terminal_panel: data.auto_open_terminal_panel !== false,
|
||||||
stacked_hide_borders: !!data.stacked_hide_borders,
|
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)
|
enhanced_tool_display_categories: Array.isArray(data.enhanced_tool_display_categories)
|
||||||
? data.enhanced_tool_display_categories.filter(
|
? data.enhanced_tool_display_categories.filter(
|
||||||
(item: unknown) => typeof item === 'string'
|
(item: unknown) => typeof item === 'string'
|
||||||
@ -469,6 +492,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
}
|
}
|
||||||
persistDefaultHideWorkspace(this.form.default_hide_workspace);
|
persistDefaultHideWorkspace(this.form.default_hide_workspace);
|
||||||
persistStackedHideBorders(this.form.stacked_hide_borders);
|
persistStackedHideBorders(this.form.stacked_hide_borders);
|
||||||
|
persistMinimalExpandHeightLimited(this.form.minimal_expand_height_limited);
|
||||||
if (this.form.default_hide_workspace) {
|
if (this.form.default_hide_workspace) {
|
||||||
useUiStore().setWorkspaceCollapsed(true);
|
useUiStore().setWorkspaceCollapsed(true);
|
||||||
}
|
}
|
||||||
@ -673,6 +697,9 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
if (payload.key === 'stacked_hide_borders') {
|
if (payload.key === 'stacked_hide_borders') {
|
||||||
persistStackedHideBorders(!!payload.value);
|
persistStackedHideBorders(!!payload.value);
|
||||||
}
|
}
|
||||||
|
if (payload.key === 'minimal_expand_height_limited') {
|
||||||
|
persistMinimalExpandHeightLimited(!!payload.value);
|
||||||
|
}
|
||||||
this.clearFeedback();
|
this.clearFeedback();
|
||||||
this.scheduleAutoSave();
|
this.scheduleAutoSave();
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user