feat: 优化滚动锁定和个性化称呼功能

滚动锁定优化:
- 滚动锁定默认始终启用,不再根据输出状态自动解锁
- 深色模式下锁定图标显示为白色,与箭头图标保持一致

个性化称呼功能:
- AI 助手名称根据个性化设置中的"自称"动态显示
- 用户名称根据个性化设置中的"称呼"动态显示
- 页面初始化时自动加载个性化设置,无需打开个人空间
- 在"模型行为"中新增"使用自定义称呼"开关
- 开关关闭时显示默认的"AI Assistant"和"用户"
- 修复保存后开关状态被重置的问题

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
JOJO 2026-03-13 12:10:08 +08:00
parent bd863f6d38
commit f3179e2a97
7 changed files with 64 additions and 10 deletions

View File

@ -28,6 +28,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"enabled": False,
"self_identify": "",
"user_name": "",
"use_custom_names": False,
"profession": "",
"tone": "",
"considerations": [],
@ -190,6 +191,12 @@ def sanitize_personalization_payload(
else:
base["enhanced_tool_display"] = bool(base.get("enhanced_tool_display", True))
# 使用自定义称呼
if "use_custom_names" in data:
base["use_custom_names"] = bool(data.get("use_custom_names"))
else:
base["use_custom_names"] = bool(base.get("use_custom_names"))
return base

View File

@ -1,6 +1,7 @@
// @ts-nocheck
import { usePolicyStore } from '../../stores/policy';
import { useModelStore } from '../../stores/model';
import { usePersonalizationStore } from '../../stores/personalization';
import { initializeLegacySocket } from '../../composables/useLegacySocket';
import { renderMarkdown as renderMarkdownHelper } from '../../composables/useMarkdownRenderer';
import {
@ -1109,6 +1110,14 @@ export const uiMethods = {
await policyStore.fetchPolicy();
this.applyPolicyUiLocks();
// 加载个性化设置
const personalizationStore = usePersonalizationStore();
if (!personalizationStore.loaded && !personalizationStore.loading) {
personalizationStore.fetchPersonalization().catch(err => {
console.warn('加载个性化设置失败:', err);
});
}
const focusPromise = this.focusFetchFiles();
const todoPromise = this.fileFetchTodoList();
let treePromise: Promise<any> | null = null;

View File

@ -5,7 +5,7 @@
<div v-if="msg.role === 'user'" class="user-message">
<div class="message-header icon-label">
<span class="icon icon-sm" :style="iconStyleSafe('user')" aria-hidden="true"></span>
<span>用户</span>
<span>{{ userName }}</span>
</div>
<div class="message-text user-bubble-text">
<div v-if="msg.content" class="bubble-text">{{ msg.content }}</div>
@ -26,7 +26,7 @@
<!-- 只有当前一条消息是 user 且当前消息有内容时才显示 -->
<div v-if="index > 0 && filteredMessages[index - 1].role === 'user' && (msg.actions?.length > 0 || msg.awaitingFirstContent)" class="message-header icon-label">
<span class="icon icon-sm" :style="iconStyleSafe('bot')" aria-hidden="true"></span>
<span>AI Assistant</span>
<span>{{ aiAssistantName }}</span>
</div>
<div
v-if="msg.awaitingFirstContent"
@ -390,6 +390,18 @@ const stackedBlocksEnabled = computed(() => {
const enabled = personalization.experiments.stackedBlocksEnabled;
return enabled !== false;
});
const aiAssistantName = computed(() => {
if (!personalization.form.use_custom_names) {
return 'AI Assistant';
}
return personalization.form.self_identify || 'AI Assistant';
});
const userName = computed(() => {
if (!personalization.form.use_custom_names) {
return '用户';
}
return personalization.form.user_name || '用户';
});
const filteredMessages = computed(() =>
(props.messages || []).filter(m => !(m && m.metadata && m.metadata.system_injected_image) && m.role !== 'system')
);

View File

@ -382,6 +382,29 @@
</section>
<section v-else-if="activeTab === 'behavior'" key="behavior" class="personal-page behavior-page">
<div class="behavior-section">
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">使用自定义称呼</span>
<p class="field-desc">开启后对话区域将使用您在"个性化设置"中配置的自称和称呼关闭则显示默认的"AI Assistant""用户"</p>
</div>
<label class="toggle-row">
<input
type="checkbox"
:checked="form.use_custom_names"
@change="personalization.updateField({ key: 'use_custom_names', value: $event.target.checked })"
/>
<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>
<span>在对话中显示自定义称呼</span>
</label>
</div>
<div class="behavior-field">
<div class="behavior-field-header">
<span class="field-title">增强工具显示</span>

View File

@ -110,16 +110,12 @@ export function toggleScrollLock(ctx: ScrollContext) {
/**
* /
*
*
*/
export function normalizeScrollLock(ctx: ScrollContext) {
const active =
(typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : false) ||
!!ctx.streamingMessage ||
(typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false);
if (!active && ctx.autoScrollEnabled) {
ctx.chatSetScrollState?.({ autoScrollEnabled: false, userScrolling: false });
// 确保滚动锁定始终启用
if (!ctx.autoScrollEnabled) {
ctx.chatSetScrollState?.({ autoScrollEnabled: true, userScrolling: false });
}
}

View File

@ -11,6 +11,7 @@ interface PersonalForm {
enabled_skills: string[];
self_identify: string;
user_name: string;
use_custom_names: boolean;
profession: string;
tone: string;
considerations: string[];
@ -67,6 +68,7 @@ const defaultForm = (): PersonalForm => ({
enabled_skills: [],
self_identify: '',
user_name: '',
use_custom_names: false,
profession: '',
tone: '',
considerations: [],
@ -201,6 +203,7 @@ export const usePersonalizationStore = defineStore('personalization', {
: [],
self_identify: data.self_identify || '',
user_name: data.user_name || '',
use_custom_names: !!data.use_custom_names,
profession: data.profession || '',
tone: data.tone || '',
considerations: Array.isArray(data.considerations) ? [...data.considerations] : [],

View File

@ -3362,6 +3362,10 @@ body[data-theme='dark'] {
filter: brightness(0) invert(1);
}
.scroll-lock-toggle.locked .scroll-lock-btn svg {
stroke: #ffffff;
}
/* 左侧面板头部(文件/待办事项/子智能体标签栏) */
.sidebar-header {
background: #1a1a1a;