Эх сурвалжийг харах

feat: rxjs restructure chat page

RyaneMax 8 сар өмнө
parent
commit
2f28df08dc

+ 2 - 2
src/app/app.component.ts

@@ -1,8 +1,8 @@
 import { Component } from '@angular/core';
 import * as Parse from "parse";
 Parse.initialize("dev");
-(Parse as any).serverURL = 'http://web2023.fmode.cn:9999/parse'
-
+(Parse as any).serverURL = 'http://web2023.fmode.cn:9999/parse';
+(Parse as any).liveQueryServerURL = 'http://web2023.fmode.cn:9999/parse';
 @Component({
   selector: 'app-root',
   templateUrl: 'app.component.html',

+ 22 - 0
src/modules/aigc/chat/chat.data.md

@@ -0,0 +1,22 @@
+# 原始接收数据
+
+``` sh
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"从"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"前"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"有"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"一个"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"小"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"村"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"庄"},"logprobs":null,"finish_reason":null}]}
+# ...............
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"充"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"实"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"和"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"幸"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"福"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"。"},"logprobs":null,"finish_reason":null}]}
+data: {"id":"chatcmpl-CMMUQdD0y8Ug6IJTnxLFAwsve42QX","object":"chat.completion.chunk","created":1720420748,"model":"gpt-3.5-turbo-0125","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
+data: [DONE]
+```

+ 22 - 2
src/modules/aigc/chat/chat.page.ts

@@ -1,6 +1,7 @@
 import { Component, OnInit } from '@angular/core';
 // 引用FmodeChatCompletion类
-import { TestChatCompletion, TestChatMessage } from './class-chat-completion';
+import { TestChatCompletion, TestChatMessage } from './class-test-chat-completion';
+import { TestRxjsChatCompletion } from './class-rxjs-chat-completion';
 
 @Component({
   selector: 'app-chat',
@@ -13,7 +14,10 @@ export class ChatPage implements OnInit {
 
   completion:TestChatCompletion
   constructor() { 
+    // 测试类:纯fetch读取http event stream数据
     this.completion = new TestChatCompletion(this.messageList)
+
+    // 测试类:rxjs封装的可观察对象
   }
 
   ngOnInit() {
@@ -24,7 +28,23 @@ export class ChatPage implements OnInit {
       content: this.userInput
     })
     this.userInput = ""
-    this.completion.createCompletionByStream()
+    
+    // this.completion.createCompletionByStream()
+
+    // messageList在competion内部,已经赋予了完整的message
+    // 下方暴露出来的可订阅内容,主要是用于关键字过滤,或者其他开发逻辑的续写
+    let testChatCompletion = new TestRxjsChatCompletion(this.messageList);
+    testChatCompletion.createCompletionByStream().subscribe({
+        next: ({ content, cumulativeContent, done }) => {
+            console.log(`Content: ${content}`);
+            console.log(`Cumulative Content: ${cumulativeContent}`);
+            if (done) {
+                console.log('Stream completed');
+            }
+        },
+        error: err => console.error(err),
+        complete: () => console.log('Observable completed')
+    });
 
   }
 

+ 109 - 0
src/modules/aigc/chat/class-rxjs-chat-completion.ts

@@ -0,0 +1,109 @@
+import { Observable, from, of } from 'rxjs';
+import { switchMap, map, catchError, finalize } from 'rxjs/operators';
+
+export interface TestRxjsChatMessage {
+    role: string;
+    content: string;
+}
+
+export class TestRxjsChatCompletion {
+    messageList: Array<TestRxjsChatMessage>;
+
+    constructor(messageList: Array<TestRxjsChatMessage>) {
+        this.messageList = messageList;
+    }
+
+    createCompletionByStream(): Observable<{ content: string, cumulativeContent: string, done: boolean }> {
+        const token = localStorage.getItem("token");
+        const bodyJson = {
+            "token": `Bearer ${token}`,
+            "messages": this.messageList,
+            "model": "fmode-3.6-16k",
+            "temperature": 0.5,
+            "presence_penalty": 0,
+            "frequency_penalty": 0,
+            "top_p": 1,
+            "stream": true
+        };
+
+        return from(fetch("https://test.fmode.cn/api/apig/aigc/gpt/v1/chat/completions", {
+            "headers": {
+                "accept": "text/event-stream",
+                "sec-fetch-dest": "empty",
+                "sec-fetch-mode": "cors",
+                "sec-fetch-site": "same-site"
+            },
+            "referrer": "https://ai.fmode.cn/",
+            "referrerPolicy": "strict-origin-when-cross-origin",
+            "body": JSON.stringify(bodyJson),
+            "method": "POST",
+            "mode": "cors",
+            "credentials": "omit"
+        })).pipe(
+            switchMap(response => {
+                const reader = response.body?.getReader();
+                if (!reader) {
+                    throw new Error("Failed to get the response reader.");
+                }
+                const decoder = new TextDecoder();
+                let buffer = "";
+                let messageAiReply = "";
+                let messageIndex = this.messageList.length;
+
+                return new Observable<{ content: string, cumulativeContent: string, done: boolean }>(observer => {
+                    const read = () => {
+                        reader.read().then(({ done, value }) => {
+                            if (done) {
+                                observer.complete();
+                                return;
+                            }
+
+                            buffer += decoder.decode(value);
+                            let messages = buffer.split("\n");
+
+                            for (let i = 0; i < messages.length - 1; i++) {
+                                let message = messages[i];
+                                let dataText = message.replace("data: ", "");
+
+                                if (dataText.startsWith("{")) {
+                                    try {
+                                        let dataJson = JSON.parse(dataText);
+                                        let content = dataJson?.choices?.[0]?.delta?.content || "";
+                                        messageAiReply += content;
+                                        this.messageList[messageIndex] = {
+                                            role: "assistant",
+                                            content: messageAiReply
+                                        };
+                                        observer.next({ content, cumulativeContent: messageAiReply, done: false });
+                                    } catch (err) { }
+                                }
+
+                                if (dataText.startsWith("[")) {
+                                    this.messageList[messageIndex] = {
+                                        role: "assistant",
+                                        content: messageAiReply
+                                    };
+                                    observer.next({ content: "", cumulativeContent: messageAiReply, done: true });
+                                    messageAiReply = "";
+                                }
+
+                                buffer = buffer.slice(message.length + 1);
+                            }
+
+                            read();
+                        }).catch(err => observer.error(err));
+                    };
+
+                    read();
+                });
+            }),
+            catchError(err => {
+                console.error(err);
+                return of({ content: "", cumulativeContent: "", done: true });
+            }),
+            finalize(() => {
+                console.log("Stream completed");
+            })
+        );
+    }
+}

+ 5 - 1
src/modules/aigc/chat/class-chat-completion.ts → src/modules/aigc/chat/class-test-chat-completion.ts

@@ -1,3 +1,6 @@
+/*
+  案例:纯fetch读取http event stream数据
+*/
 export interface TestChatMessage{
     role:string
     content:string
@@ -13,7 +16,7 @@ let token = localStorage.getItem("token");
 let bodyJson = {
   "token": `Bearer ${token}`,
   "messages": this.messageList,
-  "model": "gpt-3.5-turbo",
+  "model": "fmode-3.6-16k",
   "temperature": 0.5,
   "presence_penalty": 0,
   "frequency_penalty": 0,
@@ -68,6 +71,7 @@ while (true) {
      * data: [DONE]
      */
     let dataText = message.replace("data:\ ","")
+    console.log(dataText)
     if(dataText.startsWith("{")){
       try{
         let dataJson = JSON.parse(dataText)