feat: 项目初始化、完成基本流式传输和语音识别功能

This commit is contained in:
2025-06-28 19:21:46 +08:00
commit d6f9cd7aed
91 changed files with 7827 additions and 0 deletions

9
web/src/utils/context.ts Normal file
View File

@@ -0,0 +1,9 @@
import type { LoadingBarApiInjection } from "naive-ui/es/loading-bar/src/LoadingBarProvider"
import type { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider"
import type { NotificationApiInjection } from "naive-ui/es/notification/src/NotificationProvider"
export const context: {
message?: MessageApiInjection
notification?: NotificationApiInjection
loadingBar?: LoadingBarApiInjection
} = {}

6
web/src/utils/index.ts Normal file
View File

@@ -0,0 +1,6 @@
export * from "./context"
export * from "./media"
export * from "./pcm"
export * from "./title"
export * from "./title"
export * from "./url"

43
web/src/utils/media.ts Normal file
View File

@@ -0,0 +1,43 @@
/** 视窗匹配回调函数 */
export const matchMedia = (
type: "sm" | "md" | "lg" | string,
matchFunc?: Function,
mismatchFunc?: Function,
) => {
if (type === "sm") {
if (window.matchMedia("(max-width: 767.98px)").matches) {
/* 窗口小于或等于 */
matchFunc?.()
}
else {
mismatchFunc?.()
}
}
else if (type === "md") {
if (window.matchMedia("(max-width: 992px)").matches) {
/* 窗口小于或等于 */
matchFunc?.()
}
else {
mismatchFunc?.()
}
}
else if (type === "lg") {
if (window.matchMedia("(max-width: 1200px)").matches) {
/* 窗口小于或等于 */
matchFunc?.()
}
else {
mismatchFunc?.()
}
}
else {
if (window.matchMedia(`(max-width: ${type}px)`).matches) {
/* 窗口小于或等于 */
matchFunc?.()
}
else {
mismatchFunc?.()
}
}
}

12
web/src/utils/pcm.ts Normal file
View File

@@ -0,0 +1,12 @@
export const convertToPCM16 = (float32Array: Float32Array): Uint8Array => {
const int16Buffer = new Int16Array(float32Array.length)
for (let i = 0; i < float32Array.length; i++) {
int16Buffer[i] = Math.max(-1, Math.min(1, float32Array[i])) * 0x7FFF
}
const buffer = new ArrayBuffer(int16Buffer.length * 2)
const view = new DataView(buffer)
for (let i = 0; i < int16Buffer.length; i++) {
view.setInt16(i * 2, int16Buffer[i], true)
}
return new Uint8Array(buffer)
}

25
web/src/utils/title.ts Normal file
View File

@@ -0,0 +1,25 @@
import { useTitle } from "@vueuse/core"
const DEFAULT_TITLE = "Agent"
const DEFAULT_DESCRIPTION = document
.querySelector("meta[name='description']")
?.getAttribute("content")
export function setTitle(title?: string) {
useTitle().value = (title ? `${title} | ` : "") + DEFAULT_TITLE
}
export function resetDescription() {
document
.querySelector("meta[name='description']")
?.setAttribute("content", DEFAULT_DESCRIPTION!)
}
export function setDescription(description?: string) {
if (!description)
return
document
.querySelector("meta[name='description']")
?.setAttribute("content", `${description} | ${DEFAULT_TITLE}`)
}

15
web/src/utils/url.ts Normal file
View File

@@ -0,0 +1,15 @@
/** 直接当前页面跳转到指定url */
export const jump = (url: string) => {
window.location.href = url
}
/** 在新标签页中跳转到指定url */
export const jumpBlank = (url: string) => {
window.open(url, "_blank")
}
/** 将对象转换为url查询字符串 */
export const queryFormat = (query: Record<string, any>) => {
const params = new URLSearchParams(query)
return params.toString() ? `?${params}` : ""
}