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,
|
"enabled": False,
|
||||||
"self_identify": "",
|
"self_identify": "",
|
||||||
"user_name": "",
|
"user_name": "",
|
||||||
|
"use_custom_names": False,
|
||||||
"profession": "",
|
"profession": "",
|
||||||
"tone": "",
|
"tone": "",
|
||||||
"considerations": [],
|
"considerations": [],
|
||||||
@ -190,6 +191,12 @@ def sanitize_personalization_payload(
|
|||||||
else:
|
else:
|
||||||
base["enhanced_tool_display"] = bool(base.get("enhanced_tool_display", True))
|
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
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { usePolicyStore } from '../../stores/policy';
|
import { usePolicyStore } from '../../stores/policy';
|
||||||
import { useModelStore } from '../../stores/model';
|
import { useModelStore } from '../../stores/model';
|
||||||
|
import { usePersonalizationStore } from '../../stores/personalization';
|
||||||
import { initializeLegacySocket } from '../../composables/useLegacySocket';
|
import { initializeLegacySocket } from '../../composables/useLegacySocket';
|
||||||
import { renderMarkdown as renderMarkdownHelper } from '../../composables/useMarkdownRenderer';
|
import { renderMarkdown as renderMarkdownHelper } from '../../composables/useMarkdownRenderer';
|
||||||
import {
|
import {
|
||||||
@ -1109,6 +1110,14 @@ export const uiMethods = {
|
|||||||
await policyStore.fetchPolicy();
|
await policyStore.fetchPolicy();
|
||||||
this.applyPolicyUiLocks();
|
this.applyPolicyUiLocks();
|
||||||
|
|
||||||
|
// 加载个性化设置
|
||||||
|
const personalizationStore = usePersonalizationStore();
|
||||||
|
if (!personalizationStore.loaded && !personalizationStore.loading) {
|
||||||
|
personalizationStore.fetchPersonalization().catch(err => {
|
||||||
|
console.warn('加载个性化设置失败:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const focusPromise = this.focusFetchFiles();
|
const focusPromise = this.focusFetchFiles();
|
||||||
const todoPromise = this.fileFetchTodoList();
|
const todoPromise = this.fileFetchTodoList();
|
||||||
let treePromise: Promise<any> | null = null;
|
let treePromise: Promise<any> | null = null;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<div v-if="msg.role === 'user'" class="user-message">
|
<div v-if="msg.role === 'user'" class="user-message">
|
||||||
<div class="message-header icon-label">
|
<div class="message-header icon-label">
|
||||||
<span class="icon icon-sm" :style="iconStyleSafe('user')" aria-hidden="true"></span>
|
<span class="icon icon-sm" :style="iconStyleSafe('user')" aria-hidden="true"></span>
|
||||||
<span>用户</span>
|
<span>{{ userName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-text user-bubble-text">
|
<div class="message-text user-bubble-text">
|
||||||
<div v-if="msg.content" class="bubble-text">{{ msg.content }}</div>
|
<div v-if="msg.content" class="bubble-text">{{ msg.content }}</div>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<!-- 只有当前一条消息是 user 且当前消息有内容时才显示 -->
|
<!-- 只有当前一条消息是 user 且当前消息有内容时才显示 -->
|
||||||
<div v-if="index > 0 && filteredMessages[index - 1].role === 'user' && (msg.actions?.length > 0 || msg.awaitingFirstContent)" class="message-header icon-label">
|
<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 class="icon icon-sm" :style="iconStyleSafe('bot')" aria-hidden="true"></span>
|
||||||
<span>AI Assistant</span>
|
<span>{{ aiAssistantName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="msg.awaitingFirstContent"
|
v-if="msg.awaitingFirstContent"
|
||||||
@ -390,6 +390,18 @@ const stackedBlocksEnabled = computed(() => {
|
|||||||
const enabled = personalization.experiments.stackedBlocksEnabled;
|
const enabled = personalization.experiments.stackedBlocksEnabled;
|
||||||
return enabled !== false;
|
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(() =>
|
const filteredMessages = computed(() =>
|
||||||
(props.messages || []).filter(m => !(m && m.metadata && m.metadata.system_injected_image) && m.role !== 'system')
|
(props.messages || []).filter(m => !(m && m.metadata && m.metadata.system_injected_image) && m.role !== 'system')
|
||||||
);
|
);
|
||||||
|
|||||||
@ -382,6 +382,29 @@
|
|||||||
</section>
|
</section>
|
||||||
<section v-else-if="activeTab === 'behavior'" key="behavior" class="personal-page behavior-page">
|
<section v-else-if="activeTab === 'behavior'" key="behavior" class="personal-page behavior-page">
|
||||||
<div class="behavior-section">
|
<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">
|
||||||
<div class="behavior-field-header">
|
<div class="behavior-field-header">
|
||||||
<span class="field-title">增强工具显示</span>
|
<span class="field-title">增强工具显示</span>
|
||||||
|
|||||||
@ -110,16 +110,12 @@ export function toggleScrollLock(ctx: ScrollContext) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在页面初始化/刷新后同步滚动锁定的默认状态:
|
* 在页面初始化/刷新后同步滚动锁定的默认状态:
|
||||||
* 仅当存在输出(流式中或有未完成工具)时才保持锁定,否则自动解锁。
|
* 始终保持锁定状态,不再根据输出状态自动解锁。
|
||||||
*/
|
*/
|
||||||
export function normalizeScrollLock(ctx: ScrollContext) {
|
export function normalizeScrollLock(ctx: ScrollContext) {
|
||||||
const active =
|
// 确保滚动锁定始终启用
|
||||||
(typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : false) ||
|
if (!ctx.autoScrollEnabled) {
|
||||||
!!ctx.streamingMessage ||
|
ctx.chatSetScrollState?.({ autoScrollEnabled: true, userScrolling: false });
|
||||||
(typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false);
|
|
||||||
|
|
||||||
if (!active && ctx.autoScrollEnabled) {
|
|
||||||
ctx.chatSetScrollState?.({ autoScrollEnabled: false, userScrolling: false });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ interface PersonalForm {
|
|||||||
enabled_skills: string[];
|
enabled_skills: string[];
|
||||||
self_identify: string;
|
self_identify: string;
|
||||||
user_name: string;
|
user_name: string;
|
||||||
|
use_custom_names: boolean;
|
||||||
profession: string;
|
profession: string;
|
||||||
tone: string;
|
tone: string;
|
||||||
considerations: string[];
|
considerations: string[];
|
||||||
@ -67,6 +68,7 @@ const defaultForm = (): PersonalForm => ({
|
|||||||
enabled_skills: [],
|
enabled_skills: [],
|
||||||
self_identify: '',
|
self_identify: '',
|
||||||
user_name: '',
|
user_name: '',
|
||||||
|
use_custom_names: false,
|
||||||
profession: '',
|
profession: '',
|
||||||
tone: '',
|
tone: '',
|
||||||
considerations: [],
|
considerations: [],
|
||||||
@ -201,6 +203,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
: [],
|
: [],
|
||||||
self_identify: data.self_identify || '',
|
self_identify: data.self_identify || '',
|
||||||
user_name: data.user_name || '',
|
user_name: data.user_name || '',
|
||||||
|
use_custom_names: !!data.use_custom_names,
|
||||||
profession: data.profession || '',
|
profession: data.profession || '',
|
||||||
tone: data.tone || '',
|
tone: data.tone || '',
|
||||||
considerations: Array.isArray(data.considerations) ? [...data.considerations] : [],
|
considerations: Array.isArray(data.considerations) ? [...data.considerations] : [],
|
||||||
|
|||||||
@ -3362,6 +3362,10 @@ body[data-theme='dark'] {
|
|||||||
filter: brightness(0) invert(1);
|
filter: brightness(0) invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-lock-toggle.locked .scroll-lock-btn svg {
|
||||||
|
stroke: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
/* 左侧面板头部(文件/待办事项/子智能体标签栏) */
|
/* 左侧面板头部(文件/待办事项/子智能体标签栏) */
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user