# 核心概念 - 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); } // 格式化时间 } ```