
# 核心概念
- FmodeChatCompletion 文本补全（单次）
    - 特点：简单易用，直接输入prompt，获得文本补全结果
- FmodeChat 对话聊天（多轮）
    - 特点：应对多轮对话复杂场景，与虚拟角色对话，获得消息列表

# 项目结构
- chat-class.ts 核心类
    - interface FmodeChatMessage 飞码消息接口类型
    - class FmodeChat 聊天会话类（多次消息）
        - messageList 包含多条消息
        - mask 面具信息（AI角色定义）
    - class FmodeChatCompletion 消息补全类（单次消息）

# 核心属性
- isTalkMode:boolean 是否开启对话模式
    - true 文本显示后，自动播放音频


# AI问答：FmodeChatCompletion 文本补全（单次）
## 调用方式
> 注意：消息列表测试期间，保留关键字：FmodeAiTest测试问题，减少实际接口调用消耗，减少开发阶段的tokens支出。

``` ts
// 引用FmodeChatCompletion类
import { FmodeChatCompletion } from 'fmode-ng/core/agent/chat/completion';

class CompChatPage{

    responseMessage:string = ""
    createNewCompletion(){
        // 创建上下文提示词消息列表
        let messageList = [
            {role:"user",content:"填写你发送过的历史消息：可用于Prompt定义AI扮演的角色细节"},
            {role:"assistant",content:"填写AI回复的历史消息"},
            {role:"user",content:"FmodeAiTest测试问题"}, // 填写你要发送的消息
        ]

        let options = {
          model:"fmode-1.6-cn", 
        }

        // 创建并发起一条新的消息补全
        let completion = new FmodeChatCompletion(messageList,options)
        completion.model = "fmode-1.6-cn" 
        // 持续更新事件推送的消息体内容绑定至消息变量
        let send$ = completion.sendCompletion({
          isDirect:true, // 直接返回不限速
          temperature: 0.5,
          presence_penalty: 0,
          frequency_penalty: 0
        }).subscribe(message=>{
            if(typeof message?.content =="string"){
                this.responseMessage = message?.content
            }
            if(message.complete){
                // 完成消息推送,可执行其他业务逻辑

                // 结束订阅
                send$.unsubscribe();
            }
        })
    }
}
```
 

## AI生成：返回JSON格式可程序化调用的结果函数

``` ts
import { completionJSON } from 'fmode-ng/core/agent/chat/completion';

// 定义 JSON 结构描述
const clothingSchema = `{
  "character": "string", // 角色名称
  "outfit": {
    "head": {
      "item": "string", // 头部装饰名称
      "color": "string" // 颜色色号，格式为 #RRGGBB
    },
    "top": {
      "item": "string", // 上衣名称
      "color": "string" // 颜色色号
    },
    "bottom": {
      "item": "string", // 下装名称
      "color": "string" // 颜色色号
    },
    "shoes": {
      "item": "string", // 鞋子名称
      "color": "string" // 颜色色号
    },
    "accessories": [
      {
        "item": "string", // 配饰名称
        "color": "string" // 颜色色号
      }
    ]
  }
}`;

// 使用函数生成哪吒主题的服装搭配
async function generateNezhaOutfit() {
  try {
    const prompt = `
      请为哪吒这个经典的中国神话角色设计一套现代服装搭配。
      要求：
      1. 保持哪吒的经典特色（红色为主色调，活泼动感）
      2. 融入现代时尚元素
      3. 适合年轻人穿着
      4. 颜色搭配要协调
      5. 每个部位都要指定具体的颜色色号
    `;

    const result = await completionJSON(
      prompt,
      clothingSchema,
      (content) => {
        // 实时显示生成过程
        console.log('正在生成:', content);
      }
    );

    console.log('生成的服装搭配:', result);
    return result;
  } catch (error) {
    console.error('生成失败:', error);
  }
}

```


## AI对话：聊天弹窗组件完整用法

```
  // medical-consultation.component.ts
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { 
  IonHeader, 
  IonToolbar, 
  IonTitle, 
  IonContent, 
  IonCard, 
  IonCardHeader, 
  IonCardTitle, 
  IonCardSubtitle, 
  IonCardContent, 
  IonAvatar, 
  IonList, 
  IonItem, 
  IonLabel, 
  IonNote,
  IonButton, 
  IonIcon, 
  IonBadge, 
  IonListHeader,
  ModalController,
  InfiniteScrollCustomEvent,
  IonInfiniteScroll,
  IonInfiniteScrollContent,
  IonSpinner,
  IonGrid,
  IonRow,
  IonCol,
  IonRefresher,
  IonRefresherContent
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { 
  medicalOutline, 
  medkitOutline, 
  chatbubbleOutline, 
  timeOutline, 
  refreshOutline,
  arrowForwardOutline
} from 'ionicons/icons';
import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
import {FmodeParse,FmodeObject,FmodeQuery,FmodeUser} from "../../../../core/parse";const Parse = FmodeParse.with("nova");

@Component({
  selector: 'fm-test-fmode-chat-panel-lifecycle',
  templateUrl: './test-fmode-chat-panel-lifecycle.component.html',
  styleUrl: './test-fmode-chat-panel-lifecycle.component.scss',
  standalone: true,
  providers:[ModalController],
  imports: [
    CommonModule,
    IonHeader,
    IonToolbar,
    IonTitle,
    IonContent,
    IonCard,
    IonCardHeader,
    IonCardTitle,
    IonCardSubtitle,
    IonCardContent,
    IonAvatar,
    IonList,
    IonItem,
    IonLabel,
    IonNote,
    IonButton,
    IonIcon,
    IonBadge,
    IonListHeader,
    IonInfiniteScroll,
    IonInfiniteScrollContent,
    IonSpinner,
    IonGrid,
    IonRow,
    IonCol,
    IonRefresher,
    IonRefresherContent
  ]
})
export class TestFmodeChatPanelLifecycleComponent implements OnInit {
  doctors:FmodeObject[] = [];
  consultations:FmodeObject[] = [];
  isLoading = false;
  isRefreshing = false;

  constructor(private modalCtrl: ModalController) {
    addIcons({ 
      medicalOutline, 
      medkitOutline, 
      chatbubbleOutline, 
      timeOutline, 
      refreshOutline,
      arrowForwardOutline
    });
  }

  async ngOnInit() {
    await this.loadDoctors();
    await this.loadConsultations();
  }

  // 加载医生列表
  async loadDoctors() {
    this.isLoading = true;
    try {
      const query = new Parse.Query('Doctor');
      query.include('depart');
      query.equalTo('available', true);
      query.descending('createdAt');
      this.doctors = await query.find();
    } catch (error) {
      console.error('加载医生列表失败:', error);
    } finally {
      this.isLoading = false;
    }
  }

  // 加载咨询记录
  async loadConsultations(event?: InfiniteScrollCustomEvent) {
    if (!event) {
      this.isRefreshing = true;
    }
    
    try {
      const query = new Parse.Query('DoctorConsult');
      query.include(['doctor', 'department']);
      query.equalTo('patient', Parse.User.current());
      query.descending('updatedAt');
      query.limit(10);
      
      if (event) {
        query.skip(this.consultations.length);
      }
      
      const results = await query.find();
      
      if (event) {
        this.consultations = [...this.consultations, ...results];
        event.target.complete();
        if (results.length < 10) {
          event.target.disabled = true;
        }
      } else {
        this.consultations = results;
      }
    } catch (error) {
      console.error('加载咨询记录失败:', error);
    } finally {
      this.isRefreshing = false;
    }
  }

  // 刷新数据
  async handleRefresh(event: any) {
    await this.loadDoctors();
    await this.loadConsultations();
    event.target.complete();
  }

 // 处理问诊（新建或恢复）
async handleConsultation(doctor:FmodeObject, consultation?:FmodeObject) {
  localStorage.setItem("company", "E4KpGvTEto");
  
  // 如果是新建咨询，创建记录
  if (!consultation) {
    consultation = new Parse.Object('DoctorConsult');
    consultation.set('title', `${doctor.get('depart')?.get('name') || '未知科室'}问诊`);
    consultation.set('doctor', doctor);
    consultation.set('department', doctor.get('depart'));
    consultation.set('patient', Parse.User.current());
    consultation.set('status', '进行中');
    consultation.set('consultationTime', new Date());
    // await consultation.save();
  }

  const options: ChatPanelOptions = {
    roleId: "2DXJkRsjXK",
    chatId: consultation?.get('chatId'),
    onChatInit: (chat: FmodeChat) => {
      const currentDoctor = consultation?.get('doctor') || doctor;
      
      // 设置医生角色信息
      chat.role.set("name", currentDoctor.get('name'));
      chat.role.set("title", currentDoctor.get('title'));
      chat.role.set("desc", `${currentDoctor.get('title')} ${currentDoctor.get('name')}`);
      chat.role.set("tags", [
        consultation?.get('department')?.get('name') || currentDoctor.get('depart')?.get('name'), 
        currentDoctor.get('expertise')
      ]);
      chat.role.set("avatar", currentDoctor.get('avatar') || "assets/images/default-doctor.png");
      
      // 完整的角色提示词
      chat.role.set("prompt", `
# 角色设定
您是一名亲切和蔼的专业的全科医生，${currentDoctor.get('name')}，需要完成一次完整的门诊服务。

# 对话环节
## 1. 导诊环节：欢迎用户，请用户描述症状
用户回答后，引导合适科室，开始问诊
## 2. 问诊环节：询问症状细节、询问病史等关键信息
用户回答后进入下阶段
## 3. 检查环节：开检查单，并提醒用户检查后反馈结果
用户反馈后进入下阶段
## 4. 处方环节：给出处方内容，同时结尾携带[处方完成]"

# 开始话语
当您准备好了，可以以一个医生的身份，向来访的用户打招呼。并且每个阶段，主动一些。`);

      // 对话灵感分类
      const promptCates = [
        {
          "img": "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/r1ltv1023812146.png",
          "name": "外科"
        },
        {
          "img": "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fo81fg034154259.png",
          "name": "内科"
        },
        {
          "img": "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fc1nqi034201098.png",
          "name": "心理"
        }
      ];
      
      // 对话灵感列表
      const promptList = [
        {
          cate: "外科", 
          img: "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/r1ltv1023812146.png",
          messageList: [
            "局部疼痛或肿胀", "伤口出血或感染", "关节活动受限", 
            "体表肿块或结节", "外伤后活动障碍", "皮肤溃疡不愈合", 
            "异物刺入或嵌顿", "术后并发症复查", "肢体麻木或无力", 
            "运动损伤疼痛"
          ]
        },
        {
          cate: "内科", 
          img: "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fo81fg034154259.png",
          messageList: [
            "反复发热或低热", "持续咳嗽咳痰", "胸闷气短心悸", 
            "慢性腹痛腹泻", "头晕头痛乏力", "体重骤增或骤减", 
            "食欲异常或消化不良", "尿频尿急尿痛", "睡眠障碍易醒", 
            "异常出汗或怕冷"
          ]
        },
        {
          cate: "心理", 
          img: "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fc1nqi034201098.png",
          messageList: [
            "持续情绪低落", "焦虑紧张不安", "失眠或睡眠过多", 
            "注意力难以集中", "社交恐惧回避", "强迫思维或行为", 
            "记忆减退疑虑", "躯体无器质性疼痛", "自杀倾向念头", 
            "现实感丧失体验"
          ]
        },
      ];
      
      setTimeout(() => {
        chat.role.set("promptCates", promptCates);
        
        const ChatPrompt = Parse.Object.extend("ChatPrompt");
        chat.promptList = promptList.map(item => {
          const prompt = new ChatPrompt();
          prompt.set(item);
          prompt.img = item.img;
          return prompt;
        });
      }, 500);

      // 功能按钮区域预设
      chat.leftButtons = [
        { 
          title: "话题灵感",
          showTitle: true,
          icon: "color-wand-outline",
          onClick: () => {
            chat.isPromptModalOpen = true;
          },
          show: () => {
            return chat?.promptList?.length;
          }
        },
        {
          title: "门诊归档",
          showTitle: true,
          icon: "archive-outline",
          onClick: () => {
            console.log(chat?.chatSession);
          },
          show: () => true
        }
      ];
    },
    onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
      const content = message?.content;
      if (typeof content === 'string' && content.includes('[处方完成]')) {
        console.log('处方完成，保存咨询记录');
        consultation?.set('content', content);
        consultation?.set('status', '已完成');
        consultation?.save();
      }
    },
    onChatSaved: (chat: FmodeChat) => {
      console.log('咨询会话保存', chat?.chatSession?.id);
      consultation?.set('chatId', chat?.chatSession?.id);
      consultation?.save();
    }
  };
  
  openChatPanelModal(this.modalCtrl, options);
}

  // 格式化时间
}
```