feat: 新增全局学习模式

This commit is contained in:
肖应宇 2026-04-09 14:50:43 +08:00
parent 8261415b40
commit 9abe247503
6 changed files with 123 additions and 8 deletions

View File

@ -80,12 +80,21 @@
</button> </button>
</div> </div>
</Transition> </Transition>
<div class="learning-mode-toggle">
<span class="learning-mode-label">学习模式</span>
<FormSwitch
:model-value="settings.learningModeEnabled"
@update:model-value="settingsStore.setLearningModeEnabled($event)"
/>
</div>
</div> </div>
</header> </header>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { storeToRefs } from "pinia";
import { import {
Menu, Menu,
Trash2, Trash2,
@ -97,6 +106,7 @@ import {
import SidebarExpandIcon from "@/components/icons/custom/SidebarExpandIcon.vue"; import SidebarExpandIcon from "@/components/icons/custom/SidebarExpandIcon.vue";
import SidebarCollapseIcon from "@/components/icons/custom/SidebarCollapseIcon.vue"; import SidebarCollapseIcon from "@/components/icons/custom/SidebarCollapseIcon.vue";
import { useSettingsStore } from "@/stores/settings.ts"; import { useSettingsStore } from "@/stores/settings.ts";
import FormSwitch from "@/components/ui/FormSwitch.vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -129,6 +139,7 @@ const emit = defineEmits<{
const showMoreMenu = ref(false); const showMoreMenu = ref(false);
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const { settings } = storeToRefs(settingsStore);
function handleClear() { function handleClear() {
if (confirm("确定要清空当前对话吗?此操作不可恢复。")) { if (confirm("确定要清空当前对话吗?此操作不可恢复。")) {
@ -244,6 +255,24 @@ if (typeof window !== "undefined") {
position: relative; position: relative;
} }
.learning-mode-toggle {
display: flex;
align-items: center;
gap: 10px;
margin-left: 8px;
}
.learning-mode-label {
font-size: 13px;
color: #6b7280;
user-select: none;
white-space: nowrap;
.dark & {
color: #9ca3af;
}
}
.header-btn { .header-btn {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -216,7 +216,10 @@ async function handleSend(
} }
// 使使 // 使使
const systemPrompt = options?.systemPrompt || currentConversation.value?.settings?.systemPrompt; const systemPrompt =
options?.systemPrompt ||
currentConversation.value?.settings?.systemPrompt ||
settings.value.defaultSystemPrompt;
// //
const existingMessages = currentConversation.value?.messages || []; const existingMessages = currentConversation.value?.messages || [];
@ -283,7 +286,7 @@ async function handleSend(
deepSearch: options?.deepSearch, deepSearch: options?.deepSearch,
webSearch: options?.webSearch, webSearch: options?.webSearch,
deepThinking: options?.deepThinking, deepThinking: options?.deepThinking,
systemPrompt: options?.systemPrompt, systemPrompt,
}, },
abortController.value.signal, abortController.value.signal,
); );
@ -404,6 +407,9 @@ async function handleRetry(messageId: string) {
currentStreamingMessageId.value = messageId; currentStreamingMessageId.value = messageId;
chatStore.startStreaming(); chatStore.startStreaming();
abortController.value = new AbortController(); abortController.value = new AbortController();
const systemPrompt =
currentConversation.value?.settings?.systemPrompt ||
settings.value.defaultSystemPrompt;
try { try {
const stream = chatApi.streamChat( const stream = chatApi.streamChat(
@ -413,6 +419,7 @@ async function handleRetry(messageId: string) {
model: settings.value.defaultModel, model: settings.value.defaultModel,
stream: true, stream: true,
history: priorMessages, history: priorMessages,
systemPrompt,
}, },
abortController.value.signal, abortController.value.signal,
); );

View File

@ -203,6 +203,8 @@
</div> </div>
<textarea <textarea
class="prompt-textarea" class="prompt-textarea"
:class="{ disabled: settings.learningModeEnabled }"
:disabled="settings.learningModeEnabled"
:value="settings.defaultSystemPrompt" :value="settings.defaultSystemPrompt"
rows="4" rows="4"
placeholder="输入系统提示词..." placeholder="输入系统提示词..."
@ -214,6 +216,9 @@
}) })
" "
/> />
<p v-if="settings.learningModeEnabled" class="setting-desc">
当前提示词已由学习模式接管关闭学习模式后可恢复编辑
</p>
</div> </div>
</div> </div>
@ -849,6 +854,16 @@ function handleClearData() {
&::placeholder { &::placeholder {
color: #9ca3af; color: #9ca3af;
} }
&.disabled {
opacity: 0.7;
cursor: not-allowed;
background: #f3f4f6;
.dark & {
background: #252533;
}
}
} }
.data-actions { .data-actions {

View File

@ -3,6 +3,7 @@
* *
*/ */
import { getAuthHeaders } from './request'; import { getAuthHeaders } from './request';
import { useSettingsStore } from "@/stores/settings";
// API 端点定义(固定) // API 端点定义(固定)
const API_ENDPOINTS = { const API_ENDPOINTS = {
@ -24,6 +25,9 @@ const API_ENDPOINTS = {
STOP: "/api/chat-ui/stop", STOP: "/api/chat-ui/stop",
}; };
const DEFAULT_SYSTEM_PROMPT =
"你是一个智能助手,可以分析用户发送的文字,文件或图片内容,并进行回答。";
// 请求类型定义 // 请求类型定义
export interface ChatMessage { export interface ChatMessage {
role: "user" | "assistant" | "system"; role: "user" | "assistant" | "system";
@ -96,6 +100,24 @@ class ChatApi {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
private resolveSystemPrompt(explicit?: string): string {
if (explicit?.trim()) {
return explicit.trim();
}
try {
const settingsStore = useSettingsStore();
const fallbackPrompt = settingsStore.settings.defaultSystemPrompt;
if (fallbackPrompt?.trim()) {
return fallbackPrompt.trim();
}
} catch (error) {
console.warn("读取全局默认系统提示词失败,使用内置兜底提示词", error);
}
return DEFAULT_SYSTEM_PROMPT;
}
/** /**
* *
*/ */
@ -135,9 +157,7 @@ class ChatApi {
// 否则添加系统消息 // 否则添加系统消息
const systemMessage = { const systemMessage = {
role: "system", role: "system",
content: content: this.resolveSystemPrompt(request.systemPrompt),
request.systemPrompt ||
"你是一个智能助手,可以分析用户发送的文字,文件或图片内容,并进行回答。",
}; };
allMessages = [systemMessage, ...request.history, { role: "user", content: userContent }]; allMessages = [systemMessage, ...request.history, { role: "user", content: userContent }];
} }
@ -145,9 +165,7 @@ class ChatApi {
// 没有历史消息,添加系统消息 // 没有历史消息,添加系统消息
const systemMessage = { const systemMessage = {
role: "system", role: "system",
content: content: this.resolveSystemPrompt(request.systemPrompt),
request.systemPrompt ||
"你是一个智能助手,可以分析用户发送的文字,文件或图片内容,并进行回答。",
}; };
allMessages = [systemMessage, { role: "user", content: userContent }]; allMessages = [systemMessage, { role: "user", content: userContent }];
} }
@ -257,6 +275,7 @@ class ChatApi {
const requestBody = { const requestBody = {
...request, ...request,
message: userContent, message: userContent,
systemPrompt: this.resolveSystemPrompt(request.systemPrompt),
}; };
const response = await fetch(`${this.baseUrl}${API_ENDPOINTS.CHAT}`, { const response = await fetch(`${this.baseUrl}${API_ENDPOINTS.CHAT}`, {

View File

@ -1,6 +1,7 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
import type { AppSettings, AIModel } from "@/types/chat"; import type { AppSettings, AIModel } from "@/types/chat";
import promptData from "@/assets/prompt.json";
// 分享结果类型 // 分享结果类型
export interface ShareResult { export interface ShareResult {
@ -13,6 +14,10 @@ export interface ShareResult {
export const useSettingsStore = defineStore("settings", () => { export const useSettingsStore = defineStore("settings", () => {
const MIN_SIDEBAR_WIDTH = 310; const MIN_SIDEBAR_WIDTH = 310;
const MAX_SIDEBAR_WIDTH = 400; const MAX_SIDEBAR_WIDTH = 400;
const LEARNING_MODE_PROMPT_TITLE = "让可学 AI 成为我的全科学习导师?";
const LEARNING_MODE_SYSTEM_PROMPT =
promptData["分析与实践"]?.[LEARNING_MODE_PROMPT_TITLE] ||
"你是一位“学习模式”引导员,通过严格的苏格拉底式提问法,引导用户自己思考并逐步得出答案。";
// 默认设置 // 默认设置
const defaultSettings: AppSettings = { const defaultSettings: AppSettings = {
@ -31,6 +36,8 @@ export const useSettingsStore = defineStore("settings", () => {
defaultTemperature: 0.7, defaultTemperature: 0.7,
defaultMaxTokens: 4096, defaultMaxTokens: 4096,
defaultSystemPrompt: "你是一个有帮助的 AI 助手。", defaultSystemPrompt: "你是一个有帮助的 AI 助手。",
learningModeEnabled: false,
learningModePrevDefaultSystemPrompt: "",
// 功能设置 // 功能设置
enableSound: true, enableSound: true,
@ -223,9 +230,42 @@ export const useSettingsStore = defineStore("settings", () => {
shareResult.value = null; shareResult.value = null;
} }
function normalizeLearningModeState() {
if (!settings.value.learningModeEnabled) return;
const currentPrompt = settings.value.defaultSystemPrompt || "";
const isUsingLearningPrompt = currentPrompt === LEARNING_MODE_SYSTEM_PROMPT;
if (!isUsingLearningPrompt && !settings.value.learningModePrevDefaultSystemPrompt) {
settings.value.learningModePrevDefaultSystemPrompt = currentPrompt;
}
settings.value.defaultSystemPrompt = LEARNING_MODE_SYSTEM_PROMPT;
}
function setLearningModeEnabled(enabled: boolean) {
if (enabled) {
if (!settings.value.learningModePrevDefaultSystemPrompt) {
settings.value.learningModePrevDefaultSystemPrompt =
settings.value.defaultSystemPrompt || defaultSettings.defaultSystemPrompt;
}
settings.value.learningModeEnabled = true;
settings.value.defaultSystemPrompt = LEARNING_MODE_SYSTEM_PROMPT;
} else {
settings.value.learningModeEnabled = false;
if (settings.value.learningModePrevDefaultSystemPrompt) {
settings.value.defaultSystemPrompt =
settings.value.learningModePrevDefaultSystemPrompt;
}
settings.value.learningModePrevDefaultSystemPrompt = "";
}
saveToStorage();
}
// 更新设置 // 更新设置
function updateSettings(updates: Partial<AppSettings>) { function updateSettings(updates: Partial<AppSettings>) {
Object.assign(settings.value, updates); Object.assign(settings.value, updates);
normalizeLearningModeState();
if (updates.theme) { if (updates.theme) {
applyTheme(updates.theme); applyTheme(updates.theme);
@ -256,6 +296,7 @@ export const useSettingsStore = defineStore("settings", () => {
try { try {
const imported = JSON.parse(json); const imported = JSON.parse(json);
settings.value = { ...defaultSettings, ...imported }; settings.value = { ...defaultSettings, ...imported };
normalizeLearningModeState();
applyTheme(settings.value.theme); applyTheme(settings.value.theme);
applyFontSize(settings.value.fontSize); applyFontSize(settings.value.fontSize);
saveToStorage(); saveToStorage();
@ -304,6 +345,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (stored) { if (stored) {
settings.value = { ...defaultSettings, ...JSON.parse(stored) }; settings.value = { ...defaultSettings, ...JSON.parse(stored) };
} }
normalizeLearningModeState();
const collapsedStored = localStorage.getItem("chat-sidebar-collapsed"); const collapsedStored = localStorage.getItem("chat-sidebar-collapsed");
if (collapsedStored) { if (collapsedStored) {
@ -381,5 +423,6 @@ export const useSettingsStore = defineStore("settings", () => {
loadFromStorage, loadFromStorage,
getSelectedModelId, getSelectedModelId,
setSelectedModelId, setSelectedModelId,
setLearningModeEnabled,
}; };
}); });

View File

@ -126,6 +126,8 @@ export interface AppSettings {
defaultTemperature: number; defaultTemperature: number;
defaultMaxTokens: number; defaultMaxTokens: number;
defaultSystemPrompt: string; defaultSystemPrompt: string;
learningModeEnabled: boolean;
learningModePrevDefaultSystemPrompt: string;
// 功能设置 // 功能设置
enableSound: boolean; enableSound: boolean;