AIWbw.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. import { useState, useCallback } from "react";
  2. import type { IWbw } from "./Wbw/WbwWord"
  3. import { paliEndingGrammar, paliEndingType } from "../general/PaliEnding";
  4. import { useIntl } from "react-intl";
  5. import { _useAppSelector } from "../../hooks";
  6. import { _siteInfo } from "../../reducers/layout";
  7. // 类型定义
  8. export interface WbwElement<T> {
  9. value: T;
  10. status: number;
  11. }
  12. interface StreamController {
  13. addData: (jsonlLine: string) => void;
  14. complete: () => void;
  15. }
  16. interface ProcessWbwStreamOptions {
  17. modelId: string;
  18. data: IWbw[];
  19. endingType: string[];
  20. endingGrammar: string[];
  21. onProgress?: (data: IWbw[], isComplete: boolean) => void;
  22. onComplete?: (finalData: IWbw[]) => void;
  23. onError?: (error: string) => void;
  24. }
  25. /**
  26. * 处理JSONL流式输出的函数
  27. */
  28. export const processWbwStream = async ({
  29. modelId,
  30. data,
  31. endingType,
  32. endingGrammar,
  33. onProgress,
  34. onComplete,
  35. onError,
  36. }: ProcessWbwStreamOptions): Promise<{
  37. success: boolean;
  38. data?: IWbw[];
  39. error?: string;
  40. }> => {
  41. if (typeof import.meta.env.VITE_REACT_APP_OPENAI_PROXY === "undefined") {
  42. console.error("no REACT_APP_OPENAI_PROXY");
  43. const error = "API配置错误";
  44. onError?.(error);
  45. return { success: false, error };
  46. }
  47. const sys_prompt = `
  48. 你是一个巴利语专家。用户提供的jsonl 数据 是巴利文句子的全部单词
  49. 请根据每个单词的拼写 real.value 填写如下字段
  50. 巴利单词的词典原型:parent.value
  51. 单词的中文意思:meaning.value
  52. 巴利单词的拆分:factors.value
  53. 语尾请加[]
  54. 拆分后每个组成部分的中文意思factorMeaning.value
  55. 请按照下表填写巴利语单词的类型 type.value
  56. \`\`\`csv
  57. ${endingType.join("\n")}
  58. \`\`\`
  59. 请按照下表填写巴利语单词的语法信息 grammar.value
  60. 名词和形容词填写 性,数,格
  61. 动词填写 人称,数,时态语气
  62. 用 $ 作为分隔符
  63. \`\`\`csv
  64. ${endingGrammar.join("\n")}
  65. \`\`\`
  66. 直接输出JSONL格式数据
  67. `;
  68. const jsonl = data.map((obj) => JSON.stringify(obj)).join("\n");
  69. const prompt = `
  70. \`\`\`jsonl
  71. ${jsonl}
  72. \`\`\`
  73. `;
  74. console.debug("ai wbw system prompt", sys_prompt, prompt);
  75. try {
  76. const payload = {
  77. model: "grok-3", // 或者从models数组中获取实际模型名称
  78. messages: [
  79. {
  80. role: "system",
  81. content: sys_prompt,
  82. },
  83. { role: "user", content: prompt },
  84. ],
  85. stream: true,
  86. temperature: 0.3,
  87. max_tokens: 4000,
  88. };
  89. const url = import.meta.env.VITE_REACT_APP_OPENAI_PROXY;
  90. const requestData = {
  91. model_id: modelId,
  92. payload: payload,
  93. };
  94. console.info("api request", url, requestData);
  95. const response = await fetch(url, {
  96. method: "POST",
  97. headers: {
  98. "Content-Type": "application/json",
  99. Authorization: `Bearer AIzaSyCzr8KqEdaQ3cRCxsFwSHh8c7kF3RZTZWw`,
  100. },
  101. body: JSON.stringify(requestData),
  102. });
  103. if (!response.ok) {
  104. throw new Error(`HTTP error! status: ${response.status}`);
  105. }
  106. const reader = response.body?.getReader();
  107. if (!reader) {
  108. throw new Error("无法获取响应流");
  109. }
  110. const decoder = new TextDecoder();
  111. let buffer = "";
  112. let jsonlBuffer = ""; // 用于累积JSONL内容
  113. const resultData: IWbw[] = [];
  114. // 创建流控制器
  115. const streamController: StreamController = {
  116. addData: (jsonlLine: string) => {
  117. try {
  118. // 解析JSONL行
  119. const parsedData = JSON.parse(jsonlLine.trim());
  120. console.info("ai wbw stream ok", parsedData);
  121. // 转换为IWbw格式
  122. const wbwData: IWbw = {
  123. book: parsedData.book || 0,
  124. para: parsedData.para || 0,
  125. sn: parsedData.sn || [],
  126. word: parsedData.word || { value: "", status: 0 },
  127. real: parsedData.real || { value: null, status: 0 },
  128. meaning: parsedData.meaning,
  129. type: parsedData.type,
  130. grammar: parsedData.grammar,
  131. style: parsedData.style,
  132. case: parsedData.case,
  133. parent: parsedData.parent,
  134. parent2: parsedData.parent2,
  135. grammar2: parsedData.grammar2,
  136. factors: parsedData.factors,
  137. factorMeaning: parsedData.factorMeaning,
  138. relation: parsedData.relation,
  139. note: parsedData.note,
  140. bookMarkColor: parsedData.bookMarkColor,
  141. bookMarkText: parsedData.bookMarkText,
  142. locked: parsedData.locked || false,
  143. confidence: parsedData.confidence || 0.5,
  144. attachments: parsedData.attachments,
  145. hasComment: parsedData.hasComment,
  146. grammarId: parsedData.grammarId,
  147. bookName: parsedData.bookName,
  148. editor: parsedData.editor,
  149. created_at: parsedData.created_at,
  150. updated_at: parsedData.updated_at,
  151. };
  152. resultData.push(wbwData);
  153. // 调用进度回调
  154. onProgress?.(resultData, false);
  155. } catch (e) {
  156. console.warn("解析JSONL行失败:", e, "内容:", jsonlLine);
  157. }
  158. },
  159. complete: () => {
  160. onProgress?.(resultData, true);
  161. onComplete?.(resultData);
  162. },
  163. };
  164. try {
  165. while (true) {
  166. const { done, value } = await reader.read();
  167. if (done) {
  168. // 处理最后的缓冲内容
  169. if (jsonlBuffer.trim()) {
  170. const lines = jsonlBuffer.trim().split("\n");
  171. for (const line of lines) {
  172. if (line.trim()) {
  173. streamController.addData(line);
  174. }
  175. }
  176. }
  177. streamController.complete();
  178. return { success: true, data: resultData };
  179. }
  180. buffer += decoder.decode(value, { stream: true });
  181. const lines = buffer.split("\n");
  182. buffer = lines.pop() || "";
  183. for (const line of lines) {
  184. if (line.trim() === "") continue;
  185. if (line.startsWith("data: ")) {
  186. const data = line.slice(6);
  187. if (data === "[DONE]") {
  188. // 处理剩余的JSONL内容
  189. if (jsonlBuffer.trim()) {
  190. const jsonlLines = jsonlBuffer.trim().split("\n");
  191. for (const jsonlLine of jsonlLines) {
  192. if (jsonlLine.trim()) {
  193. streamController.addData(jsonlLine);
  194. }
  195. }
  196. }
  197. streamController.complete();
  198. return { success: true, data: resultData };
  199. }
  200. try {
  201. const parsed = JSON.parse(data);
  202. const delta = parsed.choices?.[0]?.delta;
  203. if (delta?.content) {
  204. // 累积内容到JSONL缓冲区
  205. jsonlBuffer += delta.content;
  206. // 检查是否有完整的JSONL行
  207. const jsonlLines = jsonlBuffer.split("\n");
  208. // 保留最后一行(可能不完整)
  209. jsonlBuffer = jsonlLines.pop() || "";
  210. // 处理完整的行
  211. for (const jsonlLine of jsonlLines) {
  212. if (jsonlLine.trim()) {
  213. streamController.addData(jsonlLine);
  214. }
  215. }
  216. }
  217. } catch (e) {
  218. console.warn("解析SSE数据失败:", e);
  219. }
  220. }
  221. }
  222. }
  223. } catch (error) {
  224. console.error("读取流数据失败:", error);
  225. const errorMessage = "读取响应流失败";
  226. onError?.(errorMessage);
  227. return { success: false, error: errorMessage };
  228. }
  229. } catch (error) {
  230. console.error("API调用失败:", error);
  231. const errorMessage = "API调用失败,请重试";
  232. onError?.(errorMessage);
  233. return { success: false, error: errorMessage };
  234. }
  235. };
  236. /**
  237. * React Hook 用法示例
  238. */
  239. export const useWbwStreamProcessor = () => {
  240. const [isProcessing, setIsProcessing] = useState<boolean>(false);
  241. const [wbwData, setWbwData] = useState<IWbw[]>([]);
  242. const [error, setError] = useState<string>();
  243. const intl = useIntl(); // 在Hook中使用
  244. const endingType = paliEndingType.map((item) => {
  245. return (
  246. intl.formatMessage({ id: `dict.fields.type.${item}.label` }) +
  247. `:.${item}.`
  248. );
  249. });
  250. const endingGrammar = paliEndingGrammar.map((item) => {
  251. return (
  252. intl.formatMessage({ id: `dict.fields.type.${item}.label` }) +
  253. `:.${item}.`
  254. );
  255. });
  256. const processStream = useCallback(
  257. async (modelId: string, data: IWbw[]) => {
  258. setIsProcessing(true);
  259. setWbwData([]);
  260. setError(undefined);
  261. const result = await processWbwStream({
  262. modelId,
  263. data,
  264. endingType,
  265. endingGrammar,
  266. onProgress: (data, _isComplete) => {
  267. console.info("onProgress", data);
  268. setWbwData([...data]); // 创建新数组触发重渲染
  269. },
  270. onComplete: (finalData) => {
  271. setWbwData(finalData);
  272. setIsProcessing(false);
  273. },
  274. onError: (errorMessage) => {
  275. setError(errorMessage);
  276. setIsProcessing(false);
  277. },
  278. });
  279. if (!result.success) {
  280. setError(result.error || "处理失败");
  281. setIsProcessing(false);
  282. }
  283. return result;
  284. },
  285. [endingGrammar, endingType]
  286. );
  287. return {
  288. processStream,
  289. isProcessing,
  290. wbwData,
  291. error,
  292. clearData: useCallback(() => {
  293. setWbwData([]);
  294. setError(undefined);
  295. }, []),
  296. };
  297. };