chore: 提取配置项到统一配置中心

This commit is contained in:
cc 2026-03-19 21:04:00 +08:00
parent a799afebab
commit 18ac423352
7 changed files with 100 additions and 66 deletions

41
src/config/index.js Normal file
View File

@ -0,0 +1,41 @@
// ============================================================
// 全局配置中心
// 所有 API 地址、密钥、模型名称等配置统一在此管理
// ============================================================
// ── GRS AI作文批改 / 试题分析)──
export const GRS_API_KEY = "sk-6ba4b57a0b034d9388f7a7a2d477d637";
// 作文批改接口
export const ESSAY_API_URL = "https://grsai.dakka.com.cn/v1/draw/nano-banana";
export const ESSAY_MODEL = "nano-banana-pro";
export const ESSAY_PROMPT = "请对这篇英语作文进行批改,标注语法错误、拼写错误和表达不当之处,并给出修改建议,使用中文批注";
// 试题分析接口
export const EXAM_API_URL = "https://grsaiapi.com/v1/chat/completions";
export const EXAM_MODEL = "gemini-3.1-pro";
export const EXAM_TEMPERATURE = 0.7;
export const EXAM_MAX_TOKENS = 2048;
// ── 豆包 TTS发音 / 口语对话)──
export const DOUBAO_APP_ID = "2542859186";
export const DOUBAO_ACCESS_TOKEN = "a4h5fT3cVlBi82u93iEQlqT3c4MP6_8V";
export const DOUBAO_RESOURCE_ID = "seed-tts-2.0";
export const DOUBAO_TTS_API_PATH = "/tts-api/api/v3/tts/unidirectional";
export const DOUBAO_AUDIO_FORMAT = "mp3";
export const DOUBAO_SAMPLE_RATE = 24000;
// ── 火山引擎 Ark 大模型(口语对话)──
export const ARK_API_KEY = "";
export const ARK_MODEL = "doubao-pro-4k";
export const ARK_API_PATH = "/ark-api/api/v3/chat/completions";
export const ARK_MAX_TOKENS = 200;
export const ARK_HISTORY_LIMIT = 20; // 保留最近对话条数
// ── 外部链接 ──
export const LISTENING_AUDIO_URL = "https://jz5k88k7vv.coze.site"; // 听力音频生成
// ── 文件上传限制 ──
export const IMAGE_MAX_SIZE_MB = 10;
export const IMAGE_ALLOWED_TYPES_ESSAY = ["image/jpeg", "image/jpg", "image/png"];
export const IMAGE_ALLOWED_TYPES_EXAM = ["image/jpeg", "image/jpg", "image/png", "image/webp"];

View File

@ -2,11 +2,17 @@
import { ref } from "vue"; import { ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import axios from "axios"; import axios from "axios";
import {
GRS_API_KEY,
ESSAY_API_URL,
ESSAY_PROMPT,
IMAGE_MAX_SIZE_MB,
IMAGE_ALLOWED_TYPES_ESSAY,
} from "@/config/index.js";
const API_KEY = "sk-6ba4b57a0b034d9388f7a7a2d477d637"; const API_KEY = GRS_API_KEY;
const API_URL = "https://grsai.dakka.com.cn/v1/draw/nano-banana"; const API_URL = ESSAY_API_URL;
const PROMPT = const PROMPT = ESSAY_PROMPT;
"请对这篇英语作文进行批改,标注语法错误、拼写错误和表达不当之处,并给出修改建议,使用中文批注";
const router = useRouter(); const router = useRouter();
@ -36,13 +42,13 @@ const readFileAsBase64 = (file) =>
const handleFile = async (file) => { const handleFile = async (file) => {
if (!file) return; if (!file) return;
const allowed = ["image/jpeg", "image/jpg", "image/png"]; const allowed = IMAGE_ALLOWED_TYPES_ESSAY;
if (!allowed.includes(file.type)) { if (!allowed.includes(file.type)) {
errorMsg.value = "仅支持 JPG / PNG 格式的图片"; errorMsg.value = "仅支持 JPG / PNG 格式的图片";
return; return;
} }
if (file.size > 10 * 1024 * 1024) { if (file.size > IMAGE_MAX_SIZE_MB * 1024 * 1024) {
errorMsg.value = "图片大小不能超过 10MB"; errorMsg.value = `图片大小不能超过 ${IMAGE_MAX_SIZE_MB}MB`;
return; return;
} }
errorMsg.value = ""; errorMsg.value = "";
@ -85,7 +91,7 @@ const startCorrection = async () => {
imageSize: "1K", imageSize: "1K",
urls: [originalBase64.value], urls: [originalBase64.value],
webHook: "", webHook: "",
shutProgress: true, shutProgress: false,
}, },
{ {
headers: { headers: {
@ -107,7 +113,8 @@ const startCorrection = async () => {
// SSE "data: {...}\n\n" "data: " // SSE "data: {...}\n\n" "data: "
let data; let data;
try { try {
const raw = typeof res.data === "string" ? res.data : JSON.stringify(res.data); const raw =
typeof res.data === "string" ? res.data : JSON.stringify(res.data);
const jsonStr = raw.replace(/^data:\s*/m, "").trim(); const jsonStr = raw.replace(/^data:\s*/m, "").trim();
data = JSON.parse(jsonStr); data = JSON.parse(jsonStr);
} catch { } catch {

View File

@ -3,11 +3,12 @@ import { ref, computed, onUnmounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import axios from "axios"; import axios from "axios";
import { GRS_API_KEY, EXAM_API_URL, EXAM_MODEL, EXAM_TEMPERATURE, EXAM_MAX_TOKENS, IMAGE_MAX_SIZE_MB, IMAGE_ALLOWED_TYPES_EXAM } from "@/config/index.js";
const router = useRouter(); const router = useRouter();
// API Gemini API Key const API_KEY = GRS_API_KEY;
const API_KEY = "sk-6ba4b57a0b034d9388f7a7a2d477d637"; const API_URL = EXAM_API_URL;
const API_URL = "https://grsaiapi.com/v1/chat/completions";
const SYSTEM_PROMPT = `你是一位专业的英语教师,请对以下英语试题进行深度解析。 const SYSTEM_PROMPT = `你是一位专业的英语教师,请对以下英语试题进行深度解析。
请严格按照以下格式输出每个部分用对应标题开头 请严格按照以下格式输出每个部分用对应标题开头
@ -117,13 +118,13 @@ const readFileAsBase64 = (file) =>
const handleFile = async (file) => { const handleFile = async (file) => {
if (!file) return; if (!file) return;
const allowed = ["image/jpeg", "image/jpg", "image/png", "image/webp"]; const allowed = IMAGE_ALLOWED_TYPES_EXAM;
if (!allowed.includes(file.type)) { if (!allowed.includes(file.type)) {
errorMsg.value = "仅支持 JPG / PNG / WebP 格式的图片"; errorMsg.value = "仅支持 JPG / PNG / WebP 格式的图片";
return; return;
} }
if (file.size > 10 * 1024 * 1024) { if (file.size > IMAGE_MAX_SIZE_MB * 1024 * 1024) {
errorMsg.value = "图片大小不能超过 10MB"; errorMsg.value = `图片大小不能超过 ${IMAGE_MAX_SIZE_MB}MB`;
return; return;
} }
errorMsg.value = ""; errorMsg.value = "";
@ -158,13 +159,13 @@ const buildRequestBody = (imageBase64 = null, mimeType = "image/jpeg") => {
} }
return { return {
model: "gemini-3.1-pro", model: EXAM_MODEL,
messages: [ messages: [
{ role: "system", content: SYSTEM_PROMPT }, { role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: userContent }, { role: "user", content: userContent },
], ],
temperature: 0.7, temperature: EXAM_TEMPERATURE,
max_tokens: 2048, max_tokens: EXAM_MAX_TOKENS,
stream: true, stream: true,
}; };
}; };

View File

@ -1,6 +1,7 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { LISTENING_AUDIO_URL } from "@/config/index.js";
const router = useRouter(); const router = useRouter();
const features = ref([ const features = ref([
@ -10,7 +11,7 @@ const features = ref([
desc: "输入听力文本,一键调用 AI 合成标准英语考试音频,支持多种题型场景,快速生成可用于备考的模拟听力材料。", desc: "输入听力文本,一键调用 AI 合成标准英语考试音频,支持多种题型场景,快速生成可用于备考的模拟听力材料。",
class: "card-1", class: "card-1",
icon: "audio", icon: "audio",
url: "https://jz5k88k7vv.coze.site", url: LISTENING_AUDIO_URL,
}, },
{ {
id: 2, id: 2,

View File

@ -1,16 +1,7 @@
<script setup> <script setup>
import { ref, computed, onUnmounted } from "vue"; import { ref, computed, onUnmounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { DOUBAO_APP_ID, DOUBAO_ACCESS_TOKEN, DOUBAO_RESOURCE_ID, DOUBAO_TTS_API_PATH, DOUBAO_AUDIO_FORMAT, DOUBAO_SAMPLE_RATE } from "@/config/index.js";
// ==========================================
// (Volcengine) TTS API (V3 API)
//
// ==========================================
const DOUBAO_APP_ID = "2542859186";
const DOUBAO_ACCESS_TOKEN = "a4h5fT3cVlBi82u93iEQlqT3c4MP6_8V";
// V3 API Resource ID volc.service_type.10029 (1.0) seed-tts-2.0
const DOUBAO_RESOURCE_ID = "seed-tts-2.0";
// ==========================================
const router = useRouter(); const router = useRouter();
const textInput = ref(""); const textInput = ref("");
@ -417,11 +408,11 @@ const handleDemoPlay = async (id) => {
req_params: { req_params: {
text: voice.demo_text, text: voice.demo_text,
speaker: voice.voice_type, speaker: voice.voice_type,
audio_params: { format: "mp3", sample_rate: 24000 }, audio_params: { format: DOUBAO_AUDIO_FORMAT, sample_rate: DOUBAO_SAMPLE_RATE },
}, },
}; };
const response = await fetch("/tts-api/api/v3/tts/unidirectional", { const response = await fetch(DOUBAO_TTS_API_PATH, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -599,14 +590,14 @@ const handleGenerate = async () => {
text: textInput.value, text: textInput.value,
speaker: selectedVoiceData.voice_type, speaker: selectedVoiceData.voice_type,
audio_params: { audio_params: {
format: "mp3", format: DOUBAO_AUDIO_FORMAT,
sample_rate: 24000, sample_rate: DOUBAO_SAMPLE_RATE,
}, },
}, },
}; };
// ( https://openspeech.bytedance.com/api/v3/tts/unidirectional) // ( https://openspeech.bytedance.com/api/v3/tts/unidirectional)
const response = await fetch("/tts-api/api/v3/tts/unidirectional", { const response = await fetch(DOUBAO_TTS_API_PATH, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -1,20 +1,7 @@
<script setup> <script setup>
import { ref, computed, nextTick, onUnmounted } from "vue"; import { ref, computed, nextTick, onUnmounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { DOUBAO_APP_ID, DOUBAO_ACCESS_TOKEN, DOUBAO_RESOURCE_ID, DOUBAO_TTS_API_PATH, DOUBAO_AUDIO_FORMAT, DOUBAO_SAMPLE_RATE, ARK_API_KEY, ARK_MODEL, ARK_API_PATH, ARK_MAX_TOKENS, ARK_HISTORY_LIMIT } from "@/config/index.js";
// ==========================================
// TTS API Pronunciation.vue
// ==========================================
const DOUBAO_APP_ID = "2542859186";
const DOUBAO_ACCESS_TOKEN = "a4h5fT3cVlBi82u93iEQlqT3c4MP6_8V";
const DOUBAO_RESOURCE_ID = "seed-tts-2.0";
// ==========================================
// Ark API
// Ark API Key使
// ==========================================
const ARK_API_KEY = "";
const ARK_MODEL = "doubao-pro-4k";
const router = useRouter(); const router = useRouter();
@ -175,11 +162,11 @@ const synthesizeAndPlay = async (text, msgId) => {
req_params: { req_params: {
text, text,
speaker: selectedVoice.value, speaker: selectedVoice.value,
audio_params: { format: "mp3", sample_rate: 24000 }, audio_params: { format: DOUBAO_AUDIO_FORMAT, sample_rate: DOUBAO_SAMPLE_RATE },
}, },
}; };
const response = await fetch("/tts-api/api/v3/tts/unidirectional", { const response = await fetch(DOUBAO_TTS_API_PATH, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -285,21 +272,21 @@ const replayMessage = (msg) => {
// Ark // Ark
const callArkAPI = async (history) => { const callArkAPI = async (history) => {
const response = await fetch("/ark-api/api/v3/chat/completions", { const response = await fetch(ARK_API_PATH, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": `Bearer ${ARK_API_KEY}`, "Authorization": `Bearer ${ARK_API_KEY}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
model: ARK_MODEL, model: ARK_MODEL,
messages: [ messages: [
{ role: "system", content: currentScene.value.systemPrompt }, { role: "system", content: currentScene.value.systemPrompt },
...history, ...history,
], ],
max_tokens: 200, max_tokens: ARK_MAX_TOKENS,
}), }),
}); });
if (!response.ok) throw new Error(`Ark HTTP ${response.status}`); if (!response.ok) throw new Error(`Ark HTTP ${response.status}`);
const data = await response.json(); const data = await response.json();
return data.choices[0].message.content.trim(); return data.choices[0].message.content.trim();
@ -334,7 +321,7 @@ const sendMessage = async () => {
// 20 // 20
const history = messages.value const history = messages.value
.filter((m) => !m.isLoading && m.content) .filter((m) => !m.isLoading && m.content)
.slice(-20) .slice(-ARK_HISTORY_LIMIT)
.map((m) => ({ role: m.role, content: m.content })); .map((m) => ({ role: m.role, content: m.content }));
let replyText = ""; let replyText = "";

View File

@ -1,9 +1,15 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: { server: {
proxy: { proxy: {
'/tts-api': { '/tts-api': {