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>
</div>
</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>
</header>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { storeToRefs } from "pinia";
import {
Menu,
Trash2,
@ -97,6 +106,7 @@ import {
import SidebarExpandIcon from "@/components/icons/custom/SidebarExpandIcon.vue";
import SidebarCollapseIcon from "@/components/icons/custom/SidebarCollapseIcon.vue";
import { useSettingsStore } from "@/stores/settings.ts";
import FormSwitch from "@/components/ui/FormSwitch.vue";
const props = withDefaults(
defineProps<{
@ -129,6 +139,7 @@ const emit = defineEmits<{
const showMoreMenu = ref(false);
const settingsStore = useSettingsStore();
const { settings } = storeToRefs(settingsStore);
function handleClear() {
if (confirm("确定要清空当前对话吗?此操作不可恢复。")) {
@ -244,6 +255,24 @@ if (typeof window !== "undefined") {
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 {
display: flex;
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 || [];
@ -283,7 +286,7 @@ async function handleSend(
deepSearch: options?.deepSearch,
webSearch: options?.webSearch,
deepThinking: options?.deepThinking,
systemPrompt: options?.systemPrompt,
systemPrompt,
},
abortController.value.signal,
);
@ -404,6 +407,9 @@ async function handleRetry(messageId: string) {
currentStreamingMessageId.value = messageId;
chatStore.startStreaming();
abortController.value = new AbortController();
const systemPrompt =
currentConversation.value?.settings?.systemPrompt ||
settings.value.defaultSystemPrompt;
try {
const stream = chatApi.streamChat(
@ -413,6 +419,7 @@ async function handleRetry(messageId: string) {
model: settings.value.defaultModel,
stream: true,
history: priorMessages,
systemPrompt,
},
abortController.value.signal,
);

View File

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

View File

@ -3,6 +3,7 @@
*
*/
import { getAuthHeaders } from './request';
import { useSettingsStore } from "@/stores/settings";
// API 端点定义(固定)
const API_ENDPOINTS = {
@ -24,6 +25,9 @@ const API_ENDPOINTS = {
STOP: "/api/chat-ui/stop",
};
const DEFAULT_SYSTEM_PROMPT =
"你是一个智能助手,可以分析用户发送的文字,文件或图片内容,并进行回答。";
// 请求类型定义
export interface ChatMessage {
role: "user" | "assistant" | "system";
@ -96,6 +100,24 @@ class ChatApi {
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 = {
role: "system",
content:
request.systemPrompt ||
"你是一个智能助手,可以分析用户发送的文字,文件或图片内容,并进行回答。",
content: this.resolveSystemPrompt(request.systemPrompt),
};
allMessages = [systemMessage, ...request.history, { role: "user", content: userContent }];
}
@ -145,9 +165,7 @@ class ChatApi {
// 没有历史消息,添加系统消息
const systemMessage = {
role: "system",
content:
request.systemPrompt ||
"你是一个智能助手,可以分析用户发送的文字,文件或图片内容,并进行回答。",
content: this.resolveSystemPrompt(request.systemPrompt),
};
allMessages = [systemMessage, { role: "user", content: userContent }];
}
@ -257,6 +275,7 @@ class ChatApi {
const requestBody = {
...request,
message: userContent,
systemPrompt: this.resolveSystemPrompt(request.systemPrompt),
};
const response = await fetch(`${this.baseUrl}${API_ENDPOINTS.CHAT}`, {

View File

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

View File

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