Browse Source

update:问诊页面

xukang 4 months ago
parent
commit
e6121fcabd

+ 19 - 0
TFPower-app/src/app/tab2/agent/agent-user-input/agent-user-input.component.html

@@ -0,0 +1,19 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-button color="medium" (click)="cancel()">取消</ion-button>
+    </ion-buttons>
+    <ion-title>用户输入</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="confirm()" [strong]="true">确认</ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+<ion-content class="ion-padding">
+  @for(field of fieldsArray;track field.name){
+  <ion-item>
+    <ion-input labelPlacement="stacked" label="请输入 {{field.name}}" [(ngModel)]="inputData[field.name]"
+      placeholder="{{field.desc}}"></ion-input>
+  </ion-item>
+  }
+</ion-content>

+ 0 - 0
TFPower-app/src/app/tab2/agent/agent-user-input/agent-user-input.component.scss


+ 24 - 0
TFPower-app/src/app/tab2/agent/agent-user-input/agent-user-input.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { AgentUserInputComponent } from './agent-user-input.component';
+
+describe('AgentUserInputComponent', () => {
+  let component: AgentUserInputComponent;
+  let fixture: ComponentFixture<AgentUserInputComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      declarations: [AgentUserInputComponent],
+      imports: [IonicModule.forRoot()]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AgentUserInputComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 38 - 0
TFPower-app/src/app/tab2/agent/agent-user-input/agent-user-input.component.ts

@@ -0,0 +1,38 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { ModalController, IonHeader, IonContent, IonInput, IonToolbar, IonItem, IonButtons, IonButton, IonTitle } from '@ionic/angular/standalone';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+export interface AgentUserInputField {
+  name: string, // 字段名称
+  type: string, // 输入类型
+  desc: string, // 字段说明
+}
+@Component({
+  selector: 'agent-user-input',
+  templateUrl: './agent-user-input.component.html',
+  styleUrls: ['./agent-user-input.component.scss'],
+  standalone: true,
+  imports: [
+    IonHeader, IonContent, IonInput, IonToolbar, IonItem, IonButtons, IonButton,
+    IonTitle,
+    FormsModule, ReactiveFormsModule,
+  ]
+})
+export class AgentUserInputComponent implements OnInit {
+
+  @Input()
+  fieldsArray: AgentUserInputField[] = []
+
+  @Input()
+  inputData: any = {}
+
+  constructor(private modalCtrl: ModalController) { }
+  ngOnInit() {
+  }
+  cancel() {
+    return this.modalCtrl.dismiss(null, 'cancel');
+  }
+
+  confirm() {
+    return this.modalCtrl.dismiss(this.inputData, 'confirm');
+  }
+}

+ 23 - 0
TFPower-app/src/app/tab2/agent/agent.input.ts

@@ -0,0 +1,23 @@
+import { ModalController } from '@ionic/angular/standalone';
+import { AgentUserInputComponent } from './agent-user-input/agent-user-input.component';
+
+/**
+ * 数据搜集Modal
+ * @description
+ * https://ionicframework.com/docs/api/modal#controller-modals
+ */
+export async function getUserInput(modalCtrl: ModalController, options: { fieldsArray: any }) {
+  const modal = await modalCtrl.create({
+    component: AgentUserInputComponent,
+    componentProps: {
+      fieldsArray: options.fieldsArray
+    }
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+}

+ 44 - 0
TFPower-app/src/app/tab2/agent/agent.json.ts

@@ -0,0 +1,44 @@
+/**
+ * 
+ 结果是:
+ {
+     "keshi":"呼吸科",
+     "user":{
+         "name":""
+     }
+ }
+ */
+export function extactAndParseJsonFromString(inputString: string) {
+    let startIndex = inputString.indexOf("{")
+    if (startIndex == -1) return {}
+
+    let count = 0;
+    let endIndex = startIndex;
+
+    // 遍历字符串,计算花括号实现数量平衡
+    for (let i = startIndex; i < inputString.length; i++) {
+        // console.log(i,inputString[i])
+        if (inputString[i] === "{") {
+            count++;
+        } else if (inputString[i] === "}") {
+            count--;
+        }
+        // 当花括号平衡,我们找到了完整的JSON
+        if (count === 0) {
+            endIndex = i;
+            break;
+        }
+    }
+    // 若不平衡
+    if (count != 0) return {};
+
+    // 提取JSON字符串,并解析
+    const jsonString = inputString.slice(startIndex, endIndex + 1);
+    console.log("jsonString", jsonString)
+    try {
+        return JSON.parse(jsonString);
+    } catch (error) {
+        console.error("Failed to parse JSON", error);
+        return {}
+    }
+}

+ 21 - 0
TFPower-app/src/app/tab2/agent/agent.start.ts

@@ -0,0 +1,21 @@
+import { AgentTaskStep } from "./agent.task";
+
+/**
+ * 任务执行函数
+ */
+export async function startTask(taskStepList: AgentTaskStep[]) {
+
+    for (let index = 0; index < taskStepList.length; index++) {
+        let step = taskStepList[index];
+        let result = await step.handle()
+        if (result == false) { break; }
+
+        if (step.error) {
+            break;
+        }
+        if (!step.error) { // 任务执行正常,无Error
+
+        }
+    }
+
+}

+ 81 - 0
TFPower-app/src/app/tab2/agent/agent.task.ts

@@ -0,0 +1,81 @@
+
+// 任务步骤初始化参数
+interface AgentTaskStepOptions {
+    title: string
+    shareData?: any
+    handle?(): { (): Promise<any> }
+}
+
+export class AgentTaskStep {
+    // 任务名称
+    title: string = ""
+
+    // 任务数据 共享数据(整个任务集共享传递的数据)
+    shareData: any = {}
+    // 任务数据 私有数据(当前任务私有的处理数据)
+    data: any = {}
+
+    // 父级组件:任务集组件
+    // @example 当前子任务未完成,且当前任务是必须时,需要在当前任务中,终止整个程序
+    parentComp: any | undefined
+    // 当前任务在任务集数组中的项目
+    parentIndex: number | undefined
+
+    /**
+     * 任务生命周期
+     * @constructor // 构造时执行一次
+     * @onInit // 初始化后执行一次
+     * @onProgress // 进度变化时执行一次
+     * @onComplete // 完成前执行一次
+     */
+    constructor(metaData: AgentTaskStepOptions) {
+        this.title = metaData.title
+        this.shareData = metaData.shareData
+        if (metaData.handle) {
+            this.handle = metaData.handle
+        }
+        this.init(metaData)
+    }
+    onInit(step: AgentTaskStep) {
+        this.isInit = true;
+    }
+    beforeHandle(step: AgentTaskStep) {
+
+    }
+    afterHandle(step: AgentTaskStep) {
+
+    }
+    onProgress(step: AgentTaskStep) {
+
+    }
+    onComplete(step: AgentTaskStep) {
+
+    }
+    onError(step: AgentTaskStep) {
+
+    }
+
+    /**
+     * 任务初始化函数
+     */
+    async init(metaData: AgentTaskStepOptions) {
+    }
+    isInit: boolean = false; // 是否初始化
+
+    /** 
+     * 任务执行函数
+     * @desc 函数内必须有明确的完成、结束情形,函数内必须有进度的变化指示 progress从0.00 - 1.00
+     */
+    progress: number = 0;
+    handle(): Promise<any> | any {
+        this.beforeHandle(this)
+        this.onComplete(this)
+        this.onError(this)
+        return true
+    }
+
+    /**
+     * 任务错误提示
+     */
+    error: string = "" // 错误原因
+}

+ 67 - 0
TFPower-app/src/app/tab2/agent/tasks/poem/inquiry/1.inquiry-user-story.ts

@@ -0,0 +1,67 @@
+import { AgentTaskStep } from 'src/app/tab2/agent/agent.task';
+import { getUserInput } from 'src/app/tab2/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from 'src/app/tab2/agent/agent.json';
+
+export function TaskInqueryUserStory(options: {
+    modalCtrl: ModalController
+    shareData: any
+}
+): AgentTaskStep {
+    let task1 = new AgentTaskStep({ title: "根据部位判断是什么引起不适", shareData: options.shareData })
+    task1.handle = () => {
+        return new Promise(async (resolve, reject) => {
+            // 获取用户输入的诗词
+            let userInput = await getUserInput(options.modalCtrl, {
+                fieldsArray: [
+                    { name: "症状口述", type: "text", desc: "描述下感觉到的症状和不适的部位。" }
+                ]
+            });
+
+            if (!userInput?.['症状口述']) {
+                task1.error = "无症状口述,无法判断科室"
+                resolve(false);
+            }
+
+            // 文本生成
+            let PromptTemplate = `您是一名专业的健身医疗恢复专家,在健身房保健室负责导诊服务,请您根据患者的症状口述,判断其造成原因。
+                患者症状口述:${userInput['症状口述']}
+                结果以JSON格式表示:
+                症状名称为具体的症状,症状描述为用户的感受,持续时间若没有直接说,可以写近期即可。
+                {
+                    "reason":"运动项目",
+                    "sympList":[
+                        {"title":"症状名称","desc":"症状描述","duration":"持续时间"}
+                    ]
+                }
+                `
+            let completion = new FmodeChatCompletion([
+                { role: "system", content: "" },
+                { role: "user", content: PromptTemplate }
+            ])
+            completion.sendCompletion().subscribe((message: any) => {
+                if (task1.progress < 0.5) {
+                    task1.progress += 0.1
+                }
+                if (task1.progress >= 0.5 && task1.progress <= 0.9) {
+                    task1.progress += 0.01
+                }
+                if (task1.progress >= 0.9) {
+                    task1.progress += 0.001
+                }
+                // 打印消息体
+                console.log(message.content)
+                // 赋值消息内容给组件内属性
+                if (message.complete) { // 判断message为完成状态,则设置isComplete为完成
+                    options.shareData.userStory = extactAndParseJsonFromString(message.content)
+                    options.shareData.userStory['症状口述'] = userInput['症状口述']
+                    task1.progress = 1
+                    resolve(true)
+                }
+            })
+        })
+
+    }
+    return task1
+}

+ 80 - 0
TFPower-app/src/app/tab2/agent/tasks/poem/inquiry/2.inquiry-doctor-question.ts

@@ -0,0 +1,80 @@
+import { AgentTaskStep } from 'src/app/tab2/agent/agent.task';
+import { getUserInput } from 'src/app/tab2/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from 'src/app/tab2/agent/agent.json';
+
+export function TaskInqueryDoctorQuestion(options: {
+    modalCtrl: ModalController
+    shareData: any
+}
+): AgentTaskStep {
+    /**
+     shareData.userStory // 已经拥有的用户描述数据
+     {
+              "keshi": "神经内科",
+              "sympList": [
+                  {
+                      "title": "偏头痛",
+                      "desc": "持续了2天的偏头疼",
+                      "duration": "2天"
+                  },
+                  {
+                      "title": "发冷",
+                      "desc": "感觉发冷,已经有一天",
+                      "duration": "1天"
+                  }
+              ]
+          }
+     */
+    let task1 = new AgentTaskStep({ title: "问诊:医生根据患者情况,主动询问", shareData: options.shareData })
+    task1.handle = () => {
+        return new Promise(async (resolve, reject) => {
+            // 获取用户输入
+            // let userInput = await getUserInput(options.modalCtrl,{fieldsArray:[
+            //     {name:"症状口述",type:"text",desc:"描述下感觉到的症状或不适。"}
+            // ]});
+
+            // if(!userInput?.['症状口述']){
+            //     task1.error = "无症状口述,无法判断科室"
+            //     resolve(false);
+            // }
+
+            // 文本生成
+            let PromptTemplate = `您是一名专业的健身医疗主任医生,患者简单描述了一下他的情况,请您思考下还有哪些需要询问(问题要准确,不超过3个问题)。
+                症状口述:${options.shareData.userStory['症状口述']}
+                结果以JSON格式表示:
+                {
+                    "questionList":[
+                        {"title":"问题标题","desc":"问题描述"}
+                    ]
+                }
+                `
+            let completion = new FmodeChatCompletion([
+                { role: "system", content: "" },
+                { role: "user", content: PromptTemplate }
+            ])
+            completion.sendCompletion().subscribe((message: any) => {
+                if (task1.progress < 0.5) {
+                    task1.progress += 0.1
+                }
+                if (task1.progress >= 0.5 && task1.progress <= 0.9) {
+                    task1.progress += 0.01
+                }
+                if (task1.progress >= 0.7) {
+                    task1.progress += 0.001
+                }
+                // 打印消息体
+                console.log(message.content)
+                // 赋值消息内容给组件内属性
+                if (message.complete) { // 判断message为完成状态,则设置isComplete为完成
+                    options.shareData.userStory.questionList = extactAndParseJsonFromString(message.content).questionList || []
+                    task1.progress = 1
+                    resolve(true)
+                }
+            })
+        })
+
+    }
+    return task1
+}

+ 143 - 0
TFPower-app/src/app/tab2/agent/tasks/poem/inquiry/3.inquiry-user-answer.ts

@@ -0,0 +1,143 @@
+import { AgentTaskStep } from 'src/app/tab2/agent/agent.task';
+import { getUserInput } from 'src/app/tab2/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from 'src/app/tab2/agent/agent.json';
+
+export function TaskInqueryUserAnswer(options: {
+    modalCtrl: ModalController
+    shareData: any
+}
+): AgentTaskStep {
+    /**
+     shareData.userStory // 已经拥有的医生主动询问
+     {
+        "questionList": [
+            {
+                "title": "头痛的性质",
+                "desc": "请问您的偏头痛是怎样的感觉?是刺痛、跳动还是压迫感?"
+            },
+        ]
+    }
+     */
+    let task1 = new AgentTaskStep({ title: "诊断:患者回答具体内容后,医生给出诊断结果", shareData: options.shareData })
+    task1.handle = () => {
+        return new Promise(async (resolve, reject) => {
+            // 获取用户输入的问题清单
+            let questionList = options.shareData.userStory.questionList.map((item: any) => { return { name: item.title, desc: item.desc, type: "text" } })
+
+            let userInputResult = await getUserInput(options.modalCtrl, { fieldsArray: questionList });
+            console.log(userInputResult)
+            //正好是他的标题
+            questionList.forEach((question: any) => {
+                question.answer = userInputResult[question.name]
+            })
+
+            // 文本生成
+            let qaContent = options.shareData.userStory.questionList.map((item: any) => `医生问:${item.title},${item.desc}\n患者答:${item.answer || "没回答"}`).join("\n")
+            let PromptTemplate = `您是一名专业的健身医疗主任医生,根据具体的询问,给出初步诊断。
+                症状口述:${options.shareData.userStory['症状口述']}
+                具体询问:${qaContent}
+                结果以JSON格式表示:
+                症状名称为具体的症状,症状描述为用户的感受,持续时间若没有直接说,可以写近期即可。
+                {
+                    "title":"病例标题",
+                    "desc":"病情概括",
+                    "content":"给出完整的治疗方案和建议"
+                }
+                `
+            let completion = new FmodeChatCompletion([
+                { role: "system", content: "" },
+                { role: "user", content: PromptTemplate }
+            ])
+            completion.sendCompletion().subscribe((message: any) => {
+                if (task1.progress < 0.5) {
+                    task1.progress += 0.1
+                }
+                if (task1.progress >= 0.5 && task1.progress <= 0.9) {
+                    task1.progress += 0.01
+                }
+                if (task1.progress >= 0.9) {
+                    task1.progress += 0.001
+                }
+                // 打印消息体
+                console.log(message.content)
+                // 赋值消息内容给组件内属性
+                if (message.complete) { // 判断message为完成状态,则设置isComplete为完成
+                    options.shareData.diagResult = extactAndParseJsonFromString(message.content)
+                    task1.progress = 1
+                    resolve(true)
+                }
+            })
+        })
+
+    }
+    return task1
+}
+
+
+const TestShareData = {
+    userStory: {
+        "keshi": "神经内科",
+        "sympList": [
+            {
+                "title": "偏头疼",
+                "desc": "已经持续了2天了",
+                "duration": "2天"
+            },
+            {
+                "title": "发冷",
+                "desc": "感觉已经有一天",
+                "duration": "1天"
+            }
+        ],
+        "症状口述": "偏头疼已经持续了2天了,发冷感觉已经有一天。",
+        "questionList": [
+            {
+                "title": "头痛的性质",
+                "desc": "请描述您的头痛是搏动性、压迫性还是其他类型?",
+                "answer": "压迫性头疼"
+            },
+            {
+                "title": "头痛的部位",
+                "desc": "您的头痛主要集中在头部的哪个区域?",
+                "answer": "左侧头疼"
+            },
+            {
+                "title": "伴随症状",
+                "desc": "除了头痛和发冷,您还有其他的症状吗?例如恶心、呕吐、视力模糊或对光敏感等?",
+                "answer": "有点恶心但是没有呕吐"
+            },
+            {
+                "title": "发冷的性质",
+                "desc": "您感到发冷时是否伴随有发热、出汗或其他症状?",
+                "answer": "没有"
+            },
+            {
+                "title": "既往病史",
+                "desc": "您是否有偏头痛或其他头痛的病史?",
+                "answer": "没有"
+            },
+            {
+                "title": "生活习惯",
+                "desc": "您最近的生活习惯是否有变化?例如睡眠不足、压力增大或饮食不规律?",
+                "answer": "经常熬夜,学习压力大"
+            },
+            {
+                "title": "药物使用",
+                "desc": "您是否有服用任何药物来缓解头痛或其他症状?",
+                "answer": "没有"
+            },
+            {
+                "title": "家族病史",
+                "desc": "您的家族中是否有人有类似的头痛或神经系统疾病史?",
+                "answer": "没有"
+            },
+            {
+                "title": "过敏史",
+                "desc": "您是否有药物或其他物质的过敏史?",
+                "answer": "没有"
+            }
+        ]
+    }
+}

+ 55 - 0
TFPower-app/src/app/tab2/agent/tasks/poem/poem-desc.ts

@@ -0,0 +1,55 @@
+import { AgentTaskStep } from 'src/app/tab2/agent/agent.task';
+
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { getUserInput } from '../../agent.input';
+
+export function TaskPoemPictureDesc(options: {
+    modalCtrl: ModalController
+    shareData: any
+}
+): AgentTaskStep {
+    let task1 = new AgentTaskStep({ title: "输入动作需求", shareData: options.shareData })
+    task1.handle = () => {
+        return new Promise(async (resolve, reject) => {
+            // 获取用户输入的诗词
+            let userInput = await getUserInput(options.modalCtrl, {
+                fieldsArray: [
+                    { name: "目标部位", type: "text", desc: "请输入目标锻炼的肌肉部位" },
+                    { name: "设备限制", type: "text", desc: "是否需要特定设备限制(如徒手、哑铃等)" }
+                ]
+            });
+            if (!userInput?.['目标部位']) {
+                task1.error = "缺少目标部位,请重新开始"
+                resolve(false);
+            }
+
+            // 文本生成
+            let PromptTemplate = `您是一名专业的健身教练,请根据以下需求生成一个详细的健身动作画面,包括用箭头标记动作细节并描述(用中文描述)等。
+                描述如下:
+                ${userInput['目标部位']}${userInput['设备限制']},请确保动作正确。
+                `
+            let completion = new FmodeChatCompletion([
+                { role: "system", content: "" },
+                { role: "user", content: PromptTemplate }
+            ])
+            completion.sendCompletion().subscribe((message: any) => {
+                if (task1.progress < 0.5) {
+                    task1.progress = Math.min(task1.progress + 0.02, 1); // 保证不超过1
+                } else if (task1.progress < 1) {
+                    task1.progress = Math.min(task1.progress + 0.004, 1); // 保证不超过1
+                }
+                // 打印消息体
+                // console.log(message.content)
+                // 赋值消息内容给组件内属性
+                options.shareData.PictureDescResult = message.content
+                if (message.complete) { // 判断message为完成状态,则设置isComplete为完成
+                    task1.progress = 1
+                    resolve(true)
+                }
+            })
+        })
+
+    }
+    return task1
+}

+ 46 - 0
TFPower-app/src/app/tab2/agent/tasks/poem/poem-picture.ts

@@ -0,0 +1,46 @@
+import { ModalController } from '@ionic/angular/standalone';
+import { AgentTaskStep } from '../../agent.task';
+import { ImagineWork, DalleOptions } from 'fmode-ng';
+import { getUserInput } from '../../agent.input';
+
+
+export function TaskPoemPictureCreate(options: {
+  modalCtrl: ModalController
+  shareData: any
+}): AgentTaskStep {
+  let task2 = new AgentTaskStep({ title: "生成动作图像", shareData: options.shareData })
+  task2.handle = () => {
+    return new Promise(resolve => {
+      // let userInput = await getUserInput(options.modalCtrl, {
+      //   fieldsArray: [
+      //     { name: "绘图要求", type: "text", desc: "画风、构图等等" }
+      //   ]
+      // });
+      // console.log(userInput)
+      // console.log("意境绘制:执行过程")
+
+      if (!options.shareData.PictureDescResult) {
+        task2.error = "缺少古风描述结果,请重新执行。"
+        resolve(false)
+      }
+      let imagineWork = new ImagineWork();
+      let PicturePrompt = `${options.shareData.PictureDescResult}\n特点:动作标准清晰易懂。`
+      let imgOptions: DalleOptions = { prompt: PicturePrompt }
+      let countDownInt = setInterval(() => {
+        task2.progress += 0.01
+      }, 500)
+      imagineWork?.draw(imgOptions).subscribe((work: any) => {
+        console.log("imagineWork", work?.toJSON())
+        console.log("images", work?.get("images"))
+        if (work?.get("images")?.length) {
+          options.shareData.images = work?.get("images");
+          clearInterval(countDownInt);
+          task2.progress = 1
+          resolve(true)
+        }
+      })
+    })
+
+  }
+  return task2
+}

+ 45 - 6
TFPower-app/src/app/tab2/tab2.page.html

@@ -81,13 +81,52 @@
     <p>有任何不适,可以随时询问!</p>
 
     <!-- 动作纠正按钮 -->
-    <ion-button expand="full" color="primary" (click)="goToPage('correct-movement')">
-      <ion-icon slot="start" name="medkit"></ion-icon> 动作纠正
-    </ion-button>
+    <ion-button (click)="doPoemTask()">生成健身动作</ion-button>
+    <ion-button (click)="doInqueryTask()">疼?点这里!</ion-button>
+    <div>
+      @for(step of taskList;track step.title;)
+      {
+      <div>
+        <!-- 待开始 -->
+        @if(step.progress==0 && !step.error){
+        <ion-icon name="radio-button-off-outline"></ion-icon>
+        }
+        <!-- 进行中 -->
+        @if(step.progress!=0 && step.progress!=1){
+        <ion-icon name="reload-outline"></ion-icon>
+        }
+        <!-- 已完成 -->
+        @if(step.progress==1){
+        <ion-icon name="checkmark-circle-outline"></ion-icon>
+        }
+        <!-- 已出错 -->
+        @if(step.error){
+        <ion-icon name="close-circle-outline"></ion-icon>
+        }
+        {{step.title}} @if(step.progress){<span>{{step.progress * 100 | number:"2.0-0"}}%</span>}
+
+        @if(step.error){
+        <span style="color:red;">{{step.error}}</span>
+        }
+      </div>
+      }
+    </div>
+    @if(shareData.images) {
+    @for(imageUrl of shareData.images;track imageUrl){
+    <img [src]="imageUrl" alt="" srcset="">
+    }
+    }
+    @if(shareData.diagResult){
+    <h1>{{shareData.diagResult.title}}</h1>
+    <h2>{{shareData.diagResult.desc}}</h2>
+    <p>{{shareData.diagResult.content}}</p>
+    }
 
     <!-- AI教练按钮 -->
-    <ion-button expand="full" color="secondary" (click)="goToPage('ai-coach')">
-      <ion-icon slot="start" name="school"></ion-icon> 健身教练
-    </ion-button>
+    <h1>组件:直接弹出的聊天组件</h1>
+    <ion-button (click)="openChat()">开始新聊天</ion-button>
+    <ion-button (click)="restoreChat('yHEHqMQDNv')">恢复会话</ion-button>
+    <h1>示例:门诊问诊的智能体示例(ChatPanel组件)</h1>
+    <ion-button (click)="openInquiry()">进入门诊</ion-button>
   </div>
 </ion-content>

+ 149 - 3
TFPower-app/src/app/tab2/tab2.page.ts

@@ -4,8 +4,15 @@ import { addIcons } from 'ionicons';
 import { checkmarkCircle, calendar, helpCircle } from 'ionicons/icons';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { IonButtons, IonItem, IonList, IonHeader, IonIcon, IonToolbar, IonContent, IonSegment, IonSegmentButton, IonGrid, IonRow, IonCol, IonButton, IonLabel, IonBadge, IonInput } from '@ionic/angular/standalone';
-
+import { IonButtons, IonItem, IonList, IonHeader, IonIcon, IonToolbar, IonContent, IonSegment, IonSegmentButton, IonGrid, IonRow, IonCol, IonButton, IonLabel, IonBadge, IonInput, ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion, ImagineWork, DalleOptions, ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from "fmode-ng";
+import { AgentTaskStep } from './agent/agent.task';
+import { TaskPoemPictureDesc } from './agent/tasks/poem/poem-desc';
+import { TaskPoemPictureCreate } from './agent/tasks/poem/poem-picture';
+import { startTask } from './agent/agent.start';
+import { TaskInqueryUserStory } from './agent/tasks/poem/inquiry/1.inquiry-user-story';
+import { TaskInqueryDoctorQuestion } from './agent/tasks/poem/inquiry/2.inquiry-doctor-question';
+import { TaskInqueryUserAnswer } from './agent/tasks/poem/inquiry/3.inquiry-user-answer';
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
@@ -42,7 +49,7 @@ export class Tab2Page implements OnInit {
     { date: '周五', bodyPart: '腹部', tasks: ['仰卧起坐', '卷腹', 'Russian twist'], completed: false }
   ];
 
-  constructor(private router: Router, private cdr: ChangeDetectorRef) {
+  constructor(private router: Router, private modalCtrl: ModalController, private cdr: ChangeDetectorRef) {
     addIcons({ checkmarkCircle, calendar, helpCircle });
   }
 
@@ -64,6 +71,145 @@ export class Tab2Page implements OnInit {
       this.weekPlan.splice(index, 1);
     }
   }
+  //问诊区域
+
+  taskList: AgentTaskStep[] = []
+  //一个等待一秒的函数  每经过一秒
+  wait(duration: number = 1000) {
+    return new Promise(resolve => {
+      setTimeout(() => {
+        resolve(true)
+      }, duration);
+    })
+  }
+
+  shareData: any = {}
+
+  // 任务:完成故事意境描述及图像绘制
+  doPoemTask() {
+    let task1 = TaskPoemPictureDesc({ shareData: this.shareData, modalCtrl: this.modalCtrl });
+    let task2 = TaskPoemPictureCreate({ shareData: this.shareData, modalCtrl: this.modalCtrl });
+    let PoemTaskList = [task1, task2]
+    this.taskList = PoemTaskList
+    startTask(PoemTaskList)
+  }
+
+  doInqueryTask() {
+    let task1 = TaskInqueryUserStory({ shareData: this.shareData, modalCtrl: this.modalCtrl });
+    let task2 = TaskInqueryDoctorQuestion({ shareData: this.shareData, modalCtrl: this.modalCtrl });
+    let task3 = TaskInqueryUserAnswer({ shareData: this.shareData, modalCtrl: this.modalCtrl });
+
+    // 定义任务集
+    let InquireServiceTaskList = [
+      task1, task2, task3
+    ]
+    // 传递给显示组件
+    this.taskList = InquireServiceTaskList
+    // 开始执行任务
+    startTask(InquireServiceTaskList)
+  }
+  // 聊天页面
+  openInquiry() {
+    localStorage.setItem("company", "E4KpGvTEto")
+    let options: ChatPanelOptions = {
+      roleId: "2DXJkRsjXK",
+      onChatInit: (chat: FmodeChat) => {
+        console.log("onChatInit");
+        console.log("预设角色", chat.role);
+        chat.role.set("name", "晓晓");
+        chat.role.set("title", "全科医生");
+        chat.role.set("desc", "一名亲切和蔼的门诊全科主任医生,晓晓,年龄36岁");
+        chat.role.set("tags", ["全科", "门诊"]);
+        chat.role.set("avatar", "https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png")
+        chat.role.set("prompt", `
+# 角色设定
+您是一名亲切和蔼的专业的全科医生,晓晓,年龄36岁,需要完成一次完整的门诊服务。
+
+# 对话环节
+0.导诊(根据用户基本情况,引导挂号合适的科室)
+1.预设的问询方式(感冒问呼吸、肚子疼叩诊)
+- 打招呼,以用户自述为主
+- 当信息充足时候,确认用户症状对应的科室,并进入下一个环节
+2.拓展的问询细节
+例如:用户反映呼吸不畅,拓展出:是否咳嗽;是否感觉痛或者痒等其他需要的问题。
+- 当问询细节补充完成后进入下一个环节
+3.初步的诊断结果,并且同时列出检查检验项目
+初步诊断:确定需要有哪些进一步检查
+检查检验:获取医学客观数据
+- 等待用户提交客观数据,进入下一阶段
+4.给出诊断方案并给出处方
+- 完成处方时,请在消息结尾附带: [完成]
+
+# 开始话语
+当您准备好了,可以以一个医生的身份,向来访的用户打招呼。`);
+      },
+      onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
+        console.log("onMessage", message)
+        let content: any = message?.content
+        if (typeof content == "string") {
+          if (content?.indexOf("[完成]") > -1) {
+            console.log("门诊已完成")
+          }
+        }
+      },
+      onChatSaved: (chat: FmodeChat) => {
+        // chat?.chatSession?.id 本次会话的 chatId
+        console.log("onChatSaved", chat, chat?.chatSession, chat?.chatSession?.id)
+      }
+    }
+    openChatPanelModal(this.modalCtrl, options)
+  }
+
+  openChat() {
+    let options: ChatPanelOptions = {
+      roleId: "2DXJkRsjXK",
+      onChatSaved: (chat: FmodeChat) => {
+        // chat?.chatSession?.id 本次会话的 chatId
+
+        console.log("onChatSaved", chat, chat?.chatSession, chat?.chatSession?.id)
+
+      },
+
+    }
+    openChatPanelModal(this.modalCtrl, options)
+  }
+
+  restoreChat(chatId: string) {
+    let options: ChatPanelOptions = {
+      roleId: "2DXJkRsjXK",
+      chatId: chatId
+    }
+    openChatPanelModal(this.modalCtrl, options)
+  }
+
+  goChat() {
+    this.router.navigateByUrl("/chat/session/role/2DXJkRsjXK")
+  }
+
+
+  // audioModalHeightPoint:number = 0.35;
+  // async startTalk(){
+  //   // 根据手机兼容性,适配组件弹出高度
+  //   let height = document.body.clientHeight || 960;
+  //   this.audioModalHeightPoint = Number((165/height).toFixed(2));
+
+  //   // 弹出组件
+  //   let modal:any
+  //   let chat:any
+  //   modal = await this.modalCtrl.create({
+  //     component:ModalAudioMessageComponent,
+  //     componentProps:{
+  //       chat:chat,
+  //       modal:modal,
+  //       onBreakPointSet:()=>{
+  //         modal?.setCurrentBreakpoint(this.audioModalHeightPoint)
+  //       }
+  //     }
+  //   })
+  //   modal.present();
+  // }
+
+
 
   // 页面跳转功能
   goToPage(page: string) {