Переглянути джерело

refactor:finish the first taskList

s202226701053 6 місяців тому
батько
коміт
c6e6a13c5a

+ 6 - 0
E-Cover-app/src/agent/README.md

@@ -0,0 +1,6 @@
+
+
+# 技术设计
+- AgentTaskStep类 智能体任务的某一个步骤(简单任务)
+- TaskExecutor类 任务执行器
+- AgentTaskComponent类 展示任务进行进度的面板

+ 18 - 0
E-Cover-app/src/agent/agent-user-input/agent-user-input.component.html

@@ -0,0 +1,18 @@
+<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
E-Cover-app/src/agent/agent-user-input/agent-user-input.component.scss


+ 22 - 0
E-Cover-app/src/agent/agent-user-input/agent-user-input.component.spec.ts

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

+ 38 - 0
E-Cover-app/src/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
E-Cover-app/src/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
E-Cover-app/src/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
E-Cover-app/src/agent/agent.start.ts

@@ -0,0 +1,21 @@
+import { AgentTaskStep } from "./agent.task";
+
+/**
+ * 任务执行函数
+ */
+export async function TaskExecutor(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
+
+    }
+}
+
+}

+ 94 - 0
E-Cover-app/src/agent/agent.task.ts

@@ -0,0 +1,94 @@
+
+// 任务步骤初始化参数
+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,progress:number){
+
+    }
+    onComplete(step:AgentTaskStep){
+
+    }
+    onError(step:AgentTaskStep){
+
+    }
+
+    /**
+     * 任务初始化函数
+     */
+    async init(metaData:AgentTaskStepOptions){
+    }
+    isInit:boolean = false; // 是否初始化
+
+    /** 
+     * 任务执行函数
+     * @desc 函数内必须有明确的完成、结束情形,函数内必须有进度的变化指示 progress从0.00 - 1.00
+     */
+    progress:number = 0;
+    // get progress(){
+    //     return this._progress
+    // }
+    // set progress(v:number){
+    //     this._progress = v
+    //     this.onProgress(this,this.progress)
+    // }
+
+    handle():Promise<any>|any{
+        this.beforeHandle(this)
+
+
+
+
+
+        this.onComplete(this)
+        this.onError(this)
+        return true
+    }
+
+    /**
+     * 任务错误提示
+     */
+    error:string = "" // 错误原因
+}

+ 71 - 0
E-Cover-app/src/agent/tasks/generate/generate-user-valiate.ts

@@ -0,0 +1,71 @@
+import { ModalController } from "@ionic/angular";
+import { FmodeChatCompletion } from "fmode-ng";
+import { extactAndParseJsonFromString } from "src/agent/agent.json";
+import { AgentTaskStep } from "src/agent/agent.task";
+
+export function TaskGenerateUserValiate(options: {
+    modalCtrl: ModalController
+    shareData: any
+}
+): AgentTaskStep {
+    let task1 = new AgentTaskStep({ title: "检测输入合法性", shareData: options.shareData.userProfile })
+    task1.handle = () => {
+        return new Promise(async (resolve, reject) => {
+            //获取当前任务集共享数据:userProfile
+            let userProfile = options.shareData.userProfile
+            //若必填项未填写,则提示用户补充
+            if (userProfile.gender == '不限制' || userProfile.age == '不限制' || userProfile.height == '不限制' || userProfile.weight == '不限制' || userProfile.season == '不限制') {
+                task1.error = "必填项不完整,请补充完整"
+                resolve(false);
+                return;
+            }
+            //若必填项已填写,任务继续
+            //穿搭提示词生成
+            const userProfileJson=JSON.stringify(options.shareData.userProfile,null,);
+            console.log(userProfileJson)
+            let PromptTemplate = `您是一名专业的服装搭配师,根据客户的身高、体重等数据,为客户推荐适合TA的穿搭方案。
+            客户的要求如下:${userProfileJson}
+            结果以JSON格式表示:(以下是JSON格式的参考方案)
+            {
+                "schemeName":"方案名(例如:夏日小清新)",
+                "gender":"性别",
+                "age":"年龄",
+                ......
+                (传入的json中有什么属性就补上什么)
+                "schemeList":{
+                    "上衣":["name":"上衣特征描述(简洁,例如:轻薄短袖衬衫)","desc":"穿搭思路描述(具体,例如:选择一款轻薄透气的短袖衬衫,颜色可以是浅蓝色或淡灰色,搭配细条纹或小格纹图案,以增加层次感。衬衫的剪裁要稍微宽松,以便于活动,适合办公室环境。)"],
+                    "下装":["name":"下装特征描述","desc":"穿搭思路描述"],
+                    ......
+                    (身上所有的衣服从里到外全部写出来加入schemeList中,例如冬季穿衣较多,从里到外有内衬衣、毛衣、夹克、棉袄、外套等。若部分有就将其部分写出来
+                    还有不要写笼统的“上衣”“下装”“配饰”等词条,要写例如“裤子”“棉毛衫”“项链”“围巾”等具体的服装类型的词条)
+                }
+            }
+            `
+            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) {
+                    options.shareData = extactAndParseJsonFromString(message.content)
+                    task1.progress = 1
+                    console.log(options.shareData)
+                    resolve(true);
+                }
+            })
+        })
+    }
+    return task1
+}

+ 64 - 0
E-Cover-app/src/agent/tasks/inquiry/1.inquiry-user-story.ts

@@ -0,0 +1,64 @@
+import { AgentTaskStep } from 'src/agent/agent.task';
+import { getUserInput } from 'src/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from 'src/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格式表示:
+                症状名称为具体的症状,症状描述为用户的感受,持续时间若没有直接说,可以写近期即可。
+                {
+                    "keshi":"科室名称",
+                    "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
+}

+ 79 - 0
E-Cover-app/src/agent/tasks/inquiry/2.inquiry-doctor-question.ts

@@ -0,0 +1,79 @@
+import { AgentTaskStep } from 'src/agent/agent.task';
+import { getUserInput } from 'src/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from 'src/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 = `您是一名专业的${options.shareData.userStory.keshi}主任医生,患者简单描述了一下他的情况,请您思考下还有哪些需要询问。
+                症状口述:${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
+}

+ 141 - 0
E-Cover-app/src/agent/tasks/inquiry/3.inquiry-user-answer.ts

@@ -0,0 +1,141 @@
+import { AgentTaskStep } from 'src/agent/agent.task';
+import { getUserInput } from 'src/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from 'src/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.keshi}主任医生,根据具体的询问,给出初步诊断。
+                症状口述:${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
E-Cover-app/src/agent/tasks/poem/poem-desc.ts

@@ -0,0 +1,55 @@
+import { AgentTaskStep } from 'src/agent/agent.task';
+import { getUserInput } from 'src/agent/agent.input';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+
+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:"诗文句子或段落"}
+                ]});
+                console.log("已获取用户输入:",userInput)
+                if(userInput){
+                    console.log("已获取诗文内容:",userInput['诗文内容'])
+                }
+                if(!userInput?.['诗文内容']){
+                    task1.error = "缺少诗文内容,请重新开始"
+                    resolve(false);
+                }
+
+                    // 文本生成
+                let PromptTemplate = `您是一名专业的美术画家,请您根据古诗文的内容,将其描述的画面、场景、人物、物品等用最简短的语言表达,直接写出画面,并且以中国的古风意境为主
+                诗文如下:
+                ${userInput['诗文内容']}
+                `
+                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.01
+                        }
+                        // 打印消息体
+                        // console.log(message.content)
+                        // 赋值消息内容给组件内属性
+                        options.shareData.PictureDescResult = message.content
+                        if(message.complete){ // 判断message为完成状态,则设置isComplete为完成
+                            task1.progress = 1
+                            resolve(true)
+                        }
+                })
+            })
+
+        }
+        return task1
+}

+ 43 - 0
E-Cover-app/src/agent/tasks/poem/poem-picture.ts

@@ -0,0 +1,43 @@
+import { ModalController } from '@ionic/angular/standalone';
+import { AgentTaskStep } from 'src/agent/agent.task';
+import { ImagineWork, DalleOptions } from 'fmode-ng';
+
+
+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
+        },1000)
+        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
+}

+ 29 - 2
E-Cover-app/src/app/generate-option/generate-option.component.html

@@ -40,7 +40,7 @@
   <!--风格描述输入框-->
   <div id="styleDesc">
     <ion-textarea label="风格描述" labelPlacement="floating" fill="solid" [autoGrow]="true"
-      [(ngModel)]="styleDesc"></ion-textarea>
+      [(ngModel)]="userProfile.customDesc"></ion-textarea>
     <ion-text (click)="onHelperTextClick()" id="helper-text">没有想法?点击展开</ion-text>
   </div>
   <!--可供用户选择的提示词,默认隐藏,通过点击风格输入框中的helper-text展开-->
@@ -63,7 +63,34 @@
 <div id="container">
   <div id="popover-container">
     <img style="width: 100px;" src="/assets/generate-option-style/load_animation.gif">
-    <ion-text>提示</ion-text>
+    <p>正在生成,请耐心等待...</p>
+    <ul>
+      @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>   
+      }
+      </ul>
     <ion-button (click)="hidePopover()">确定</ion-button>
   </div>
 </div>

+ 8 - 4
E-Cover-app/src/app/generate-option/generate-option.component.scss

@@ -167,9 +167,13 @@ ion-content {
 }
 #popover-container {
   position: absolute;
-  width: 50%;
-  height: 20%;
-  top: 40%;
-  left: 25%;
+  width: 70%;
+  height: 30%;
+  top: 35%;
+  left: 15%;
   background-color: white;
+  display:grid;
+  place-items: center;
+  color: black;
+  border-radius: 15px;
 }

+ 61 - 56
E-Cover-app/src/app/generate-option/generate-option.component.ts

@@ -1,11 +1,14 @@
 import { CommonModule } from '@angular/common';
 import { Component, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
-import { IonicModule, NavController } from '@ionic/angular';
+import { IonicModule, NavController, ModalController } from '@ionic/angular';
 import { addIcons } from 'ionicons';
-import { arrowBackOutline } from 'ionicons/icons';
+import { arrowBackOutline, checkmarkCircleOutline, closeCircleOutline, radioButtonOffOutline, reload, reloadOutline } from 'ionicons/icons';
 import { FormsModule } from '@angular/forms';
-addIcons({ 'arrow-back-outline': arrowBackOutline });
+import { AgentTaskStep } from 'src/agent/agent.task';
+import { TaskGenerateUserValiate } from 'src/agent/tasks/generate/generate-user-valiate';
+import { TaskExecutor } from 'src/agent/agent.start';
+addIcons({ 'arrow-back-outline': arrowBackOutline, radioButtonOffOutline, closeCircleOutline, checkmarkCircleOutline, reloadOutline });
 @Component({
   selector: 'app-generate-option',
   templateUrl: './generate-option.component.html',
@@ -18,10 +21,12 @@ export class GenerateOptionComponent implements OnInit {
    * @构造和初始化
    */
   //构造器
-  constructor(private router: Router, private navCtrl: NavController) { }
+  constructor(private modalCtrl: ModalController, private router: Router, private navCtrl: NavController) { }
   //初始化
   ngOnInit() { }
-  //返回按钮事件
+  /**
+   * @返回按钮事件
+   */
   goBack() {
     this.navCtrl.back();
   }
@@ -35,6 +40,7 @@ export class GenerateOptionComponent implements OnInit {
     height: '不限制',
     weight: '不限制',
     season: '不限制',
+    customDesc: '',
     regStyle: '不限制',
     sceFunction: '不限制',
     dsgPhilosophy: '不限制',
@@ -76,6 +82,9 @@ export class GenerateOptionComponent implements OnInit {
     '秋季': false,
     '冬季': false
   };
+  //自定义描述输入框定义变量
+
+  
   /**
    * @切换选项卡
    * 1.option:字典对象
@@ -179,11 +188,42 @@ export class GenerateOptionComponent implements OnInit {
     },
   ];
 
+  /**
+   * @其他变量定义
+   */
+
+  taskList: AgentTaskStep[] = [];//任务列表
+  shareData: any = {};//共享数据
+  /**
+   * @任务链设计
+   */
+  //
+  wait(duration: number = 1000) {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve(true);
+      }, duration);
+    });
+  }
+  /**
+   * 任务:
+   * 1.验证必填资料
+   * 2.验证话题是否相关
+   * 3.生成服装提示词
+   * 4.生成图片
+   */
+  doGenerateTask() {
+    console.log(this.userProfile);
+    this.shareData.userProfile = this.userProfile;
+    let task1 = TaskGenerateUserValiate({ shareData: this.shareData, modalCtrl: this.modalCtrl })
+    //定义任务集
+    let GenerateTaskList = [task1]
+    this.taskList = GenerateTaskList;
+    TaskExecutor(GenerateTaskList)
+
+  }
+
 
-  //补充说明
-  styleDesc: string = '';
-  //用户提示词
-  userPrompt: string = '';
 
   //点击选项卡事件
   toggleChip(cardId: string, chipId: number): void {
@@ -220,51 +260,15 @@ export class GenerateOptionComponent implements OnInit {
     })
   };
 
-  //生成传递提示词事件
-  sendMsg() {
-    this.userPrompt = "请生成一个穿搭照片绘画提示词:模板如下:\n" +
-      "穿搭主题:夏季通勤风格\n" +
-      "性别:男性\n" +
-      "体重:70kg\n" +
-      "年龄:约40-45岁\n" +
-      "整体风格:简约、舒适、专业\n" +
-      "上装:\n" +
-      "衬衫:选择一款轻薄透气的短袖衬衫,颜色可以是浅蓝色或淡灰色,搭配细条纹或小格纹图案,以增加层次感。衬衫的剪裁要稍微宽松,以便于活动,适合办公室环境。\n" +
-      "外套(可选):如果需要在空调环境中,搭配一件轻便的深色西装外套,面料应为透气的棉麻混纺,能够增添正式感。\n" +
-      "下装:\n" +
-      "裤子:选择一条修身但不紧身的深色休闲裤(如海军蓝或深灰色),采用轻便的面料,保持通勤的舒适性。裤子长度适中,裤脚可微微收口,展现干练的形象。\n" +
-      "鞋子:\n" +
-      "鞋款:搭配一双经典的休闲皮鞋或轻便的商务休闲鞋,颜色可以选择黑色或深棕色,鞋面光滑,适合正式场合,同时也提供舒适的步行体验。\n" +
-      "配饰:" +
-      "腰带:搭配一条与鞋子颜色一致的皮质腰带,简约设计,增加整体协调感。\n" +
-      "手表:佩戴一款简约风格的手表,金属表带或皮表带均可,展现成熟稳重的气质。\n" +
-      "公文包:选择一款深色的公文包,材质为皮革或优质帆布,既实用又具有商务气息。\n" +
-      "场景功能:适合在办公室、商务会议、午餐约会等场合穿着,整体搭配既显得专业又不失时尚感,适合夏季通勤的需求。\n" +
-      "以上模板仅供参考,可以写的更详细一些,但不要过于繁琐和冗长。\n" +
-      "以下内容为用户需求:\n" +
-      "性别:" + this.userProfile.gender + "\n" +
-      "年龄:" + this.userProfile.age + "\n" +
-      "身高:" + this.userProfile.height + "\n" +
-      "体重:" + this.userProfile.weight + "\n" +
-      "季节:" + this.userProfile.season + "\n" +
-      "区域风格:" + this.userProfile.regStyle + "\n" +
-      "场景功能:" + this.userProfile.sceFunction + "\n" +
-      "设计理念:" + this.userProfile.dsgPhilosophy + "\n" +
-      "艺术风格:" + this.userProfile.artStyle + "\n" +
-      "色彩搭配:" + this.userProfile.color + "\n" +
-      "补充说明:" + this.styleDesc + "\n";
-    console.log(this.userPrompt)
-  }
-
   /**
    * @跳转到聊天面板
    * 调用路由跳转到chatPanel页面,并传递用户需求信息
    */
-  goChatPanel() {
+  /*goChatPanel() {
     this.router.navigate(['/chatPanel'], {
       queryParams: { userPrompt: this.userPrompt }
     });
-  }
+  }*/
   /**
    * @风格描述下的帮助文本点击事件
    * 1. 将可选提示词展示或隐藏
@@ -294,20 +298,20 @@ export class GenerateOptionComponent implements OnInit {
    */
   submitJudge() {
     let element = document.getElementById('container');
-      if (element) {
-        // 直接切换元素的显示状态
-        element.style.display = 'block';
-      }
+    if (element) {
+      // 直接切换元素的显示状态
+      element.style.display = 'block';
+    }
   }
   /**
    * @提交生成终止返回
    */
   hidePopover() {
     let element = document.getElementById('container');
-      if (element) {
-        // 直接切换元素的显示状态
-        element.style.display = 'none';
-      }
+    if (element) {
+      // 直接切换元素的显示状态
+      element.style.display = 'none';
+    }
   }
   /**
    * @发送提示词到下一个页面并跳转到生成结果页面
@@ -316,7 +320,7 @@ export class GenerateOptionComponent implements OnInit {
    */
   sendMsgAndGoGenerateResult() {
     this.submitJudge();
-    this.sendMsg();
+    this.doGenerateTask();
     //this.goChatPanel();
   }
 }
@@ -330,6 +334,7 @@ type UserProfile = {
   height: string;
   weight: string;
   season: string;
+  customDesc: string;
   regStyle: string;
   sceFunction: string;
   dsgPhilosophy: string;