Quellcode durchsuchen

Implementation: Implement aiplan-page to generate training reports from ai

cyx vor 5 Monaten
Ursprung
Commit
7f72ec1c0f

+ 13 - 2
TFPower-app/angular.json

@@ -31,8 +31,19 @@
                 "output": "assets"
               }
             ],
-            "styles": ["src/global.scss", "src/theme/variables.scss"],
-            "scripts": []
+            "styles": [
+              "src/global.scss",
+              "src/theme/variables.scss",
+              "node_modules/prismjs/themes/prism-coy.css",
+              "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css"
+            ],
+            "scripts": [
+              "node_modules/marked/marked.min.js",
+              "node_modules/prismjs/prism.js",
+              "node_modules/prismjs/components/prism-properties.min.js",
+              "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js",
+              "node_modules/prismjs/components/prism-typescript.min.js"
+            ]
           },
           "configurations": {
             "production": {

Datei-Diff unterdrückt, da er zu groß ist
+ 1071 - 186
TFPower-app/package-lock.json


+ 1 - 0
TFPower-app/package.json

@@ -28,6 +28,7 @@
     "@capacitor/status-bar": "6.0.2",
     "@ionic/angular": "^8.0.0",
     "ionicons": "^7.2.1",
+    "ngx-markdown": "^19.0.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.14.2"

+ 18 - 1
TFPower-app/src/app/aiplan-page/aiplan-page.component.html

@@ -13,7 +13,10 @@
     <ion-list>
       <ion-item>
         <ion-input label="训练类型" [value]="trainingType" [clearInput]="true" type="text"
-          placeholder="修身、锻炼、出汗等">{{trainingType}}</ion-input>
+          placeholder="修身、锻炼、出汗等"></ion-input>
+      </ion-item>
+      <ion-item>
+        <ion-input label="性别" [value]="sex" [clearInput]="true" type="text" placeholder="男(女)"></ion-input>
       </ion-item>
       <ion-item>
         <ion-input label="体重(kg)" [value]="weight" type="number" placeholder="75"></ion-input>
@@ -41,6 +44,20 @@
         </ion-row>
       </ion-grid>
     </ion-list>
+  </ion-card>
 
+  <!-- 生成计划 -->
+  @if(aiplan.length>0) {
+  <ion-card color="light">
+    <ion-card-header>
+      <ion-card-title>生成计划</ion-card-title>
+      <ion-card-subtitle>根据您填写的信息,我给你生成训练计划。</ion-card-subtitle>
+    </ion-card-header>
+    <ion-list>
+      <ion-item>
+        {{aiplan}}
+      </ion-item>
+    </ion-list>
   </ion-card>
+  }
 </ion-content>

+ 40 - 5
TFPower-app/src/app/aiplan-page/aiplan-page.component.ts

@@ -17,6 +17,7 @@ import {
   IonTitle,
   IonToolbar,
 } from '@ionic/angular/standalone';
+import { TestChatCompletion } from './fmode-chat-completion';
 
 @Component({
   selector: 'app-aiplan-page',
@@ -48,11 +49,14 @@ export class AiplanPageComponent implements OnInit {
   ngOnInit() {}
 
   trainingType: string = '';
+  sex: string = '';
   weight: number = NaN;
   trainingTime: number = NaN;
   trainingIntensity: string = '';
   trainingSite: string = '';
   needs: string = '';
+
+  aiplan: string = '';
   reset() {
     // 重置表单数据(有问题)
     // this.trainingType = '';
@@ -83,6 +87,8 @@ export class AiplanPageComponent implements OnInit {
     //   trainingSite: this.trainingSite,
     //   needs: this.needs,
     // };
+
+    // 用户输入数据提取
     let ipts = document.querySelectorAll('ion-input');
     let textarea = document.querySelectorAll('ion-textarea');
     let userinputs = '';
@@ -95,12 +101,41 @@ export class AiplanPageComponent implements OnInit {
     let userinputlist = userinputs.split(' ');
     let data = {
       trainingType: userinputlist[0],
-      weight: userinputlist[1],
-      trainingTime: userinputlist[2],
-      trainingIntensity: userinputlist[3],
-      trainingSite: userinputlist[4],
-      needs: userinputlist[5],
+      sex: userinputlist[1],
+      weight: userinputlist[2],
+      trainingTime: userinputlist[3],
+      trainingIntensity: userinputlist[4],
+      trainingSite: userinputlist[5],
+      needs: userinputlist[6],
     };
     console.log(data);
+
+    // ai对话实现
+    let prompt = `我是一名体重为${data.weight}kg的${data.sex}性,我只有${data.trainingTime}分钟来${data.trainingType},我希望训练强度${data.trainingIntensity}一点,而我想训练的部位为${data.trainingSite}, 一些其他的需求有${data.needs}。 请你作为一名专业的健身教练,根据我提供的信息,为我制定${data.trainingType}计划。`;
+    console.log(prompt);
+
+    let token = `r:E4KpGvTEto-187799890851732794669`;
+    localStorage.setItem('token', token);
+
+    let messageList = [
+      {
+        role: 'system',
+        content: `${new Date().toLocaleString}`,
+      },
+      {
+        role: 'user',
+        content: prompt,
+      },
+    ];
+
+    //fmode-chat
+    let completion = new TestChatCompletion(messageList);
+    completion.createCompletionByStream();
+
+    this.aiplan = '';
+    setInterval(() => {
+      // console.log(messageList);
+      this.aiplan = messageList[messageList.length - 1].content;
+    }, 1000);
   }
 }

+ 101 - 0
TFPower-app/src/app/aiplan-page/fmode-chat-completion.ts

@@ -0,0 +1,101 @@
+export interface TestChatMessage {
+  role: string;
+  content: string;
+}
+export class TestChatCompletion {
+  messageList: Array<TestChatMessage>;
+  constructor(messageList: Array<TestChatMessage>) {
+    this.messageList = messageList;
+  }
+  async createCompletionByStream() {
+    let token = localStorage.getItem('token');
+    let bodyJson = {
+      token: `Bearer ${token}`,
+      messages: this.messageList,
+      model: 'fmode-4.5-128k',
+      temperature: 0.5,
+      presence_penalty: 0,
+      frequency_penalty: 0,
+      top_p: 1,
+      stream: true,
+    };
+
+    let response = await 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',
+      }
+    );
+
+    let messageAiReply = '';
+    let messageIndex = this.messageList.length;
+    let reader = response.body?.getReader();
+    if (!reader) {
+      throw new Error('Failed to get the response reader.');
+    }
+
+    let decoder = new TextDecoder();
+    let buffer = '';
+
+    while (true) {
+      let { done, value } = await reader.read();
+      if (done) {
+        break;
+      }
+
+      buffer += decoder.decode(value);
+
+      // Split the buffer by newlines to get individual messages
+      let messages = buffer.split('\n');
+
+      // Process each message
+      for (let i = 0; i < messages.length - 1; i++) {
+        let message = messages[i];
+
+        // Process the message as needed
+        /**
+         * data: {"id":"chatcmpl-y2PLKqPDnwAFJIj2L5aqdH5TWK9Yv","object":"chat.completion.chunk","created":1696770162,"model":"fmode-4.5-128k","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
+         * data: {"id":"chatcmpl-y2PLKqPDnwAFJIj2L5aqdH5TWK9Yv","object":"chat.completion.chunk","created":1696770162,"model":"fmode-4.5-128k","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
+         * data: [DONE]
+         */
+        let dataText = message.replace('data: ', '');
+        if (dataText.startsWith('{')) {
+          try {
+            let dataJson = JSON.parse(dataText);
+            // console.log(dataJson);
+            messageAiReply += dataJson?.choices?.[0]?.delta?.content || '';
+            this.messageList[messageIndex] = {
+              role: 'assistant',
+              content: messageAiReply,
+            };
+          } catch (err) {}
+        }
+        if (dataText.startsWith('[')) {
+          // console.log(message);
+          // console.log('完成');
+          this.messageList[messageIndex] = {
+            role: 'assistant',
+            content: messageAiReply,
+          };
+          messageAiReply = '';
+        }
+        // Parse the message as JSON
+        // let data = JSON.parse(message);
+
+        // Clear the processed message from the buffer
+        buffer = buffer.slice(message.length + 1);
+      }
+    }
+  }
+}

+ 10 - 0
TFPower-app/src/app/app.module.ts

@@ -0,0 +1,10 @@
+import { NgModule } from '@angular/core';
+import { MarkdownModule } from 'ngx-markdown';
+import { AppComponent } from './app.component';
+
+@NgModule({
+  imports: [MarkdownModule.forRoot(), AppComponent],
+  declarations: [AppComponent],
+  bootstrap: [AppComponent],
+})
+export class AppModule {}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.