Browse Source

feat:soul-app ncloud.ts and update: tab2 page use CloudObject

cainiao-hue 3 months ago
parent
commit
e1ca4542b8

+ 4 - 2
docs-prod/schema.md

@@ -27,8 +27,10 @@ class ChatPartner {
 ' Record chat history between users and chat partners
 class ChatRecord {
     + objectId: String //聊天记录唯一标识符
-    + timestamp: Date //聊天时间戳
-    - chatContent: List //聊天内容列表
+    + title:String //聊天标题
+    + timestamp: Date //聊天时间
+    + content:String //聊天内容
+    - chatList: Array //聊天记录
     + user: Pointer<User> //关联的用户对象
     + chatpartner: Pointer<ChatPartner> //关联的聊天机器人对象
     + getChatHistory(): List

+ 5 - 5
soul-app/src/app/tab2/tab2.page.html

@@ -21,15 +21,15 @@
       <ion-card-content>
         <p>以下是一些专业性的的智能心理陪聊师:</p>
         <ion-list>
-          <ion-item *ngFor="let consultant of consultants">
+          <ion-item *ngFor="let chatpartner of chatpartnerList">
             <ion-avatar slot="start">
-              <img [src]="consultant.avatar" alt="{{ consultant.name }}"/>
+              <img [src]="chatpartner.get('avatar') || '/assets/img/2.png'" [alt]="chatpartner.get('name')"/>
             </ion-avatar>
             <ion-label>
-              <h2>{{ consultant.name }}</h2>
-              <p>{{ consultant.expertise.join(', ') }}</p>
+              <h2>{{ chatpartner.get('name') }}</h2>
+              <p>专业领域:{{ chatpartner.get('expertise') }}</p>
             </ion-label>
-            <ion-button slot="end" (click)="clickToConsult(consultant)">开始陪聊</ion-button>
+            <ion-button slot="end" (click)="clickToConsult(chatpartner)">开始陪聊</ion-button>
           </ion-item>
         </ion-list>
       </ion-card-content>

+ 45 - 40
soul-app/src/app/tab2/tab2.page.ts

@@ -5,8 +5,9 @@ import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonIte
    IonList,IonSelect, IonSelectOption } from '@ionic/angular/standalone';
 import { CommonModule } from '@angular/common';
 import { Router } from '@angular/router';
-import { ChatPanelOptions, FmodeChat, openChatPanelModal } from 'fmode-ng';
+import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 import { ModalController } from '@ionic/angular/standalone';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
 
 @Component({
   selector: 'app-tab2',
@@ -29,50 +30,53 @@ export class Tab2Page {
     this.modalCtrl = modalCtrl;
     // 其他构造函数代码
   }
-  consultants = [
-    {
-      name: '小娇陪聊师',
-      bio:"注于焦虑症的李晓慧,通过认知行为疗法帮助来访者重拾内心的平静与自信",
-      avatar: '/assets/img/2.png',
-      expertise: ['专业领域:焦虑']
-    },
-    {
-      name: '小易陪聊师',
-      bio:"致力于抑郁症辅导,运用共情与认知疗法帮助来访者重建积极的生活态度",
-      avatar: '/assets/img/4.png',
-      expertise: ['专业领域:抑郁']
-    },
-    {
-      name: '小雅陪聊师',
-      bio:"专注于压力管理,通过实用技巧帮助来访者提升自我效能,找到生活平衡",
-      avatar: '/assets/img/5.png',
-      expertise: ['专业领域:压力']
-    }
-  ];
-  clickToConsult(consultant:any) {
+  clickToConsult(chatpartner:CloudObject) {
     // 弹窗形式聊天:开始咨询
+    localStorage.setItem("company","E4KpGvTEto")
+    let consult = new CloudObject("ChatRecord")
+    let now = new Date();
+    let dateStr = `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()}`
+    consult.set({
+      title:`${chatpartner.get('expertise') || ""}聊天记录${dateStr}-${chatpartner.get('name')}`,
+      chatpartner:chatpartner.toPointer(),
+    })
     let options:ChatPanelOptions = {
       roleId:"2DXJkRsjXK",
       onChatInit: (chat: FmodeChat) => {
         console.log("onChatInit");
         console.log("预设角色", chat.role);
-        chat.role.set("name", consultant.name);
-        chat.role.set("bio",consultant.bio);
-        chat.role.set("expertise", consultant.expertise);
-        chat.role.set("avatar", consultant.avatar)
+        chat.role.set("name", chatpartner.get("name"));
+        chat.role.set("bio",chatpartner.get("bio"));
+        chat.role.set("expertise", chatpartner.get("expertise"));
+        chat.role.set("avatar", chatpartner.get("avatar") || "/assets/img/2.png")
         chat.role.set("prompt", `
         # 角色设定
-        您是${consultant.name},一位${consultant.title},${consultant.desc},需要为学生提供陪伴和支持等积极情绪。
-`);
+        您是${chatpartner.get("name")},一位${chatpartner.get("bio")},${chatpartner.get("expertise")},需要为用户提供陪伴和支持等积极情绪。
+        # 对话环节
+        用户提及到关于不想聊了、不想说了、有事先走了类似的话语,耐心积极开导回复结束后请在消息结尾附带: [聊天结束]
+        # 开始话语
+        当您准备好了,可以以一个关心用户的朋友的身份,向来访的用户打招呼。
+        `);
+      },
+      onMessage:(chat:FmodeChat,message:FmodeChatMessage)=>{
+        console.log("onMessage",message)
+        let content:any = message?.content
+        if(typeof content == "string"){
+          if(content?.indexOf("[聊天结束]")>-1){
+            console.log("聊天结束")
+            consult.set({
+              content:content
+            })
+            consult.save();
+          }
+        }
       },
-      onChatSaved:(chat:FmodeChat)=>{
         // chat?.chatSession?.id 本次会话的 chatId
+        onChatSaved:(chat:FmodeChat)=>{
         console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id)
-      },
+      }
     }
     openChatPanelModal(this.modalCtrl,options)
-    // 页面形式聊天:开始咨询
-    // this.router.navigateByUrl("/chat/session/role/2DXJkRsjXK")
   }
   //selectedIssue:string='';
 
@@ -98,15 +102,16 @@ export class Tab2Page {
 
   matchCounselor() {
   }
-  putong = [
-    {
-      name: '小娇陪聊师',
-      bio:"注于焦虑症的李晓慧,通过认知行为疗法帮助来访者重拾内心的平静与自信",
-      avatar: '/assets/img/2.png',
-      expertise: ['专业领域:焦虑']
-    },
-  ]
   goChat(){
       this.router.navigateByUrl("/chat/session/role/2DXJkRsjXK")
+  } 
+  ngOnInit() {
+    this.loadChatPartnerList()
+  }
+
+  chatpartnerList:Array<CloudObject>=[]
+  async loadChatPartnerList(){
+    let query = new CloudQuery("ChatPartner");
+    this.chatpartnerList = await query.find()
   }
 }

+ 221 - 0
soul-app/src/lib/ncloud.ts

@@ -0,0 +1,221 @@
+// CloudObject.ts
+export class CloudObject {
+    id: string | null = null; // 编号
+    className: string; // 名称
+    createdAt:any;
+    updatedAt:any;
+    data: Record<string, any> = {}; // 属性、内容
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    // 作用是将查询参数转换为对象
+    include(...fileds:string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        this.queryParams["where"][key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        // return json || {};
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+
+    }
+
+    async find() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key=>{
+            let paramStr = JSON.stringify(this.queryParams[key]); // 作用是将对象转换为JSON字符串
+            if(key=="include"){
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if(key=="where"){
+                paramStr = JSON.stringify(this.queryParams[key]);
+
+            }
+            if(queryStr) {
+                url += `${key}=${paramStr}`;
+            }else{
+                url += `&${key}=${paramStr}`;
+            }
+        })
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item:any)=>this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+         if (exists) {
+             let existsObject = this.dataToObj(exists)
+             return existsObject;
+         }
+         return null
+        //let list = json?.results || []
+        //let objList = list.map((item:any)=>this.dataToObj(item))
+        //return objList || [];
+    }
+
+    dataToObj(exists:any):CloudObject{
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}

+ 0 - 16
soul-server/lib/ncloud.js

@@ -19,22 +19,6 @@ class CloudObject{
     get(key){
         return this.data[key] || null
     }
-    greaterThan(key,value){
-      if(!this.whereOptions[key]) this.whereOptions[key] = {}
-      this.whereOptions[key]["$gt"] = value
-    }
-    greaterThanAndEqualTo(key,value){
-        if(!this.whereOptions[key]) this.whereOptions[key] = {}
-        this.whereOptions[key]["$gte"] = value
-    }
-    lessThan(key,value){
-        if(!this.whereOptions[key]) this.whereOptions[key] = {}
-        this.whereOptions[key]["$lt"] = value
-    }
-    lessThanAndEqualTo(key,value){
-        if(!this.whereOptions[key]) this.whereOptions[key] = {}
-        this.whereOptions[key]["$lte"] = value
-    }
     // 更新
     async save(){
         let method = "POST"