flowaaa 1 жил өмнө
parent
commit
a6706a6aa0

+ 4 - 1
app-angular/src/app/app.component.html

@@ -4,13 +4,16 @@
     <ion-tab-bar slot="bottom">
         <ion-tab-button routerLink="/lesson/community">
             <ion-icon name="home-outline"></ion-icon>
-
             社区
         </ion-tab-button>
         <!-- <ion-tab-button routerLink="/lesson/near">
             <ion-icon name="footsteps"></ion-icon>
             附近
         </ion-tab-button> -->
+        <ion-tab-button routerLink="/lesson/page-chat">
+            <ion-icon name="footsteps"></ion-icon>
+            AI
+        </ion-tab-button>
         <ion-tab-button routerLink="/lesson/they">
             <ion-icon name="chatbubbles"></ion-icon>
             消息

+ 100 - 0
app-angular/src/modules/lesson/class-chat-completion.ts

@@ -0,0 +1,100 @@
+export interface TestChatMessage {
+    role: string
+    content: string
+}
+export class TestChatCompletion {
+    messageList: Array<TestChatMessage>
+    constructor(messageList: Array<TestChatMessage>) {
+        this.messageList = messageList
+    }
+    async createCompletionByStream(call?: Function) {
+
+        let token = localStorage.getItem("token");
+        let bodyJson = {
+            "token": `Bearer ${token}`,
+            "messages": this.messageList,
+            "model": "gpt-3.5-turbo",
+            "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":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
+                 * data: {"id":"chatcmpl-y2PLKqPDnwAFJIj2L5aqdH5TWK9Yv","object":"chat.completion.chunk","created":1696770162,"model":"gpt-3.5-turbo-0613","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
+                    }
+                    call && call(true)
+                    messageAiReply = ""
+                }
+                // Parse the message as JSON
+                // let data = JSON.parse(message);
+
+                // Clear the processed message from the buffer
+                buffer = buffer.slice(message.length + 1);
+            }
+        }
+    }
+}

+ 1 - 0
app-angular/src/modules/lesson/community/community.module.ts

@@ -14,6 +14,7 @@ import { RecommendDetailComponent } from '../recommend-detail/recommend-detail.c
        
         ScienceDetailComponent,
         RecommendDetailComponent,
+        AttentionDetailComponent
     ],
     imports: [
         CommonModule,

+ 2 - 0
app-angular/src/modules/lesson/lesson-routing.module.ts

@@ -12,6 +12,7 @@ import { RecommendDetailComponent } from './recommend-detail/recommend-detail.co
 import { UserFollowComponent } from './user-follow/user-follow.component';
 import { UserTagComponent } from './user-tag/user-tag.component';
 import { UserCollectionComponent } from './user-collection/user-collection.component';
+import { PageChatComponent } from './page-chat/page-chat.component';
 
 
 const routes: Routes = [
@@ -19,6 +20,7 @@ const routes: Routes = [
   { path: 'they', component: TheyComponent },
   { path: 'community', component: CommunityComponent },
   { path: 'near', component: NearComponent },
+  { path: 'page-chat', component: PageChatComponent },
   { path: 'they/detail', component: TheyDetailComponent },
   { path: 'community/scienceDetail', component: ScienceDetailComponent },
   { path: 'community/share', component: ShareComponent },

+ 3 - 0
app-angular/src/modules/lesson/lesson.module.ts

@@ -16,6 +16,7 @@ import { AttentionDetailComponent } from './attention-detail/attention-detail.co
 import { UserFollowComponent } from './user-follow/user-follow.component';
 import { UserTagComponent } from './user-tag/user-tag.component';
 import { UserCollectionComponent } from './user-collection/user-collection.component';
+import { PageChatComponent } from './page-chat/page-chat.component';
 
 @NgModule({
   declarations: [
@@ -31,6 +32,8 @@ import { UserCollectionComponent } from './user-collection/user-collection.compo
     UserFollowComponent,
     UserTagComponent,
     UserCollectionComponent,
+    PageChatComponent,
+
 
 
   ],

+ 5 - 5
app-angular/src/modules/lesson/near/near.component.ts

@@ -1,11 +1,11 @@
 import { Component } from '@angular/core';
 
-
+// 使用@Component装饰器定义组件的元数据
 @Component({
-  selector: 'app-near',
-  templateUrl: './near.component.html',
-  styleUrls: ['./near.component.scss']
+  selector: 'app-near', // 组件的选择器,用于在模板中引用组件
+  templateUrl: './near.component.html', // 组件的模板文件路径
+  styleUrls: ['./near.component.scss'] // 组件的样式文件路径
 })
 export class NearComponent {
-
+  // 这是NearComponent组件的类定义,它是一个空类,没有任何属性和方法
 }

+ 123 - 0
app-angular/src/modules/lesson/page-chat/page-chat.component.html

@@ -0,0 +1,123 @@
+<div class="ion-page">
+    <ion-header>
+        <ion-toolbar>
+            <ion-title>AI</ion-title>
+        </ion-toolbar>
+        <ion-segment value="all">
+            <ion-segment-button value="all" (click)="changeTab('all')">
+                <ion-label>AI宠物</ion-label>
+            </ion-segment-button>
+            <ion-segment-button value="favorites" (click)="changeTab('favorites')">
+                <ion-label>定制专属</ion-label>
+            </ion-segment-button>
+        </ion-segment>
+    </ion-header>
+    <ion-content>
+
+        <!-- AI宠物 -->
+        <ng-container *ngIf="currentTab === 'all'">
+
+            <div class="page">
+                <div class="chat-container">
+                    <!--AI固定内容-->
+                    <div class="message-container">
+                        <img class="avatar-left"
+                            src="https://imgsource.huashi6.com/images/ai/2023/12/2/15_98156.jpg?e=1701504174&token=qFZErZx7WS1v5B4rgQE2KLMHlYHVNaCuXeaA9OLD:hb4mNBg4O9FVkTgqkjIu1FpL_sU=?x-image-process=image/resize,m_fixed,w_300"
+                            alt="Avatar">
+                        <div class="message-box-left">
+                            <div class="message-content">
+                                有什么我可以帮助你的吗?
+                            </div>
+                        </div>
+                    </div>
+                    <!--AI-->
+                    <ng-container *ngFor="let msg of messageList">
+                        <ng-container *ngIf="msg?.role === 'assistant'">
+                            <div class="message-container">
+                                <img class="avatar-left"
+                                    src="https://imgsource.huashi6.com/images/ai/2023/12/2/15_98156.jpg?e=1701504174&token=qFZErZx7WS1v5B4rgQE2KLMHlYHVNaCuXeaA9OLD:hb4mNBg4O9FVkTgqkjIu1FpL_sU=?x-image-process=image/resize,m_fixed,w_300"
+                                    alt="Avatar">
+                                <div class="message-box-left">
+                                    <div class="message-content">
+                                        {{msg?.content}}
+                                    </div>
+                                </div>
+                            </div>
+                        </ng-container>
+                        <ng-container *ngIf="msg?.role === 'user'">
+                            <div class="message-container-mine">
+                                <img class="avatar-right"
+                                    [src]="currentUser?.get('avatar')||'https://ionicframework.com/docs/img/demos/avatar.svg'"
+                                    alt="Avatar">
+                                <div class="message-box-right">
+                                    <div class="message-content">
+                                        {{msg?.content}}
+                                    </div>
+                                </div>
+                            </div>
+                        </ng-container>
+                    </ng-container>
+
+                </div>
+
+                <div class="footer">
+                    <div class="send-container">
+                        <input [(ngModel)]="userInput" type="text" class="message-input" placeholder="输入消息...">
+                        <button class="send-button" (click)="send()">发送</button>
+                    </div>
+
+                </div>
+
+            </div>
+
+        </ng-container>
+
+        <!-- 显示AI宠物定制 -->
+        <ng-container *ngIf="currentTab === 'favorites'">
+            <ion-list>
+                <h4 style="text-align: center;padding: 15px;">请填写以下信息,以便为你定制专属宠物</h4>
+
+                <ion-item>
+                    <ion-select [value]="planOptions.gender" (ionChange)="setOption('gender',$event)" label="性别"
+                        placeholder="">
+                        <ion-select-option value="女">女</ion-select-option>
+                        <ion-select-option value="男">男</ion-select-option>
+                        <ion-select-option value="未知">未知</ion-select-option>
+                    </ion-select>
+                </ion-item>
+
+                <ion-item>
+                    <ion-input label="年龄" type="number" placeholder=""></ion-input>
+                </ion-item>
+                <ion-item>
+                    <ion-input label="身高(cm)" type="number" placeholder=""></ion-input>
+                </ion-item>
+                <ion-item>
+                    <ion-input label="体重(kg)" type="number" placeholder=""></ion-input>
+                </ion-item>
+                <ion-item>
+                    <ion-input label="性别" type="string" placeholder=""></ion-input>
+                </ion-item>
+                <ion-item>
+                    <ion-select [value]="planOptions.targets" (ionChange)="setOption('targets',$event)"
+                        aria-label="object" placeholder="选择你的偏好" [multiple]="true">
+                        <ion-select-option value="慵懒休闲">慵懒休闲</ion-select-option>
+                        <ion-select-option value="活泼好动">活泼好动</ion-select-option>
+                        <ion-select-option value="独立自主">独立自主</ion-select-option>
+                    </ion-select>
+                </ion-item>
+                <ion-item>
+                    <ion-textarea label="你是否有其他信息或补充" labelPlacement="floating" placeholder="Enter text"></ion-textarea>
+                </ion-item>
+            </ion-list>
+            <!-- <div style="display: flex; justify-content: center;"> -->
+            <ion-button (click)="sendPlan()" expand="block">生成</ion-button>
+            <!-- </div> -->
+            <div>
+                <!--AI计划显示区-->
+            </div>
+
+        </ng-container>
+        <div class="navfooter"> </div>
+    </ion-content>
+</div>

+ 96 - 0
app-angular/src/modules/lesson/page-chat/page-chat.component.scss

@@ -0,0 +1,96 @@
+// AI菜谱
+.page {
+    background-color: #f2f2f2;
+    height: calc(100% - 114px);
+    width: 100vw;
+    overflow-y: scroll;
+}
+
+.footer {
+    position: fixed;
+    bottom: 60px;
+    width: 100vw;
+}
+
+.chat-container {
+    padding: 10px;
+
+}
+
+.message-container {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 10px;
+}
+
+.message-container-mine {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 10px;
+    flex-direction: row-reverse;
+}
+
+.avatar-left {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    margin-right: 10px;
+
+}
+
+.avatar-right {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    margin-left: 10px;
+}
+
+.message-box-left {
+    background-color: #fff;
+    border-radius: 10px;
+    padding: 10px;
+    max-width: 70%;
+}
+
+.message-box-right {
+    background-color: #95ec69;
+    border-radius: 10px;
+    padding: 10px;
+    max-width: 70%;
+}
+
+.message-content {
+    word-wrap: break-word;
+}
+
+.send-container {
+    display: flex;
+    align-items: center;
+    padding: 10px;
+    background-color: #f2f2f2;
+
+}
+
+.message-input {
+    flex: 1;
+    padding: 8px;
+    border: none;
+    border-radius: 20px;
+    background-color: #fff;
+}
+
+.send-button {
+    padding: 8px 15px;
+    border: none;
+    border-radius: 20px;
+    background-color: #38c1f5;
+    color: white;
+    font-weight: bold;
+    cursor: pointer;
+    margin-left: 10px;
+}
+
+.navfooter {
+    width: 100%;
+    height: 120px;
+}

+ 21 - 0
app-angular/src/modules/lesson/page-chat/page-chat.component.spec.ts

@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PageChatComponent } from './page-chat.component';
+
+describe('PageChatComponent', () => {
+  let component: PageChatComponent;
+  let fixture: ComponentFixture<PageChatComponent>;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      declarations: [PageChatComponent]
+    });
+    fixture = TestBed.createComponent(PageChatComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 100 - 0
app-angular/src/modules/lesson/page-chat/page-chat.component.ts

@@ -0,0 +1,100 @@
+import { Component } from '@angular/core';
+import { TestChatCompletion, TestChatMessage } from '../class-chat-completion';
+// 引入Parse第三方库
+import * as Parse from "parse"
+
+@Component({
+  selector: 'app-page-chat', // 组件的选择器,用于在模板中引用组件
+  templateUrl: './page-chat.component.html', // 组件的模板文件路径
+  styleUrls: ['./page-chat.component.scss'] // 组件的样式文件路径
+})
+export class PageChatComponent {
+  currentUser = Parse.User.current() // 当前用户对象
+  currentTab: string = 'all'; // 当前选项卡的标识符
+  changeTab(tab: string) {
+    this.currentTab = tab;
+  }
+
+  messageList: Array<TestChatMessage> = [] // 消息列表数组
+  userInput: string = "" // 用户输入的内容
+
+  completion: TestChatCompletion | undefined // 完成对象
+  constructor() {
+    this.ref() // 调用ref方法加载AI定制对话的20条消息
+  }
+
+  // 加载AI定制对话的20条消息
+  async ref() {
+    let query = new Parse.Query('AIPet') // 创建Parse查询对象
+    query.equalTo('user', Parse.User.current()?.id) // 设置查询条件,筛选出当前用户的消息
+    query.descending('createdAt') // 按照创建时间降序排序
+    query.limit(20) // 设置查询结果的数量上限为20条
+    let request = await query.find() // 执行查询
+
+    request.forEach(item => {
+      this.messageList.push({
+        role: "user",
+        content: item.get('userMessage')
+      }) // 将用户消息添加到消息列表中
+      this.messageList.push({
+        role: "assistant",
+        content: item.get('aiMessage')
+      }) // 将AI助手的消息添加到消息列表中
+    })
+  }
+
+  send() {
+    this.messageList.push({
+      role: "user",
+      content: this.userInput
+    }) // 将用户输入的消息添加到消息列表中
+
+    let obj = Parse.Object.extend('AIPet') // 创建Parse对象
+    let AIPet = new obj() // 创建AIRecipe对象
+    AIPet.set('user', {
+      __type: 'Pointer',
+      className: '_User',
+      objectId: Parse.User.current()?.id
+    }) // 设置AIRecipe对象的user字段为当前用户的指针
+    AIPet.set('userMessage', this.userInput) // 设置AIPet对象的userMessage字段为用户输入的消息
+
+    this.completion = new TestChatCompletion(this.messageList) // 创建TestChatCompletion对象,并传入消息列表
+    this.completion?.createCompletionByStream(async (isComplete: Boolean) => {
+      if (isComplete) {
+        let currentBackMsg = this.messageList[this.messageList.length - 1]
+        console.log(currentBackMsg);
+        AIPet.set('aiMessage', currentBackMsg?.content) // 设置AIPet对象的aiMessage字段为AI助手的回复消息
+        await AIPet.save() // 保存AIPet对象到Parse数据库
+        console.log('已保存');
+      }
+    }) // 通过流式API创建完成对象,并传入回调函数
+
+    this.userInput = "" // 清空用户输入
+    console.log(this.messageList);
+  }
+
+  // AI计划
+  planOptions: any = {
+    gender: "未知",
+    targets: []
+  } // 计划选项对象,包含性别和目标属性
+  setOption(key: string, event: any) {
+    this.planOptions[key] = event.detail.value; // 更新计划选项对象的属性值
+  }
+  sendPlan() {
+    let content = `宠物的性别:${this.planOptions.gender},品种,年龄:,体重,
+    信息:${this.planOptions.targets?.join(";")}
+    补充:
+    请扮演符合上述信息既可爱又体贴的宠物陪伴我聊天
+    ` // 构建计划的内容字符串
+    console.log(content)
+    return
+    this.messageList.push({
+      role: "user",
+      content: content
+    }) // 将计划的内容添加到消息列表中
+    this.completion = new TestChatCompletion(this.messageList) // 创建TestChatCompletion对象,并传入消息列表
+    this.completion?.createCompletionByStream() // 通过流式API创建完成对象
+    this.userInput = "" // 清空用户输入
+  }
+}