before refractor

This commit is contained in:
2026-01-05 16:55:34 +08:00
parent 3bc804c5e8
commit bd7eca1bd0
14 changed files with 1062 additions and 396 deletions

View File

@@ -5,19 +5,19 @@ import { headers } from "next/headers";
import { redirect } from "next/navigation";
export interface SignUpFormData {
username: string;
email: string;
password: string;
username: string;
email: string;
password: string;
}
export interface SignUpState {
success?: boolean;
message?: string;
errors?: {
username?: string[];
email?: string[];
password?: string[];
};
success?: boolean;
message?: string;
errors?: {
username?: string[];
email?: string[];
password?: string[];
};
}
export async function signUpAction(prevState: SignUpState, formData: FormData) {
@@ -111,6 +111,9 @@ export async function signInAction(prevState: SignUpState, formData: FormData) {
redirect(redirectTo || "/");
} catch (error) {
if (error instanceof Error && error.message.includes('NEXT_REDIRECT')) {
throw error;
}
return {
success: false,
message: "登录失败,请检查您的邮箱和密码"

View File

@@ -1,15 +0,0 @@
import { ProsodyOptions, EdgeTTS } from "edge-tts-universal/browser";
export async function getTTSAudioUrl(
text: string,
short_name: string,
options: ProsodyOptions | undefined = undefined,
) {
const tts = new EdgeTTS(text, short_name, options);
try {
const result = await tts.synthesize();
return URL.createObjectURL(result.audio);
} catch (e) {
throw e;
}
}

View File

@@ -38,6 +38,47 @@ export const genLocale = async (text: string) => {
);
};
export const genLanguage = async (text: string) => {
const language = await getAnswer([
{
role: "system",
content: `
你是一个语言检测工具。请识别文本的语言并返回语言名称。
返回语言的标准英文名称,例如:
- 中文: Chinese
- 英语: English
- 日语: Japanese
- 韩语: Korean
- 法语: French
- 德语: German
- 意大利语: Italian
- 葡萄牙语: Portuguese
- 西班牙语: Spanish
- 俄语: Russian
- 阿拉伯语: Arabic
- 印地语: Hindi
- 泰语: Thai
- 越南语: Vietnamese
- 等等...
如果无法识别语言,返回 "Unknown"
规则:
1. 只返回语言的标准英文名称
2. 首字母大写,其余小写
3. 不要附带任何说明
4. 不要擅自增减符号
`.trim()
},
{
role: "user",
content: `<text>${text}</text>`
}
]);
return language.trim();
};
export const genTranslation = async (text: string, targetLanguage: string) => {
return await getAnswer(
`

View File

@@ -0,0 +1,248 @@
// ==================== 类型定义 ====================
/**
* 支持的语音合成模型
*/
type TTSModel = 'qwen3-tts-flash' | string; // 主要模型为 'qwen3-tts-flash'
/**
* API 支持的语言类型(必须严格按文档使用)
*/
type SupportedLanguage =
| 'Auto' // 自动检测(混合语言场景)
| 'Chinese' // 中文
| 'English' // 英文
| 'German' // 德文
| 'Italian' | 'Portuguese' | 'Spanish'
| 'Japanese' | 'Korean' | 'French'
| 'Russian';
/**
* API 请求参数接口
*/
interface TTSRequest {
model: TTSModel;
input: {
text: string; // 要合成的文本qwen3-tts-flash最长600字符
voice: string; // 音色名称,如 'Cherry'
language_type?: SupportedLanguage; // 可选,默认为 'Auto'
};
parameters?: {
stream?: boolean; // 是否流式输出需配合特定Header
};
}
/**
* API 响应接口(通用结构)
*/
interface TTSResponse {
status_code: number; // HTTP状态码200表示成功
request_id: string; // 请求唯一标识,用于排查问题
code: string; // 错误码,成功时为 ''
message: string; // 错误信息,成功时为 ''
output: {
audio: {
data: string; // Base64编码的音频数据流式输出时有效
url: string; // 音频文件下载URL非流式输出时有效
id: string; // 音频ID
expires_at: number; // URL过期时间戳
};
text: null; // 文档注明始终为null
choices: null; // 文档注明始终为null
finish_reason: string; // 生成状态
};
usage: {
characters: number; // 计费字符数qwen3-tts-flash
input_tokens?: number;
output_tokens?: number;
};
}
// ==================== TTS 服务类 ====================
class QwenTTSService {
private baseUrl: string;
private apiKey: string;
private region: 'cn-beijing' | 'intl-singapore'; // 地域
/**
* 构造函数
* @param apiKey - DashScope API Key从环境变量获取更安全
* @param region - 服务地域,默认北京
*/
constructor(
apiKey: string,
region: 'cn-beijing' | 'intl-singapore' = 'cn-beijing'
) {
this.apiKey = apiKey;
this.region = region;
// 根据地域设置API端点文档中特别强调
this.baseUrl = region === 'cn-beijing'
? 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation'
: 'https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation';
}
/**
* 验证文本长度qwen3-tts-flash模型限制600字符
*/
private validateTextLength(text: string, model: TTSModel): void {
const maxLength = model.includes('qwen3-tts-flash') ? 600 : 512;
if (text.length > maxLength) {
throw new Error(
`文本长度 ${text.length} 字符超过模型限制(最大 ${maxLength} 字符)`
);
}
}
/**
* 合成语音非流式输出返回音频URL
*/
async synthesize(
text: string,
options: {
voice?: string; // 音色,默认 'Cherry'
language?: SupportedLanguage; // 语种,默认 'Auto'
model?: TTSModel; // 模型,默认 'qwen3-tts-flash'
} = {}
): Promise<TTSResponse> {
const {
voice = 'Cherry',
language = 'Auto',
model = 'qwen3-tts-flash'
} = options;
// 1. 文本长度验证
this.validateTextLength(text, model);
// 2. 构建请求体
const requestBody: TTSRequest = {
model,
input: {
text,
voice,
language_type: language
}
// 非流式输出不需要 stream 参数
};
try {
// 3. 调用API
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
const data: TTSResponse = await response.json();
// 4. 错误处理
if (data.status_code !== 200) {
throw new Error(`API错误: [${data.code}] ${data.message}`);
}
return data;
} catch (error) {
console.error('语音合成请求失败:', error);
throw error;
}
}
/**
* 流式合成语音边生成边输出Base64音频数据
*/
async synthesizeStream(
text: string,
options: {
voice?: string;
language?: SupportedLanguage;
model?: TTSModel;
onAudioChunk?: (chunk: string) => void; // 接收音频片段的回调
} = {}
): Promise<void> {
const {
voice = 'Cherry',
language = 'Auto',
model = 'qwen3-tts-flash',
onAudioChunk
} = options;
this.validateTextLength(text, model);
const requestBody: TTSRequest = {
model,
input: {
text,
voice,
language_type: language
},
parameters: {
stream: true // 启用流式输出
}
};
try {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'X-DashScope-SSE': 'enable' // 关键:启用服务器发送事件
},
body: JSON.stringify(requestBody),
});
if (!response.ok || !response.body) {
throw new Error(`流式请求失败: ${response.status}`);
}
// 处理流式响应此处为简化示例实际需解析SSE格式
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
if (onAudioChunk && chunk.trim()) {
onAudioChunk(chunk); // 处理音频数据片段
}
}
} catch (error) {
console.error('流式合成失败:', error);
throw error;
}
}
}
export type TTS_SUPPORTED_LANGUAGES = 'Auto' | 'Chinese' | 'English' | 'German' | 'Italian' | 'Portuguese' | 'Spanish' | 'Japanese' | 'Korean' | 'French' | 'Russian';
export async function getTTSUrl(text: string, lang: TTS_SUPPORTED_LANGUAGES) {
try {
if (!process.env.DASHSCORE_API_KEY) {
console.warn(
`⚠️ 环境变量 DASHSCORE_API_KEY 未设置\n` +
` 请在 .env 文件中设置或直接传入API Key\n` +
` 获取API Key: https://help.aliyun.com/zh/model-studio/get-api-key`
);
throw "API Key设置错误";
}
const ttsService = new QwenTTSService(
process.env.DASHSCOPE_API_KEY || 'sk-xxx',
);
const result = await ttsService.synthesize(
text,
{
voice: 'Cherry',
language: lang
}
);
return result.output.audio.url;
} catch (error) {
console.error('TTS合成失败:', error instanceof Error ? error.message : error);
return "error";
}
}