feat: 优化滚动锁定和个性化称呼功能
滚动锁定优化: - 滚动锁定默认始终启用,不再根据输出状态自动解锁 - 深色模式下锁定图标显示为白色,与箭头图标保持一致 个性化称呼功能: - AI 助手名称根据个性化设置中的"自称"动态显示 - 用户名称根据个性化设置中的"称呼"动态显示 - 页面初始化时自动加载个性化设置,无需打开个人空间 - 在"模型行为"中新增"使用自定义称呼"开关 - 开关关闭时显示默认的"AI Assistant"和"用户" - 修复保存后开关状态被重置的问题 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd863f6d38
commit
f3179e2a97
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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')
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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] : [],
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user