chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
import request from "./useRequest";
import { RequestResponse } from "../types/interfaces/request";
import type {
BindEmailParams,
CodeLogin,
ILoginResult,
LoginFieldType,
ResetPasswordParams,
SendCode,
SetPasswordParams,
TenantConfigInfo,
UserInfo,
UserUpdateParams,
WechatLoginParams,
IWechatLoginResult,
} from "@/types/interfaces/login";
// 租户配置信息查询接口
export function apiTenantConfig() {
return request<RequestResponse<TenantConfigInfo>>({
url: "/api/tenant/config",
method: "GET",
});
}
// 查询当前登录用户信息
export function apiUserInfo() {
return request<RequestResponse<UserInfo>>({
url: "/api/user/getLoginInfo",
method: "GET",
});
}
// 微信登录
export function apiWechatLogin(data: WechatLoginParams) {
return request<RequestResponse<IWechatLoginResult>>({
url: "/api/user/mp/codeLogin",
method: "POST",
data,
});
}
// 账号密码登录
export function apiLogin(data: LoginFieldType) {
return request<RequestResponse<ILoginResult>>({
url: "/api/user/passwordLogin",
method: "POST",
data,
});
}
// 发送验证码
export function apiSendCode(data: SendCode) {
return request<RequestResponse<null>>({
url: "/api/user/code/send",
method: "POST",
data,
});
}
// 验证码登录/注册接口
export function apiLoginCode(data: CodeLogin) {
return request<RequestResponse<ILoginResult>>({
url: "/api/user/codeLogin",
method: "POST",
data,
});
}
// 首次登录设置密码
export function apiSetPassword(data: SetPasswordParams) {
return request<RequestResponse<null>>({
url: "/api/user/password/set",
method: "POST",
data,
});
}
// 退出登录接口
export function apiLogout() {
return request<RequestResponse<null>>({
url: "/api/user/logout",
method: "GET",
});
}
// 获取动态认证码
export function apiUserDynamicCode() {
return request<RequestResponse<string>>({
url: "/api/user/dynamicCode",
method: "GET",
});
}

View File

@@ -0,0 +1,191 @@
import request from "./useRequest";
import { RequestResponse } from "@/types/interfaces/request";
import {
HomeAgentCategoryInfo,
StaticFileListResponse,
} from "@/types/interfaces/agentConfig";
import type { ListParams } from "@/types/interfaces/common";
import type {
AgentAkDeleteParams,
ApiAgentConversationChatPageResultParams,
AgentDetailDto,
AgentInfo,
ShareFileInfo,
AgentConversationShareParams,
AgentModelOption,
} from "@/types/interfaces/agent";
import { ACCESS_TOKEN } from "@/constants/home.constants";
import { API_BASE_URL } from "@/constants/config";
import { COMMON_HEADERS } from "@/constants/common.constants";
import { t } from "@/utils/i18n";
// 主页智能体列表接口 - 数据列表查询
export async function apiHomeCategoryList() {
return request<RequestResponse<HomeAgentCategoryInfo>>({
url: "/api/home/list",
method: "GET",
});
}
// 已发布的智能体详情接口
export function apiPublishedAgentInfo(
agentId: number,
withConversationId: boolean = false,
) {
return request<RequestResponse<AgentDetailDto>>({
url: `/api/published/agent/${agentId}`,
method: "GET",
data: {
withConversationId,
},
});
}
// 查询用户最近使用过的智能体列表
export function apiUserUsedAgentList(params: ListParams) {
const { size, pageIndex, keyword } = params;
// 对关键词进行编码
const _keyword = keyword ? `&kw=${encodeURIComponent(keyword)}` : "";
return request<RequestResponse<AgentInfo[]>>({
url: `/api/user/agent/used/list/${size}?pageIndex=${pageIndex}${_keyword}`,
method: "GET",
});
}
// 删除用户最近智能体使用记录
export function apiUserUsedAgentDelete(agentId: number) {
return request<RequestResponse<null>>({
url: `/api/user/agent/used/delete/${agentId}`,
method: "POST",
});
}
// 智能体收藏
export function apiCollectAgent(agentId: number) {
return request<RequestResponse<null>>({
url: `/api/user/agent/collect/${agentId}`,
method: "POST",
});
}
// 智能体取消收藏
export function apiUnCollectAgent(agentId: number) {
return request<RequestResponse<null>>({
url: `/api/user/agent/unCollect/${agentId}`,
method: "POST",
});
}
// 页面请求结果回写
export function apiAgentComponentPageResultUpdate(
data: ApiAgentConversationChatPageResultParams,
) {
return request<RequestResponse<null>>({
url: "/api/agent/conversation/chat/page/result",
method: "POST",
data,
});
}
// 创建用户临时票据
export function apiUserTicketCreate() {
const accessToken = uni.getStorageSync(ACCESS_TOKEN) || "";
return request<RequestResponse<string>>({
url: "/api/user/ticket/create",
method: "POST",
data: {
token: accessToken,
},
});
}
// 查询文件列表
export async function apiGetStaticFileList(cId: number) {
return request<RequestResponse<StaticFileListResponse>>({
url: "/api/computer/static/file-list",
method: "GET",
data: {
cId,
},
});
}
// 分享文件
export function apiAgentConversationShare(data: AgentConversationShareParams) {
return request<RequestResponse<ShareFileInfo>>({
url: "/api/agent/conversation/share",
method: "POST",
data,
});
}
// 下载全部文件
export async function apiDownloadAllFiles(cId: number): Promise<{
data: Blob | ArrayBuffer;
headers: {
"content-disposition"?: string;
"content-length"?: string;
"content-type"?: string;
};
}> {
// #ifdef H5 || WEB
// H5 环境:直接使用 uni.request 获取完整响应
const accessToken = uni.getStorageSync(ACCESS_TOKEN);
const header: any = {
// ...COMMON_HEADERS,
};
if (accessToken) {
header["Authorization"] = `Bearer ${accessToken}`;
}
return new Promise((resolve, reject) => {
uni.request({
url: API_BASE_URL + `/api/computer/static/download-all-files`,
method: "GET",
header,
responseType: "arraybuffer", // H5 使用 arraybuffer
data: {
cId,
},
success: (res) => {
if (res.statusCode === 200) {
// 将 ArrayBuffer 转换为 Blob
const blob = new Blob([res.data as ArrayBuffer], {
type: "application/zip",
});
resolve({
data: blob,
headers: res.header || {},
});
} else {
reject(
new Error(
t("Mobile.AgentDev.downloadFailedWithCode", {
code: String(res.statusCode),
}),
),
);
}
},
fail: (err) => {
reject(err);
},
});
});
// #endif
// #ifndef H5 || WEB
// 小程序环境:返回下载 URL实际下载由调用方处理
return Promise.resolve({
data: null as any,
headers: {},
});
// #endif
}
// 智能体会话可选模型列表
export function apiGetAgentModelOptions(agentId: number) {
return request<RequestResponse<AgentModelOption[]>>({
url: `/api/agent/conversation/model/options/${agentId}`,
method: "GET",
});
}

View File

@@ -0,0 +1,556 @@
/**
* 音频文件上传器
* 处理音频文件上传和语音转文字功能
*/
import { ACCESS_TOKEN } from "@/constants/home.constants";
import { REDIRECT_LOGIN, SUCCESS_CODE } from "@/constants/codes.constants";
import { API_BASE_URL } from "../constants/config";
import { t } from "@/utils/i18n";
// 音频文件接口
export interface AudioFile {
tempFilePath: string;
duration: number;
fileSize: number;
format?: string;
}
// 转录结果接口
export interface TranscriptResult {
success: boolean;
text: string;
confidence: number;
duration: number;
error?: string;
segments?: Array<{
text: string;
start: number;
end: number;
confidence: number;
}>;
}
// 上传配置接口
export interface UploadConfig {
apiUrl?: string; // API地址
timeout?: number; // 超时时间(毫秒)
language?: string; // 语言类型
enableRetry?: boolean; // 是否启用重试
maxRetries?: number; // 最大重试次数
retryDelay?: number; // 重试延迟(毫秒)
}
// 上传进度回调接口
export interface UploadProgress {
loaded: number; // 已上传字节数
total: number; // 总字节数
percent: number; // 上传进度百分比
}
/**
* 音频上传器类
*/
export class AudioUploader {
private config: UploadConfig = {
apiUrl: API_BASE_URL + "/api/audio/stt",
// apiUrl: 'https://stt-api.gd.ddnsto.com' + '/transcribe',
timeout: 30000,
language: "zh-CN",
enableRetry: true,
maxRetries: 1,
retryDelay: 1000,
};
constructor(config: Partial<UploadConfig> = {}) {
this.config = { ...this.config, ...config };
}
/**
* 上传音频文件并转换为文字
*/
async uploadAudio(
audioFile: AudioFile,
onProgress?: (progress: UploadProgress) => void,
): Promise<TranscriptResult> {
if (!audioFile.tempFilePath) {
throw new Error(t("Mobile.AudioUploader.pathRequired"));
}
if (audioFile.fileSize === 0) {
throw new Error(t("Mobile.AudioUploader.fileEmpty"));
}
// 检查文件大小限制10MB
const maxFileSize = 10 * 1024 * 1024;
if (audioFile.fileSize > maxFileSize) {
throw new Error(t("Mobile.AudioUploader.fileTooLarge"));
}
// 检查录音时长最短0.5秒最长10分钟
// if (audioFile.duration < 0.5) {
// throw new Error('录音时长过短至少需要0.5秒')
// }
/**
* 此处的duration是录音停止时的duration而不是录音开始时的duration, 单位为秒
*/
if (audioFile.duration > 600) {
throw new Error(t("Mobile.AudioUploader.durationTooLong"));
}
// 开始上传
return this.performUpload(audioFile, onProgress);
}
/**
* 执行上传操作
*/
private async performUpload(
audioFile: AudioFile,
onProgress?: (progress: UploadProgress) => void,
retryCount: number = 0,
): Promise<TranscriptResult> {
try {
// 模拟上传进度
// if (onProgress) {
// this.simulateProgress(onProgress)
// }
// 暂时使用模拟实现后续可替换为真实API调用
// if (this.config.apiUrl === '/api/voice-to-text') {
// return await this.mockUpload(audioFile)
// }
// 真实的上传实现
return await this.realUpload(audioFile, onProgress);
} catch (error: any) {
// console.error(`上传失败 (尝试 ${retryCount + 1}/${this.config.maxRetries + 1}):`, error)
// // 如果启用重试且未达到最大重试次数
// if (
// this.config.enableRetry &&
// retryCount < this.config.maxRetries! &&
// this.shouldRetry(error)
// ) {
// // 等待一段时间后重试
// await this.delay(this.config.retryDelay! * Math.pow(2, retryCount))
// return this.performUpload(audioFile, onProgress, retryCount + 1)
// }
// 不重试或重试次数已达上限
throw this.formatError(error);
}
}
/**
* 真实的上传实现
*/
private async realUpload(
audioFile: AudioFile,
onProgress?: (progress: UploadProgress) => void,
): Promise<TranscriptResult> {
return new Promise((resolve, reject) => {
const accessToken = uni.getStorageSync(ACCESS_TOKEN);
const header = {};
if (accessToken) {
// #ifdef H5 || WEB
if (process.env.NODE_ENV === "development") {
header["Authorization"] = `Bearer ${accessToken}`;
}
// #endif
// #ifdef MP-WEIXIN
header["Authorization"] = `Bearer ${accessToken}`;
// #endif
}
// uEnvProd
if (process.env.NODE_ENV === "production") {
// #ifdef H5
//仅H5需要添加fragment参数
const fragment = (window.location.hash || "#/").replace("#/", "/");
if (fragment) {
header["fragment"] = fragment;
}
// #endif
}
const uploadTask = uni.uploadFile({
header,
url: this.config.apiUrl,
filePath: audioFile.tempFilePath,
name: "file",
// formData: {
// format: audioFile.format || 'mp3',
// duration: audioFile.duration.toString(),
// language: this.config.language || 'zh-CN',
// fileSize: audioFile.fileSize.toString()
// },
timeout: this.config.timeout,
success: (res) => {
try {
const { code, data } = JSON.parse(res.data);
if (code === SUCCESS_CODE) {
if (data?.text) {
resolve({
success: true,
text: data.text,
});
} else {
reject(
new Error(t("Mobile.AudioUploader.noTextRecognized")),
);
}
} else {
reject(
new Error(
t("Mobile.AudioUploader.requestFailedWithCode", {
code: String(res.statusCode),
}),
),
);
}
} catch (error) {
reject(
new Error(t("Mobile.AudioUploader.responseParseFailed")),
);
}
},
fail: (err) => {
reject(
new Error(
err.errMsg || t("Mobile.AudioUploader.networkFailed"),
),
);
},
});
// 监听上传进度
// if (onProgress) {
// uploadTask.onProgressUpdate((res) => {
// onProgress({
// loaded: res.totalBytesSent,
// total: res.totalBytesExpectedToSend,
// percent: res.progress
// })
// })
// }
});
}
/**
* 模拟上传实现(用于测试)
*/
// private async mockUpload(audioFile: AudioFile): Promise<TranscriptResult> {
// // 模拟网络延迟
// await this.delay(1500 + Math.random() * 1000)
// // 模拟转换结果
// const mockTexts = [
// "这是一段测试语音转文本的内容",
// "语音识别功能正在正常工作",
// "请检查录音质量是否清晰",
// "您好,这是语音转文本测试",
// "录音功能已经成功集成",
// "人工智能语音识别技术日趋成熟",
// "语音交互将成为未来的主要方式",
// "欢迎使用语音录制功能",
// "系统正在处理您的语音信息",
// "语音转文字功能测试成功"
// ]
// // 根据录音时长选择不同长度的文本
// let selectedText: string
// if (audioFile.duration < 2) {
// selectedText = mockTexts[Math.floor(Math.random() * 3)]
// } else if (audioFile.duration < 5) {
// selectedText = mockTexts[Math.floor(Math.random() * 6) + 3]
// } else {
// selectedText = mockTexts[Math.floor(Math.random() * mockTexts.length)]
// }
// // 模拟成功率95% 成功率)
// const isSuccess = Math.random() > 0.05
// if (isSuccess) {
// return {
// success: true,
// text: selectedText,
// confidence: 0.80 + Math.random() * 0.20,
// duration: audioFile.duration,
// segments: this.generateMockSegments(selectedText, audioFile.duration)
// }
// } else {
// // 模拟不同类型的错误
// const errorTypes = [
// '网络连接异常,请检查网络设置',
// '语音内容不清晰,请重新录制',
// '服务器繁忙,请稍后重试',
// '音频格式不支持,请使用标准格式'
// ]
// const errorMessage = errorTypes[Math.floor(Math.random() * errorTypes.length)]
// return {
// success: false,
// text: '',
// confidence: 0,
// duration: audioFile.duration,
// error: errorMessage
// }
// }
// }
/**
* 生成模拟的分段结果
*/
// private generateMockSegments(text: string, duration: number): Array<{
// text: string
// start: number
// end: number
// confidence: number
// }> {
// const words = text.split('')
// const segments = []
// const wordsPerSegment = Math.ceil(words.length / Math.max(1, Math.floor(duration / 2)))
// let startTime = 0
// for (let i = 0; i < words.length; i += wordsPerSegment) {
// const segmentWords = words.slice(i, i + wordsPerSegment)
// const segmentDuration = duration * segmentWords.length / words.length
// segments.push({
// text: segmentWords.join(''),
// start: startTime,
// end: startTime + segmentDuration,
// confidence: 0.75 + Math.random() * 0.25
// })
// startTime += segmentDuration
// }
// return segments
// }
/**
* 模拟上传进度
*/
// private simulateProgress(onProgress: (progress: UploadProgress) => void): void {
// let progress = 0
// const interval = setInterval(() => {
// progress += Math.random() * 20
// if (progress >= 100) {
// progress = 100
// clearInterval(interval)
// }
// const fakeTotal = 1024 * 1024 // 1MB
// const fakeLoaded = Math.floor(fakeTotal * progress / 100)
// onProgress({
// loaded: fakeLoaded,
// total: fakeTotal,
// percent: Math.floor(progress)
// })
// }, 100)
// }
/**
* 判断是否应该重试
*/
// private shouldRetry(error: any): boolean {
// // 网络错误可以重试
// if (error.message?.includes('网络') || error.message?.includes('timeout')) {
// return true
// }
// // 服务器错误可以重试
// if (error.message?.includes('服务器') || error.message?.includes('繁忙')) {
// return true
// }
// // 其他错误不重试
// return false
// }
/**
* 格式化错误信息
*/
private formatError(error: any): Error {
let message = error.message || t("Mobile.AudioUploader.uploadFailed");
// 根据错误类型返回用户友好的提示
if (message.includes("timeout")) {
message = t("Mobile.AudioUploader.networkTimeout");
} else if (message.includes("network")) {
message = t("Mobile.AudioUploader.networkException");
} else if (message.includes("500")) {
message = t("Mobile.AudioUploader.serverError");
} else if (message.includes("413")) {
message = t("Mobile.AudioUploader.fileTooLargeDuration");
} else if (message.includes("415")) {
message = t("Mobile.AudioUploader.formatNotSupported");
}
return new Error(message);
}
/**
* 延迟函数
*/
// private delay(ms: number): Promise<void> {
// return new Promise(resolve => setTimeout(resolve, ms))
// }
/**
* 更新配置
*/
updateConfig(config: Partial<UploadConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* 获取当前配置
*/
getConfig(): UploadConfig {
return { ...this.config };
}
}
/**
* 创建音频上传器实例
*/
export function createAudioUploader(
config?: Partial<UploadConfig>,
): AudioUploader {
return new AudioUploader(config);
}
/**
* 内存管理器
* 用于清理临时音频文件和管理内存使用
*/
export class MemoryManager {
private static audioFiles: Set<string> = new Set();
private static readonly MAX_FILES = 3;
/**
* 添加音频文件到管理列表
*/
static addAudioFile(filePath: string): void {
this.audioFiles.add(filePath);
// 如果文件数量超过限制,清理最老的文件
if (this.audioFiles.size > this.MAX_FILES) {
const oldestFile = this.audioFiles.values().next().value;
this.cleanupAudioFile(oldestFile);
}
}
/**
* 清理音频文件
*/
static cleanupAudioFile(filePath: string): void {
if (!filePath) return;
try {
// 从管理列表中移除
this.audioFiles.delete(filePath);
// 如果是临时文件URL释放内存仅H5平台
// #ifdef H5
if (filePath.startsWith("blob:")) {
URL.revokeObjectURL(filePath);
}
// #endif
// 尝试删除临时文件
uni.removeSavedFile({
filePath: filePath,
success: () => {
console.log("清理音频文件成功:", filePath);
},
fail: (error) => {
console.warn("清理音频文件失败:", error);
},
});
} catch (error) {
console.warn("清理音频文件时发生错误:", error);
}
}
/**
* 清理所有音频文件
*/
static cleanupAllAudioFiles(): void {
for (const filePath of this.audioFiles) {
this.cleanupAudioFile(filePath);
}
this.audioFiles.clear();
}
/**
* 获取当前文件数量
*/
static getFileCount(): number {
return this.audioFiles.size;
}
}
/**
* 工具函数将音频文件转换为Base64
*/
export function audioFileToBase64(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const fileManager = uni.getFileSystemManager();
fileManager.readFile({
filePath: filePath,
encoding: "base64",
success: (res) => {
resolve(res.data as string);
},
fail: (error) => {
reject(
new Error(
t("Mobile.AudioUploader.fileReadFailed", {
reason: error.errMsg || "",
}),
),
);
},
});
});
}
/**
* 工具函数:检查音频文件格式
*/
export function validateAudioFormat(format: string): boolean {
const supportedFormats = ["mp3", "wav", "aac", "PCM", "webm"];
return supportedFormats.includes(format.toLowerCase());
}
/**
* 工具函数:格式化文件大小
*/
export function formatFileSize(bytes: number): string {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
/**
* 工具函数:格式化时长
*/
export function formatDuration(seconds: number): string {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
if (minutes > 0) {
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
} else {
return `${remainingSeconds}s`;
}
}

View File

@@ -0,0 +1,134 @@
import { RequestResponse } from "@/types/interfaces/request";
import {
ConversationChatSuggestParams,
ConversationCreateParams,
ConversationInfo,
ConversationListParams,
MessageInfo,
ConversationChatResponse,
} from "@/types/interfaces/conversationInfo";
import { AgentConversationUpdateParams } from "@/types/interfaces/agent";
import {
TempConversationQueryDto,
TempConversationCreateDto,
TempChatCompletionsParams,
} from "@/types/interfaces/tempChat";
import request from "@/servers/useRequest";
// 查询会话
export async function apiAgentConversation(conversationId: number) {
return request<RequestResponse<ConversationInfo>>({
url: `/api/agent/conversation/${conversationId}`,
method: "POST",
});
}
// 停止会话
export async function apiAgentConversationChatStop(conversationId: string) {
return request<RequestResponse<null>>({
url: `/api/agent/conversation/chat/stop/${conversationId}`,
method: "POST",
});
}
// 根据用户消息更新会话主题
export async function apiAgentConversationUpdate(
data: AgentConversationUpdateParams,
) {
return request<RequestResponse<ConversationInfo>>({
url: "/api/agent/conversation/update",
method: "POST",
data,
});
}
// 查询用户历史会话
export async function apiAgentConversationList(data: ConversationListParams) {
return request<RequestResponse<ConversationInfo[]>>({
url: "/api/agent/conversation/list",
method: "POST",
data,
});
}
// 删除会话
export async function apiAgentConversationDelete(conversationId: number) {
return request<RequestResponse<null>>({
url: `/api/agent/conversation/delete/${conversationId}`,
method: "POST",
});
}
// 创建会话
export async function apiAgentConversationCreate(
data: ConversationCreateParams,
) {
return request<RequestResponse<ConversationInfo>>({
url: "/api/agent/conversation/create",
method: "POST",
data,
});
}
// 智能体会话问题建议
export async function apiAgentConversationChatSuggest(
data: ConversationChatSuggestParams,
) {
return request<RequestResponse<string[]>>({
url: "/api/agent/conversation/chat/suggest",
method: "POST",
data,
});
}
// 查询临时会话详细
export async function apiTempChatConversationQuery(
data: TempConversationQueryDto,
) {
return request<RequestResponse<ConversationInfo>>({
url: "/api/temp/chat/conversation/query",
method: "POST",
data,
});
}
// 创建临时会话
export async function apiTempChatConversationCreate(
data: TempConversationCreateDto,
) {
return request<RequestResponse<ConversationInfo>>({
url: "/api/temp/chat/conversation/create",
method: "POST",
data,
});
}
// 临时消息会话
export async function apiTempChatCompletions(data: TempChatCompletionsParams) {
return request<RequestResponse<ConversationChatResponse>>({
url: "/api/temp/chat/completions",
method: "POST",
data,
});
}
// 停止临时会话
export async function apiTempChatConversationStop(requestId: string) {
return request<RequestResponse<null>>({
url: `/api/temp/chat/conversation/${requestId}`,
method: "POST",
});
}
// 查询会话消息列表(加载更多历史消息)
export async function apiAgentConversationMessageList(data: {
conversationId: number;
index: number;
size: number;
}) {
return request<RequestResponse<MessageInfo[]>>({
url: "/api/agent/conversation/message/list",
method: "POST",
data,
});
}

View File

@@ -0,0 +1,45 @@
/**
* 事件轮询 API 服务
* 用于 batch 轮询获取服务端推送事件
*
* 与 PC 端 @/services/event.ts 保持一致
*/
import request from './useRequest';
import { EventTypeEnum } from '@/types/enums/event';
/**
* 事件列表项
*/
interface EventListItem {
event: any;
type: EventTypeEnum;
}
/**
* 事件收集响应
*/
export interface ApiCollectEventResponse {
hasEvent: boolean;
eventList: EventListItem[];
version?: string; // 版本号
}
/**
* 轮询获取事件
*/
export async function apiCollectEvent(): Promise<ApiCollectEventResponse> {
return request<ApiCollectEventResponse>({
url: '/api/notify/event/collect/batch',
method: 'GET',
});
}
/**
* 清除事件
*/
export async function apiClearEvent(): Promise<any> {
return request({
url: '/api/notify/event/clear',
method: 'GET',
});
}

View File

@@ -0,0 +1,35 @@
import request from "./useRequest";
import type { RequestResponse } from "@/types/interfaces/request";
import type { I18nLangDto } from "@/types/interfaces/i18n";
import { I18N_SET_LANG_API } from "@/constants/i18n.constants";
export type LangMap = Record<string, string>;
// 查询语言列表
export function apiI18nLangList() {
return request<RequestResponse<I18nLangDto[]>>({
url: "/api/i18n/lang/list",
method: "GET",
});
}
// 查询语言字典
export function apiI18nQuery(lang: string = "") {
const side = "Mobile";
const query = lang
? `?lang=${encodeURIComponent(lang)}&side=${side}`
: `?side=${side}`;
return request<RequestResponse<LangMap>>({
url: `/api/i18n/query${query}`,
method: "GET",
});
}
// 设置用户语言(后端专用接口)
export function apiI18nSetLang(lang: string) {
return request<RequestResponse<any>>({
url: I18N_SET_LANG_API,
method: "POST",
data: { lang },
});
}

View File

@@ -0,0 +1,33 @@
import { Page, RequestResponse } from '../types/interfaces/request';
import { SquarePublishedListParams, SquarePublishedItemInfo, SquareCategoryInfo } from '../types/interfaces/square';
import request from './useRequest';
import { AgentDetailDto } from '../types/interfaces/agent';
// 广场-已发布智能体列表接口
export async function apiPublishedAgentList(
data: SquarePublishedListParams,
) {
return request<RequestResponse<Page<SquarePublishedItemInfo>>>({
url: '/api/published/agent/list',
method: 'POST',
data,
});
}
// 获取广场分类列表
export async function apiPublishedCategoryList() {
return request<RequestResponse<SquareCategoryInfo[]>>({
url: '/api/published/category/list',
method: 'GET',
});
}
// 获取已发布的智能体详情
export async function apiSquareAgentDetail(
agentId: number,
) {
return request<RequestResponse<AgentDetailDto>>({
url: `/api/published/agent/${agentId}`,
method: 'GET',
});
}

View File

@@ -0,0 +1,244 @@
import {
CONVERSATION_CONNECTION_URL,
COMMON_HEADERS,
} from "@/constants/common.constants";
import { ACCESS_TOKEN, EXPIRE_DATE } from "../constants/home.constants";
import {
DEFAULT_LANG,
I18N_LANG_STORAGE_KEY,
} from "@/constants/i18n.constants";
import { API_BASE_URL, LOGIN_WHITELIST_URL } from "../constants/config";
import {
REDIRECT_LOGIN,
SUCCESS_CODE,
USER_NO_LOGIN,
} from "@/constants/codes.constants";
import {
fetchEventSource,
EventSourceMessage,
} from "@microsoft/fetch-event-source";
import { handleExternalLink } from "@/utils/system.uts";
import { getCurrentPageFullPath } from "@/utils/common";
// 微信小程序request
export default function request<T = any>(options: any) {
const { url, method, ...rest } = options;
const accessToken = uni.getStorageSync(ACCESS_TOKEN);
let currentLang = uni.getStorageSync(I18N_LANG_STORAGE_KEY) || DEFAULT_LANG;
currentLang = String(currentLang || DEFAULT_LANG).toLowerCase();
const header = {
...COMMON_HEADERS,
"Accept-Language": currentLang,
};
if (accessToken) {
// #ifdef H5 || WEB
if (process.env.NODE_ENV === "development") {
header["Authorization"] = `Bearer ${accessToken}`;
}
// #endif
// #ifdef MP-WEIXIN
header["Authorization"] = `Bearer ${accessToken}`;
// #endif
}
// uEnvProd
if (process.env.NODE_ENV === "production") {
// #ifdef H5
//仅H5需要添加fragment参数
const fragment = (window.location.hash || "#/").replace("#/", "/");
if (fragment) {
header["fragment"] = fragment;
}
// #endif
}
// uni.request 在 UTS 里返回 RequestTask非 Promise这里手动包装为 Promise
return new Promise<T>((resolve, reject) => {
uni.request<T>({
header,
url: API_BASE_URL + url,
method: method || "POST",
...rest,
success: (res: any) => {
const responseData = res.data as any;
const { code, message: errorMessage } = responseData || {};
// batch 接口不做错误消息与跳转相关逻辑
const isBatchUrl = url?.includes("/api/notify/event/collect/batch");
if (isBatchUrl) {
resolve(responseData as T);
return;
}
// 用户未登录,跳转到登录页
if (code === USER_NO_LOGIN) {
uni.setStorageSync(ACCESS_TOKEN, ""); // 清空token【兼容鸿蒙系统】
uni.clearStorageSync(); // 清空所有缓存
// #ifdef H5 || WEB
uni.reLaunch({
url: "/subpackages/pages/login/login",
});
// #endif
// #ifdef MP-WEIXIN
uni.reLaunch({
url: "/subpackages/pages/login-weixin/login-weixin",
});
// #endif
}
// 重定向到登录页
if (code === REDIRECT_LOGIN) {
uni.setStorageSync(ACCESS_TOKEN, ""); // 清空token【兼容鸿蒙系统】
uni.clearStorageSync(); // 清空所有缓存
let redirectUrl = errorMessage;
// 如果错误信息包含/login开头则替换为登录页否则直接打开完整链接
if (errorMessage?.startsWith("/login")) {
// #ifdef H5 || WEB
// 如果url在登录白名单地址中则不跳转到登录页
if (LOGIN_WHITELIST_URL.includes(url)) {
resolve(responseData as T);
return;
}
redirectUrl = errorMessage?.replace(
"/login",
"/subpackages/pages/login/login",
);
uni.reLaunch({ url: redirectUrl });
// #endif
// #ifdef MP-WEIXIN
// 微信小程序:获取当前页面的 url
const currentUrl = getCurrentPageFullPath();
if (currentUrl) {
if (globalThis.appConfig.redirectUrl) {
const otherQueryStr = globalThis.appConfig.redirectUrl;
uni.reLaunch({
url:
"/subpackages/pages/login-weixin/login-weixin?redirect=" +
encodeURIComponent(
"/subpackages/pages/webview/webview?url=" + otherQueryStr,
),
});
} else {
uni.reLaunch({
url:
"/subpackages/pages/login-weixin/login-weixin?redirect=" +
encodeURIComponent("/" + currentUrl),
});
}
}
// #endif
} else {
// #ifdef H5 || WEB
window.location.href = errorMessage;
// #endif
// #ifdef MP
handleExternalLink(errorMessage);
// #endif
}
}
resolve(responseData as T);
},
fail: (error: any) => {
console.error("Request error:", error);
// 网络错误或其他错误,也返回错误信息
reject(error);
},
});
});
}
export interface SSEOptions<T = any> {
url: string;
method?: "GET" | "POST" | "PUT" | "DELETE";
headers?: { [key: string]: string };
body?: any;
onMessage: (data: T) => void;
onError?: (error: Error) => void;
onOpen?: (response: any) => void;
onClose?: () => void;
abortController?: any;
}
// H5 创建SSE连接
export async function createSSEConnection<T = any>(options: SSEOptions<T>) {
const controller = options.abortController || new AbortController();
const currentLang = String(
uni.getStorageSync(I18N_LANG_STORAGE_KEY) || DEFAULT_LANG,
).toLowerCase();
//仅H5需要添加fragment参数
const fragment = (window.location.hash || "#/").replace("#/", "/");
try {
// 通过 Parameters<typeof fetchEventSource>[1] 对齐第三方库参数类型
const fetchOptions = {
method: options.method || "POST",
headers: {
"Content-Type": "application/json",
"Accept-Language": currentLang,
...options.headers,
...(fragment && { fragment }),
},
body:
typeof options.body === "object"
? JSON.stringify(options.body)
: options.body,
signal: controller.signal,
openWhenHidden: true, // 页面不可见时保持连接
onopen: async (response) => {
if (response.status >= 400) {
throw new Error(
String(response.statusText || response.status || 0),
);
}
options.onOpen?.(response);
},
onmessage: (event: EventSourceMessage) => {
try {
const data = event.data ? (JSON.parse(event.data) as T) : null;
// 无消息体时不回调,避免把 null 传给强类型的 T
if (data !== null) {
options.onMessage(data);
}
} catch (error) {
const normalizedError =
error instanceof Error ? error : new Error(String(error));
options.onError?.(normalizedError);
}
},
onclose: () => {
options.onClose?.();
},
onerror: (error) => {
// 如果是中止错误,不抛出异常
if (error instanceof Error && error.name === "AbortError") {
console.log("SSE连接已被中止");
return;
}
const normalizedError =
error instanceof Error ? error : new Error(String(error));
options.onError?.(normalizedError);
throw normalizedError; // 停止自动重试
},
} as Parameters<typeof fetchEventSource>[1];
await fetchEventSource(options.url, fetchOptions);
} catch (error) {
// 静默处理所有 AbortError
if (error instanceof Error && error.name === "AbortError") {
console.log("SSE连接已被中止");
return;
}
// 只有真正的错误才触发错误回调
const normalized =
error instanceof Error ? error : new Error(String(error));
options.onError?.(normalized);
throw normalized;
}
}