+
diff --git a/web/src/interfaces/chat_service.ts b/web/src/interfaces/chat_service.ts
index d04f064..7d62063 100644
--- a/web/src/interfaces/chat_service.ts
+++ b/web/src/interfaces/chat_service.ts
@@ -1,24 +1,31 @@
export interface IChatWithLLMRequest {
- messages: Message[]
+ messages: Message[];
/**
* 要使用的模型的 ID
*/
- model: string
+ model: string;
}
export interface Message {
- content?: string
- role?: string
- [property: string]: any
+ content?: string;
+ role?: string;
+ usage?: UsageInfo;
+ [property: string]: any;
}
export interface ModelInfo {
- model_id: string
- model_name: string
- model_type: string
+ model_id: string;
+ model_name: string;
+ model_type: string;
}
export interface ModelListInfo {
- vendor: string
- models: ModelInfo[]
+ vendor: string;
+ models: ModelInfo[];
+}
+
+export interface UsageInfo {
+ prompt_tokens: number;
+ completion_tokens: number;
+ total_tokens: number;
}
diff --git a/web/src/services/chat_service.ts b/web/src/services/chat_service.ts
index d075ea7..336e197 100644
--- a/web/src/services/chat_service.ts
+++ b/web/src/services/chat_service.ts
@@ -1,20 +1,21 @@
-import type { AxiosRequestConfig } from "axios"
+import type { AxiosRequestConfig } from "axios";
-import type { IChatWithLLMRequest } from "@/interfaces"
-import BaseClientService, { BaseUrl } from "./base_service.ts"
+import type { IChatWithLLMRequest, UsageInfo } from "@/interfaces";
+import BaseClientService, { BaseUrl } from "./base_service.ts";
export class ChatService {
- public static basePath = BaseUrl
+ public static basePath = BaseUrl;
/** Chat with LLM */
public static async ChatWithLLM(
accessToken: string,
request: IChatWithLLMRequest,
onProgress: (content: string) => void,
+ getUsageInfo: (object: UsageInfo) => void = () => { },
) {
- let response
- let buffer = ""
- let accumulatedContent = ""
+ let response;
+ let buffer = "";
+ let accumulatedContent = "";
try {
response = await fetch("/v1/chat/completions", {
method: "POST",
@@ -23,69 +24,77 @@ export class ChatService {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
- })
+ });
if (!response.ok) {
// eslint-disable-next-line unicorn/error-message
- throw new Error()
+ throw new Error();
}
- const reader = response.body?.getReader()
- const decoder = new TextDecoder()
+ const reader = response.body?.getReader();
+ const decoder = new TextDecoder();
while (true) {
- const { done, value } = await reader!.read()
+ const { done, value } = await reader!.read();
if (done)
- break
+ break;
// 将二进制数据转为字符串并存入缓冲区
- buffer += decoder.decode(value)
+ buffer += decoder.decode(value);
// 查找换行符分割数据
- const lines = buffer.split("\n")
+ const lines = buffer.split("\n");
// 保留未处理完的部分
- buffer = lines.pop() || ""
+ buffer = lines.pop() || "";
// 处理每一行
for (const line of lines) {
- const trimmedLine = line.trim()
+ const trimmedLine = line.trim();
if (!trimmedLine)
- continue
+ continue;
if (trimmedLine.startsWith("data: ")) {
- const jsonStr = trimmedLine.slice(6)
+ const jsonStr = trimmedLine.slice(6);
// 处理结束标记
if (jsonStr === "[DONE]") {
- onProgress(accumulatedContent) // 最终更新
- return
+ onProgress(accumulatedContent); // 最终更新
+ return;
}
try {
- const data = JSON.parse(jsonStr)
+ const data = JSON.parse(jsonStr);
+
+ // 处理内容
if (data.choices?.[0]?.delta?.content) {
// 累积内容
- accumulatedContent += data.choices[0].delta.content
+ accumulatedContent += data.choices[0].delta.content;
// 触发回调
- onProgress(accumulatedContent)
+ onProgress(accumulatedContent);
+ }
+
+ // 处理使用信息
+ if (data.usage) {
+ const { prompt_tokens, completion_tokens, total_tokens } = data.usage;
+ getUsageInfo({ prompt_tokens, completion_tokens, total_tokens });
}
}
catch (err) {
- console.error("JSON解析失败:", err)
+ console.error("JSON解析失败:", err);
}
}
}
}
}
catch (err) {
- console.error("Error:", err)
+ console.error("Error:", err);
}
}
// 获取模型列表
public static GetModelList(config?: AxiosRequestConfig
) {
- return BaseClientService.get(`${this.basePath}/model/list`, config)
+ return BaseClientService.get(`${this.basePath}/model/list`, config);
}
}
diff --git a/web/src/stores/chat_store.ts b/web/src/stores/chat_store.ts
index 1016a8d..c8c2827 100644
--- a/web/src/stores/chat_store.ts
+++ b/web/src/stores/chat_store.ts
@@ -1,4 +1,4 @@
-import type { IChatWithLLMRequest, ModelInfo, ModelListInfo } from "@/interfaces";
+import type { IChatWithLLMRequest, ModelInfo, ModelListInfo, UsageInfo } from "@/interfaces";
import { ChatService } from "@/services";
export const useChatStore = defineStore("chat", () => {
@@ -16,6 +16,7 @@ export const useChatStore = defineStore("chat", () => {
const chatWithLLM = async (
request: IChatWithLLMRequest,
onProgress: (content: string) => void, // 接收进度回调
+ getUsageInfo: (object: UsageInfo) => void = () => { },
) => {
if (completing.value)
throw new Error("正在响应中");
@@ -24,6 +25,8 @@ export const useChatStore = defineStore("chat", () => {
try {
await ChatService.ChatWithLLM(token, request, (content) => {
onProgress(content);
+ }, (object: UsageInfo) => {
+ getUsageInfo(object);
});
}
catch (error) {
@@ -71,6 +74,15 @@ export const useChatStore = defineStore("chat", () => {
});
}
historyMessages.value[historyMessages.value.length - 1].content = content;
+ }, (usageInfo: UsageInfo) => {
+ // 处理使用usage信息回调
+ // 如果最后一条消息是助手的回复,则更新使用信息
+ if (
+ historyMessages.value.length > 0
+ && historyMessages.value[historyMessages.value.length - 1].role === "assistant"
+ ) {
+ historyMessages.value[historyMessages.value.length - 1].usage = usageInfo;
+ }
});
}
}
diff --git a/web/src/stores/index.ts b/web/src/stores/index.ts
index fd59d5d..0637980 100644
--- a/web/src/stores/index.ts
+++ b/web/src/stores/index.ts
@@ -1,2 +1,2 @@
-export * from "./asr_store"
-export * from "./chat_store"
+export * from "./asr_store";
+export * from "./chat_store";
diff --git a/web/src/utils/media.ts b/web/src/utils/media.ts
index 1793bad..9c47b46 100644
--- a/web/src/utils/media.ts
+++ b/web/src/utils/media.ts
@@ -7,37 +7,55 @@ export const matchMedia = (
if (type === "sm") {
if (window.matchMedia("(max-width: 767.98px)").matches) {
/* 窗口小于或等于 */
- matchFunc?.()
+ matchFunc?.();
}
else {
- mismatchFunc?.()
+ mismatchFunc?.();
}
}
else if (type === "md") {
if (window.matchMedia("(max-width: 992px)").matches) {
/* 窗口小于或等于 */
- matchFunc?.()
+ matchFunc?.();
}
else {
- mismatchFunc?.()
+ mismatchFunc?.();
}
}
else if (type === "lg") {
if (window.matchMedia("(max-width: 1200px)").matches) {
/* 窗口小于或等于 */
- matchFunc?.()
+ matchFunc?.();
}
else {
- mismatchFunc?.()
+ mismatchFunc?.();
}
}
else {
if (window.matchMedia(`(max-width: ${type}px)`).matches) {
/* 窗口小于或等于 */
- matchFunc?.()
+ matchFunc?.();
}
else {
- mismatchFunc?.()
+ mismatchFunc?.();
}
}
-}
+};
+
+export const useWindowWidth = () => {
+ const width = ref(window.innerWidth);
+
+ const updateWidth = () => {
+ width.value = window.innerWidth;
+ };
+
+ onMounted(() => {
+ window.addEventListener("resize", updateWidth);
+ });
+
+ onUnmounted(() => {
+ window.removeEventListener("resize", updateWidth);
+ });
+
+ return width;
+};
diff --git a/web/src/views/CommunityView.vue b/web/src/views/CommunityView.vue
index 92c4d17..77dbea4 100644
--- a/web/src/views/CommunityView.vue
+++ b/web/src/views/CommunityView.vue
@@ -49,7 +49,6 @@ watch(() => modelList.value, (newVal) => {
if (newVal.length > 0 && newVal[0].models.length > 0) {
modelInfo.value = newVal[0].models[0];
}
- console.log("Options updated:", options.value);
}
}, { immediate: true, deep: true });
@@ -89,17 +88,17 @@ onMounted(() => {
-
+
- 助手:
+ 助手:
你好,我是你的智能助手,请问有什么可以帮助你的吗?
-
+
@@ -107,6 +106,9 @@ onMounted(() => {
{{ msg.role === 'user' ? '你:' : '助手:' }}
+
+ Tokens: {{ msg.usage?.total_tokens }}
+
@@ -121,7 +123,12 @@ onMounted(() => {
-
+