程序入口
ai问答效果视频
import { AppRouter, DynamicsRouter } from '@brainmed/router';
import { AppStorageModel } from '@brainmed/utils';
import { AiTopBar } from '../components/ai/AiTopBar';
import { AIContentList } from '../components/ai/AIContentList';
import { WebSocketManager } from '../util/AIWebSocket';
import { promptAction } from '@kit.ArkUI';
import { JSON, util } from '@kit.ArkTS';
import { AIChatBean, AIHistoryBean, AISendMessage } from '../types/KnowledgeResultType';
import { KnowledgeApi } from '../api/KnowledgeApi';
import { NetworkAddress } from '@brainmed/common';
import { KnowledgeAiController } from '../controller/KnowledgeAiController';
/**
* AI问答
*/
@AppRouter()
@Component
export struct KnowledgeAI {
@State isOnblur: boolean = false
@State chatStatus: number = 1 // 0不可发送,1可发送
@State areaCount: number = 0 //是否存在发送内容
@State isIng: boolean = false //是否正在输出内容
@State areaContent: string = "" //待发送内容
@Provide sessionId: string = ""
@State aiChatBeanList: AIChatBean[] = []
@State fullAnswer: string = ""
@State isFirst: boolean = true
@State aiChatBean: AIChatBean = {
queryData: '',
answerFlag: '',
answer: '',
chatId: '',
sessionId: '',
keywords: [],
question: '',
collect: 'F',
feedback: 'F',
interrupt: 'F',
saveStatus: false,
isCollect: false,
knowledgeInfo: [],
relatedQuestions: [],
}
controller: TextAreaController = new TextAreaController()
// 使用示例
wsManager = new WebSocketManager(NetworkAddress.BASE_URL_AI_WS);
aboutToAppear(): void {
this.initWebSocket()
}
aboutToDisappear(): void {
this.wsManager.close()
}
/**
* 创建新会话
*/
createNewSession(): void {
if (this.aiChatBeanList.length == 0) {
promptAction.showToast({
message: "当前已是最新会话",
duration: 2000,
alignment: Alignment.Center
})
return
}
if (this.isIng) { //正在输出内容,结束输出
promptAction.showToast({
message: "当前问题正在输出",
duration: 2000,
alignment: Alignment.Center
})
return
}
this.sessionId = util.generateRandomUUID()
this.aiChatBeanList = []
}
initWebSocket(): void {
this.sessionId = util.generateRandomUUID()
this.wsManager.connect(
() => {
this.chatStatus = 1
if (this.isFirst) {
this.isFirst = false
const params = DynamicsRouter.getParamByName<AIChatBean>('knowledge/KnowledgeAI');
if (params) {
if (params.question) {
this.createNewMessage(params.question)
}
}
}
},
() => {
this.chatStatus = 0
}
);
this.wsManager.onMessage((message) => {
let aiBean = this.jsonToBean(message.toString())
console.log("AI_WEB_SOCKET输出内容" + message);
if (this.sessionId === aiBean.sessionId) {
// 查找 aiChatBeanList 中是否已有相同 chatId 的项
let existingIndex = this.aiChatBeanList.findIndex(bean => bean.chatId === aiBean.chatId);
// 如果列表中没有找到相同的 chatId
if (existingIndex === -1) {
if (this.aiChatBean.interrupt === "F") {
this.aiChatBean.sessionId = aiBean.sessionId
this.aiChatBean.queryData = aiBean.queryData
this.aiChatBean.chatId = aiBean.chatId
this.aiChatBean.answer = aiBean.answer
this.aiChatBean.answerFlag = aiBean.answerFlag
this.aiChatBean.question = aiBean.question
if (aiBean.knowledgeInfo.length > 0) {
this.aiChatBean.knowledgeInfo = []
this.aiChatBean.knowledgeInfo = aiBean.knowledgeInfo
}
if (aiBean.keywords.length > 0) {
this.aiChatBean.keywords = []
this.aiChatBean.keywords = aiBean.keywords
}
//检查当前列表中是否存在该数据。不存在则保存
this.isIng = true
// 将当前接收到的answer追加到fullAnswer
if (this.aiChatBean.answer != null) {
this.fullAnswer += this.aiChatBean.answer;
}
this.aiChatBean.answer = this.fullAnswer
}
}
}
});
this.wsManager.onError(() => {
});
}
jsonToBean(message: string): AIChatBean {
return JSON.parse(message.toString()) as AIChatBean
}
createNewMessage(question: string): void {
this.areaContent = question
this.sendMessage()
}
/**
* 发送内容
*/
sendMessage(): void {
if (this.chatStatus == 0) { //当前websocket未连接不可发送内容
return
}
if (this.isIng) { //正在输出内容,结束输出
this.stopMessage()
return
}
if (this.areaContent.length <= 0) {
promptAction.showToast({
message: "请输入您的问题",
duration: 2000,
alignment: Alignment.Center
})
return
}
//组装数据发送
let aiSendMessage: AISendMessage = {
question: this.areaContent,
model: "doubao",
sessionId: this.sessionId,
history: []
}
let jsonString = JSON.stringify(aiSendMessage);
console.log("AI_WEB_SOCKET_Message 发送内容:" + jsonString);
this.wsManager.send(jsonString, () => {
});
this.areaContent = ""
}
/**
* 结束发送内容
*/
stopMessage(): void {
this.aiChatBean.interrupt = "T"
this.clearData([])
this.showImage()
}
/**
* 控制底部输入区域内图片根据状态展示
* @returns
*/
showImage(): Resource {
if (this.isIng) {
return $r('app.media.ai_chat_send_stop')
} else {
if (this.chatStatus == 0) {
return $r('app.media.ai_chat_send_off')
} else {
if (this.areaCount > 0) {
return $r('app.media.ai_chat_send_on')
} else {
return $r('app.media.ai_chat_send_off')
}
}
}
}
async saveAI(chatId: string) {
console.log("MZQ_AI---执行保存" + chatId);
const index = this.aiChatBeanList.findIndex(bean => bean.chatId === chatId);
if (index !== -1) {
const foundBean = this.aiChatBeanList[index];
KnowledgeApi.saveAi(foundBean).then(() => {
console.log("MZQ_AI---存储成功:" + chatId);
this.aiChatBeanList[index].saveStatus = true
if (foundBean.relatedQuestions.length > 0) {
console.log("MZQ_AI---开始存储相关问题:" + chatId);
this.saveOtherQuestionList(foundBean)
}
})
} else {
console.log("MZQ_AI---存储失败,未查询到数据 AI——ID" + chatId + "====列表数据:" + this.aiChatBeanList);
}
}
async saveOtherQuestionList(foundBean: AIChatBean) {
KnowledgeApi.saveOtherQuestionList(foundBean).then(() => {
console.log("MZQ_AI---相关问题存储成功:" + foundBean.chatId);
})
}
clearData(otherList: string[]): void {
this.fullAnswer = ""
this.isIng = false
this.aiChatBean.relatedQuestions = otherList
//结束后插入数据
let existingIndexTwo = this.aiChatBeanList.findIndex(bean => bean.chatId === this.aiChatBean.chatId);
if (existingIndexTwo !== -1) {
// 更新 aiChatBeanList 中的对象
this.aiChatBeanList[existingIndexTwo] = this.aiChatBean;
} else {
this.aiChatBeanList.push(this.aiChatBean)
}
console.log("MZQ_AI---存储状态:" + this.aiChatBean.saveStatus + "相关问题列表:" + otherList.length);
if (this.aiChatBean.saveStatus === false) {
//调用存储方法
this.saveAI(this.aiChatBean.chatId).then(() => {
console.log("MZQ_AI---执行保存saveAI返回");
})
}
this.aiChatBean = {
queryData: '',
answerFlag: '',
answer: '',
chatId: '',
sessionId: '',
keywords: [],
knowledgeInfo: [],
question: '',
collect: 'F',
feedback: 'F',
interrupt: 'F',
saveStatus: false,
isCollect: false,
relatedQuestions: []
}
// console.log("MZQ_AI---结束:" + this.aiChatBeanList.length);
}
/**
* 选中当前数据
* @param item
*/
selectAiHistory(item: AIHistoryBean): void {
console.log("MZQ_AI---选中当前指定的数据:" + item.name);
//清空本地数据,
if (this.isIng) {
promptAction.showToast({
message: '当前会话无法删除',
alignment: Alignment.Center,
duration: 1500
});
} else {
//根据历史ID获取AIHistory列表
this.getAiHistoryBySessionId(item.sessionId!)
}
}
/**
* 根据会话ID获取会话列表
* @param sessionId
*/
async getAiHistoryBySessionId(sessionId: string) {
this.sessionId = sessionId
KnowledgeApi.getAiHistoryBySessionId(sessionId).then((data) => {
this.aiChatBeanList = []
data.list?.map((item) => {
this.aiChatBeanList.push(item)
})
this.knowledgeAiController.scrollBottom()
})
}
/**
* 删除当前会话列表
* @param item
*/
deleteAiHistory(item: AIHistoryBean): void {
console.log("MZQ_AI---删除当前指定的数据:" + item.name);
//检查会话ID是否与当前ID相同
if (item.sessionId === this.sessionId) {
//不允许删除
promptAction.showToast({
message: '当前会话无法删除',
alignment: Alignment.Center,
duration: 1500
});
} else {
//执行删除
this.deleteSession(item.sessionId!!).then(() => {
})
}
}
private knowledgeAiController = new KnowledgeAiController()
//删除当前会话
async deleteSession(sessionId: string) {
KnowledgeApi.deleteSession(sessionId).then(() => {
promptAction.showToast({
message: '删除成功',
alignment: Alignment.Center,
duration: 1500
});
this.knowledgeAiController.refHistoryList()
})
}
build() {
NavDestination() {
Column() {
AiTopBar({
knowledgeAiController: this.knowledgeAiController,
selectAiHistory: (item: AIHistoryBean) => this.selectAiHistory(item),
deleteAiHistory: (item: AIHistoryBean) => this.deleteAiHistory(item),
createNewSession: () => this.createNewSession()
})
AIContentList({
knowledgeAiController: this.knowledgeAiController,
aiChatBeanList: this.aiChatBeanList,
status: this.fullAnswer,
aIChatBeanBottom: this.aiChatBean,
clear: (otherList: string[]) => this.clearData(otherList),
createMessage: (question: string) => this.createNewMessage(question)
}).layoutWeight(1)
Stack() {
TextArea({ text: this.areaContent, placeholder: "请输入您的问题", controller: this.controller })
.width('90%')
.placeholderColor($r('app.color.tipText'))
.padding(12)
.backgroundColor(Color.White)
.borderWidth(this.isOnblur ? 1 : 0)
.borderColor($r('app.color.text_area_border'))
.margin({ bottom: 12, top: 12 })
.padding({ right: 34 })
.onChange((value: string) => {
this.areaContent = value
this.areaCount = value.length
})
.onBlur(() => {
this.isOnblur = false;
})
.onFocus(() => {
this.isOnblur = true;
})
.constraintSize({
maxHeight: 70
})
Image(this.showImage())
.width(28)
.aspectRatio(1)
.position({ right: 8, bottom: 15 }).onClick(() => {
this.sendMessage()
})
}
}.height('100%').width('100%')
}
.width('100%')
.hideTitleBar(true)
.padding({ top: AppStorageModel.statusBarHeight() })
.backgroundImage($r('app.media.knowledge_ai_top_bg'))
.backgroundImageSize({
width: '100%',
})
.backgroundColor($r('app.color.knowledge_bg'))
}
}
侧边栏:
import { DynamicsRouter } from '@brainmed/router';
import { SilkPopup } from 'silk_ui';
import { AppStorageModel, UserModel } from '@brainmed/utils';
import { KnowledgeApi } from '../../api/KnowledgeApi';
import { AIHistoryBean, AIHistoryBeanGroup, AIHistoryBeanRequest } from '../../types/KnowledgeResultType';
import { BarImage, Pull2refresh2 } from '@brainmed/uicomponents';
import { KnowledgeAiController } from '../../controller/KnowledgeAiController';
import { inputMethod } from '@kit.IMEKit';
import { Page } from '@brainmed/network';
@Component
export struct AiTopBar {
@State showPopup: boolean = false
selectAiHistory: (item: AIHistoryBean) => void = (item) => {
}
deleteAiHistory: (item: AIHistoryBean) => void = (item) => {
}
createNewSession: () => void = () => {
}
@Consume sessionId: string
closePopup():
void {
this.showPopup = false
}
private knowledgeAiController = new KnowledgeAiController()
@State timer: number | null = null // 存储定时器ID
build() {
Row() {
BarImage({
click: () => {
DynamicsRouter.popAppRouter();
}
}).margin({ left: 12 })
Text('AI问答')
.padding(12)
.fontSize(16)
.fontColor(Color.Black)
.align(Alignment.Center)
.alignSelf(ItemAlign.Center)
Row() {
BarImage({
icon: $r('app.media.ai_icon_new_chat'),
click: () => {
this.createNewSession()
}
}).margin({ right: 12 })
BarImage({
icon: $r('app.media.ai_icon_more'),
click: () => {
let inputMethodController = inputMethod.getController();
inputMethodController.stopInputSession()
this.timer = setInterval(() => {
this.showPopup = true
clearInterval(this.timer!);
}, 50);
}
}).margin({ right: 12 })
}.align(Alignment.End).alignSelf(ItemAlign.Center)
SilkPopup({
show: this.showPopup,
showPosition: 'right',
safe_bottom: true,
showClose: true,
round: 0
}) {
AiHistoryList({
knowledgeAiController: this.knowledgeAiController,
selectAiHistory: (item: AIHistoryBean) => this.selectAiHistory(item)
,
deleteAiHistory: (item: AIHistoryBean) => this.deleteAiHistory(item),
closePopup: () => this.closePopup()
}).width('100%')
}
}.width('100%').justifyContent(FlexAlign.SpaceBetween).padding({ top: 8 })
}
}
@Component
export struct AiHistoryList {
@State isEdit: boolean = false
@State aIHistoryBeanList: AIHistoryBean[] = []
@State pageIndex: number = 1
scroller: Scroller = new Scroller();
@State nullData: boolean = false;
@State loading: boolean = false;
@State useList: AIHistoryBeanGroup[] = []
@State isRequest: boolean = false
selectAiHistory: (item: AIHistoryBean) => void = (item) => {
}
deleteAiHistory: (item: AIHistoryBean) => void = (item) => {
}
closePopup: () => void = () => {
}
@Consume sessionId: string
@State aIHistoryBeanListUse: AIHistoryBean[] = []
private knowledgeAiController = new KnowledgeAiController()
aboutToAppear(): void {
if (this.isRequest === false) {
// 将testFunc方法用子组件方法进行覆盖
if (this.knowledgeAiController) {
this.knowledgeAiController.refHistoryList = () => this.onRefresh()
}
this.isRequest = true
this.loading = true;
this.onRefresh();
}
}
/**
* 获取知识工具常用工具列表
*/
async getConversationHistory() {
let param: AIHistoryBeanRequest;
param = {
userId: UserModel.getUserInfo()?.id.toString(),
pageNum: this.pageIndex,
pageSize: 20,
};
let res = await KnowledgeApi.getConversationHistory(param)
this.aIHistoryBeanList = []
res.list?.map((v) => {
this.aIHistoryBeanList.push(v)
})
this.resetData()
if (this.aIHistoryBeanListUse.length === 0) {
this.nullData = true;
}
this.loading = false;
return res.page!;
}
@State index: number = 0
@State indexChild: number = 0
/**
* 重新组装数据,将数据分组
*/
resetData() {
// 遍历 aIHistoryBeanList,将数据按 dateType 分组
this.aIHistoryBeanList.forEach((v) => {
//判断是否已经存在存在则增加数据
if (this.useList.length > 0) {
// 检查是否存在
let isIn = false;
for (let i = 0; i < this.useList.length; i++) {
let u = this.useList[i];
if (u.dateType === v.dateType) {
// 存在
isIn = true;
break;
}
}
if (isIn == false) {
let aiHistoryBeanGroup: AIHistoryBeanGroup = {
dateType: v.dateType,
}
this.useList.push(aiHistoryBeanGroup);
}
} else {
let aiHistoryBeanGroup: AIHistoryBeanGroup = {
dateType: v.dateType,
}
this.useList.push(aiHistoryBeanGroup);
}
});
this.useList.forEach((v) => {
if (!v.list) {
v.list = []
v.id = this.index++
}
for (let i = 0; i < this.aIHistoryBeanList.length; i++) {
let u = this.aIHistoryBeanList[i];
if (u.dateType === v.dateType) {
u.id = this.indexChild++
v.list.push(u)
}
}
});
this.aIHistoryBeanListUse = []
this.useList.forEach((item) => {
if (Array.isArray(item.list)) {
item.list[0].isGroup = true
item.list.forEach((itemChild) => {
this.aIHistoryBeanListUse.push(itemChild)
})
}
console.log("MZQ_AI : " + item.dateType + ",list :" + item?.list?.length);
});
}
async onRefresh(): Promise<Page | undefined> {
this.pageIndex = 1
this.aIHistoryBeanList = []
this.useList = []
this.index = 0
this.indexChild = 0
let page = await this.getConversationHistory();
return page;
}
async onLoadMore() {
console.log('123123', '翻页了' + this.pageIndex)
this.pageIndex++;
console.log('123123', '翻页了2' + this.pageIndex)
let page = await this.getConversationHistory()
return page;
}
build() {
// 侧边抽屉
Column() {
Row() {
Text('历史提问').fontSize(16).fontColor($r('app.color.black')).padding(12)
Blank()
Text(this.isEdit ? '完成' : "编辑")
.fontSize(16)
.fontColor(this.isEdit ? $r('app.color.theme_color') : $r('app.color.black'))
.padding(12).onClick(() => {
this.isEdit = !this.isEdit
})
}.width('100%')
Pull2refresh2({
listData: this.aIHistoryBeanList,
scroller: this.scroller,
loading: this.loading,
nullData: this.nullData,
onRefresh: (): Promise<Page | undefined> => this.onRefresh(),
onLoadMore: (): Promise<Page | undefined> => this.onLoadMore(),
listView: (): void => this.contentView(),
})
}
.align(Alignment.Top)
.alignItems(HorizontalAlign.Start)
.width('100%')
.height('100%')
.padding({ top: AppStorageModel.statusBarHeight() })
}
getTypeName(dateType?: number): string {
let name = '';
switch (dateType) {
case 1:
name = '今天';
break;
case 2:
name = '本周';
break;
case 3:
name = '本月';
break;
default:
name = '更早';
}
return name
}
@Builder
contentView() {
List({ space: 8, initialIndex: 0, scroller: this.scroller }) {
ForEach(this.aIHistoryBeanListUse, (item: AIHistoryBean) => {
if (item.isGroup) {
ListItem() {
Text(this.getTypeName(item?.dateType))
.padding({ left: 12, top: 12 })
.fontSize(14)
.fontColor($r('app.color.ai_other_info'))
.width('100%')
.backgroundColor(Color.White)
}
}
ListItem() {
Row() {
Row() {
Text(item.name)
.fontSize(14)
.fontColor($r('app.color.black'))
.padding(12)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
.layoutWeight(1)
Text(item.dateValue)
.fontSize(12)
.fontColor($r('app.color.ai_history_time'))
.padding({ top: 12, bottom: 12, right: 12 })
}
.backgroundColor($r('app.color.ai_history_item'))
.borderRadius(4)
.borderWidth(item.sessionId === this.sessionId ? 1 : 0)
.borderColor(item.sessionId === this.sessionId ? $r('app.color.theme_color') : Color.Transparent)
.margin({ left: 12, right: 12, top: 12 })
.layoutWeight(1)
Image($r('app.media.ai_delete'))
.visibility(this.isEdit ? Visibility.Visible : Visibility.None)
.width(18)
.aspectRatio(1)
.alignSelf(ItemAlign.Center)
.margin({ right: 12, top: 12 })
.onClick(() => {
this.deleteAiHistory(item)
})
}.width('100%').justifyContent(FlexAlign.Center)
}.width('100%').onClick(() => {
this.closePopup()
this.selectAiHistory(item)
})
})
}
.onReachEnd(() => this.onLoadMore())
.margin({ right: 12, bottom: 40 })
.listDirection(Axis.Vertical)
.scrollBar(BarState.Off)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
.sticky(StickyStyle.Header) // 设置吸顶,实现粘性标题效果
.edgeEffect(EdgeEffect.None)
.width('100%')
.constraintSize({ maxHeight: '100%' })
}
}
具体内容渲染:
import { DynamicsRouter } from '@brainmed/router';
import { LengthMetrics, promptAction } from '@kit.ArkUI';
import { Markdown } from '@lidary/markdown';
import { KnowledgeApi } from '../../api/KnowledgeApi';
import { KnowledgeAiController } from '../../controller/KnowledgeAiController';
import { AIChatBean, AIFeedBackBean, AIKnowledgeInfo, AIOtherQuestion } from '../../types/KnowledgeResultType';
/**
* ai记录 列表
*/
@Component
export struct AIContentList {
private listScroller: Scroller = new Scroller();
@Prop aiChatBeanList: AIChatBean[]
@Provide feedBackTypeList: AIFeedBackBean[] = []
@Link @Watch('scrollBottom') status: string
@Link @Watch('updateBottom') aIChatBeanBottom: AIChatBean
private knowledgeAiController = new KnowledgeAiController()
clear: (otherList: string[]) => void = (otherList) => {
}
createMessage: (question: string) => void = (question) => {
}
aboutToAppear(): void {
// 将testFunc方法用子组件方法进行覆盖
if (this.knowledgeAiController) {
this.knowledgeAiController.scrollBottom = () => this.scrollBottom()
}
this.getFeedbackTypes()
}
scrollBottom(): void {
this.listScroller.scrollEdge(Edge.End)
}
updateBottom(): AIChatBean {
this.scrollBottom()
return this.aIChatBeanBottom
}
async getFeedbackTypes() {
KnowledgeApi.getFeedbackTypes().then((result) => {
this.feedBackTypeList = []
result.list?.map((item) => {
this.feedBackTypeList.push(item)
})
})
}
build() {
List({ scroller: this.listScroller }) {
defaultAi()
ForEach(this.aiChatBeanList, (aiBean: AIChatBean) => {
ListItem() {
AiContent({
aiBean: aiBean,
isOld: true,
clear: (otherList: string[]) => this.clear(otherList),
createMessage: (question: string) => this.createMessage(question)
})
}
})
bottomAi({
aiBean: this.updateBottom(),
clear: (otherList: string[]) => this.clear(otherList),
createMessage: (question: string) => this.createMessage(question)
})
.visibility(!this.updateBottom().question ? Visibility.None : Visibility.Visible)
}.scrollBar(BarState.Auto)
}
}
/**
* 最底部需要输出得正文item
*/
@Component
export struct bottomAi {
@Prop aiBean: AIChatBean
clear: (otherList: string[]) => void = (otherList) => {
}
createMessage: (question: string) => void = (question) => {
}
build() {
AiContent({
aiBean: this.aiBean,
isOld: false,
clear: (otherList: string[]) => this.clear(otherList),
createMessage: (question: string) => this.createMessage(question)
})
}
}
/**
* 历史数据正文item
*/
@Component
export struct AiContent {
@Prop isOld: boolean = true
@Prop aiBean: AIChatBean
clear: (otherList: string[]) => void = (otherList) => {
}
createMessage: (question: string) => void = (question) => {
}
build() {
Column() {
AiContentTop({ question: this.aiBean.question })
if (this.isOld) {
AiContentOld({
aiBean: this.aiBean,
createMessage: (question: string) => this.createMessage(question)
})
} else {
AiContentIn({
aiBean: this.aiBean,
clear: (otherList: string[]) => this.clear(otherList)
})
}
}
}
}
/**
* 正在输出得数据item
*/
@Component
export struct AiContentIn {
@Prop @Watch('onReady') aiBean: AIChatBean
@State answer: string = ""
@State remainingTime: number = 0 // 倒计时剩余时间
@State timerIdTitle: number | null = null // 存储定时器ID
@State displayText: string = "" // 用于显示的文本
@State currentIndex: number = 0 // 当前显示到文本的哪个索引
@State timerId: number | null = null // 存储定时器ID
clear: (otherList: string[]) => void = (otherList) => {
}
// 初始化倒计时
onReady() {
if (!this.aiBean.answerFlag && this.remainingTime <= 0) {
if (this.aiBean.saveStatus == false && this.aiBean.chatId) {
this.displayText = ""
this.startCountdown(20);
this.requestOtherQuestionList(this.aiBean).then(() => {
})
}
} else {
if (this.aiBean.answerFlag === "ing") {
if (this.displayText === '') {
this.startTextOutput(this.aiBean.answer)
}
}
}
}
// 启动倒计时
startCountdown(seconds: number) {
this.remainingTime = seconds;
console.log("MZQ_AI---重置计时参数:" + this.remainingTime);
this.timerIdTitle = setInterval(() => {
if (this.remainingTime > 0) {
this.remainingTime--;
} else {
clearInterval(this.timerIdTitle!);
}
}, 1000);
}
// 启动逐字输出
startTextOutput(text: string) {
// 清理当前定时器
if (this.timerId) {
clearInterval(this.timerId);
}
this.displayText = ""; // 清空当前显示的文本
this.currentIndex = 0; // 从头开始
this.timerId = setInterval(() => {
if (this.currentIndex < this.aiBean.answer.length && this.aiBean.interrupt == "F") {
this.displayText += this.aiBean.answer[this.currentIndex];
this.currentIndex++;
} else {
if (this.aiBean.answerFlag === "end" || this.aiBean.interrupt === "T") {
// console.log("AI_WEB_SOCKET_" + this.aiBean.answerFlag + "===" + this.aiBean.interrupt);
if (this.timerId) {
this.clear(this.relatedQuestions)
clearInterval(this.timerId);
}
}
}
}, 20); // 每 20 毫秒输出一个字符
}
@State relatedQuestions: string [] = []
async requestOtherQuestionList(aiBean: AIChatBean) {
console.log("MZQ_AI---准备执行" + aiBean.saveStatus + "==" + aiBean.chatId);
let params: AIOtherQuestion = {
question: aiBean.question,
chatId: aiBean.chatId
}
KnowledgeApi.getQueryList(params).then((res) => {
if (typeof res === 'string') {
this.relatedQuestions = [res];
} else if (Array.isArray(res)) {
this.relatedQuestions = res as string[];
}
console.log("MZQ_AI---请求相关问题列表集合:" + this.relatedQuestions.length + "===会话ID:" + aiBean.chatId);
})
}
build() {
Row() {
if (!this.aiBean.answerFlag) {
Text() {
//倒计时
if (this.remainingTime > 0) {
Span('正在获取资料,答案预计在 ')
.fontSize(14)
.fontColor($r('app.color.black'))
Span(`${this.remainingTime} 秒`)
.fontSize(14)
.fontColor($r('app.color.theme_color')) // 设置秒数的颜色
Span('内生成…')
.fontSize(14)
.fontColor($r('app.color.black'))
} else {
Span('答案生成中…')
.fontSize(14)
.fontColor($r('app.color.black'))
}
}
.lineSpacing(LengthMetrics.vp(6))
.fontSize(14)
.padding(12)
.backgroundColor(Color.White)
.borderRadius({
topLeft: 0,
topRight: 8,
bottomLeft: 8,
bottomRight: 8
})
.margin({ left: 12, right: 12 })
} else {
Column() {
Stack() {
Markdown({
content: this.displayText + " ●",
lineSpace: 8,
textLineSpace: 8,
fontStyle: {
fontColor: $r('app.color.txt_color'),
fontSize: 14,
}
}).padding(12)
}
}
.backgroundColor(Color.White)
.borderRadius({
topLeft: 0,
topRight: 8,
bottomLeft: 8,
bottomRight: 8
})
.margin({ left: 12, right: 12 })
}
}.justifyContent(FlexAlign.Start).width('100%')
}
}
/**
* 历史数据
*/
@Component
export struct AiContentOld {
@Prop aiBean: AIChatBean
createMessage: (question: string) => void = (question) => {
}
build() {
Column() {
Column() {
AIContent({ aiBean: this.aiBean })
if (this.aiBean.answer.indexOf("您好,我是脑医汇") < 0) {
Text('内容由AI结合脑医汇知识库生成')
.fontSize(12)
.borderRadius(25)
.backgroundColor($r('app.color.page_bg'))
.padding({
left: 12,
right: 12,
top: 8,
bottom: 8
})
.fontColor($r('app.color.txt_tips_color'))
.margin({ top: 12, bottom: 12 })
AIPause({ aiBean: this.aiBean })
AIKnowledge({ aiBean: this.aiBean })
AIBottomMenu({ aiBean: this.aiBean, createMessage: (question: string) => this.createMessage(question) })
AISendCommunity({ aiBean: this.aiBean })
AIKeyWords({ aiBean: this.aiBean })
AIKnowledgeOtherQuestion({
aiBean: this.aiBean,
createMessage: (question: string) => this.createMessage(question)
})
}
}
.padding(12)
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.borderRadius({
topLeft: 0,
topRight: 8,
bottomLeft: 8,
bottomRight: 8
})
.margin({ left: 12, right: 12 })
if (this.aiBean.answer.indexOf("您好,我是脑医汇") < 0) {
LineNew({ aiBean: this.aiBean })
}
}.justifyContent(FlexAlign.Start).width('100%')
}
}
/**
* question默认
*/
@Component
export struct AiContentTop {
@Prop question: string
build() {
Column() {
Column() {
Row() {
Image($r('app.media.ai_default_user')).width(24).aspectRatio(1)
Text('你').fontSize(14).fontColor($r('app.color.ai_user_color')).padding({ left: 12 })
}
.padding(12)
.justifyContent(FlexAlign.End).width('100%')
Row() {
Text(this.question)
.fontColor($r('app.color.black'))
.fontSize(14)
.padding(12)
.backgroundColor($r('app.color.ai_user_content'))
.borderRadius({
topLeft: 8,
topRight: 0,
bottomLeft: 8,
bottomRight: 8
})
}.justifyContent(FlexAlign.End).width('100%').padding({ right: 12 })
}.margin({ right: 12 }).width('100%')
Row() {
Image($r('app.media.ai_icon_top_logo')).width(24).aspectRatio(1)
Text('脑医汇AI问答助手').fontSize(14).fontColor($r('app.color.ai_user_color')).padding({ left: 12 })
}
.padding(12)
.justifyContent(FlexAlign.Start).width('100%')
}
}
}
/**
* 默认数据
*/
@Component
export struct defaultAi {
build() {
ListItem() {
Stack() {
Column() {
Row() {
Image($r('app.media.ai_icon_top_logo')).width(24).aspectRatio(1).margin({ left: 12 })
Text('脑医汇AI问答助手')
.padding({
left: 6,
top: 16,
right: 12,
bottom: 12
}).fontSize(14).fontColor($r('app.color.ai_user_name'))
}.width('100%').justifyContent(FlexAlign.Start)
Text('您好!欢迎使用脑医汇AI问答服务。我是您的智能医学助手。请随时向我提出您的问题,我将努力为您提供专业的回答。让我们开始吧!')
.padding({ left: 12, right: 12, bottom: 16 })
.fontSize(16)
.fontColor($r('app.color.black'))
.lineSpacing(LengthMetrics.vp(8))
}.margin({ left: 15, right: 15, top: 55 }).borderRadius(8).backgroundColor(Color.White)
Image($r('app.media.ai_chat')).width(80).aspectRatio(1).position({ right: 15, top: 15 })
}
}
}
}
/**
* 参考资料
*/
@Component
export struct AIKnowledge {
@Prop aiBean: AIChatBean
@State isOpenMore: boolean = false
build() {
if (this.aiBean.knowledgeInfo.length > 0 && this.aiBean.answerFlag === "end") {
Column() {
Text('参考资料:').fontColor($r('app.color.txt_color')).fontSize(14).padding(12).alignSelf(ItemAlign.Start)
List() {
ForEach(this.aiBean.knowledgeInfo, (item: AIKnowledgeInfo, index) => {
if (this.isOpenMore) {
knowledgeItem({ index: index, item: item })
} else {
if (index < 3) {
knowledgeItem({ index: index, item: item })
}
}
})
if (this.isOpenMore) {
ListItem() {
Text() {
Span('收起')
ImageSpan($r('app.media.ai_more_off')).width(16).aspectRatio(1).margin({ left: 6 })
}.fontColor($r('app.color.ai_info_more')).fontSize(14).padding(12).align(Alignment.Center)
}.width('100%').onClick(() => {
this.isOpenMore = false
})
} else {
ListItem() {
Text() {
Span('展开全部')
ImageSpan($r('app.media.ai_more')).width(16).aspectRatio(1).margin({ left: 6 })
}.fontColor($r('app.color.ai_info_more')).fontSize(14).padding(12).align(Alignment.Center)
}.width('100%').onClick(() => {
this.isOpenMore = true
})
}
}
}.margin({ top: 12, bottom: 12 }).borderRadius(4).backgroundColor($r('app.color.ai_info_bg'))
}
}
}
/**
* 参考资料Item
*/
@Component
export struct knowledgeItem {
@Prop index: number
@Prop item: AIKnowledgeInfo
goPageByType() {
if (this.item.type === "literature") {
promptAction.showToast({
message: '敬请期待',
alignment: Alignment.Center,
duration: 1500
});
} else if (this.item.type === "community_qa") {
promptAction.showToast({
message: '敬请期待',
alignment: Alignment.Center,
duration: 1500
});
} else if (this.item.type === "user") {
DynamicsRouter.pushUri('mine/UserCenter', {
id: this.item.userId
})
} else {
const match = this.item.url?.match(/\/info\/(\d+)\.jspx/);
if (match) {
const param = match[1]; // 截取到的参数
DynamicsRouter.pushUri('article/WebInfo', {
id: param,
url: this.item.url
})
}
}
}
build() {
ListItem() {
Text((this.index + 1) + "." + this.item.title)
.fontColor($r('app.color.txt_color'))
.fontSize(14)
.lineSpacing(LengthMetrics.vp(6))
.padding({
left: 12,
right: 12,
top: 7,
bottom: 7
})
.decoration({ type: TextDecorationType.Underline })
}.onClick(() => {
this.goPageByType()
})
}
}
/**
* 暂停
*/
@Component
export struct AIPause {
@Prop aiBean: AIChatBean
build() {
if (this.aiBean.interrupt == "T" || this.aiBean.interrupt == "S") {
Text(this.aiBean.interrupt == "T" ? "(用户暂停)" : "(连接已断开)")
.fontColor($r('app.color.txt_tips_color'))
.fontSize(14)
}
}
}
/**
* 正文渲染
*/
@Component
export struct AIContent {
@Prop aiBean: AIChatBean
build() {
if (this.aiBean.answerFlag) {
if (this.aiBean.answerFlag === "ing") {
Markdown({
content: this.aiBean.answer,
lineSpace: 8,
textLineSpace: 8,
fontStyle: {
fontColor: $r('app.color.txt_color'),
fontSize: 14,
}
}).padding(12)
.backgroundColor(Color.White)
.borderRadius({
topLeft: 0,
topRight: 8,
bottomLeft: 8,
bottomRight: 8
})
.margin({ left: 12, right: 12 })
} else if (this.aiBean.answerFlag === "end") {
Markdown({
content: this.aiBean.answer,
lineSpace: 8,
textLineSpace: 8,
fontStyle: {
fontColor: $r('app.color.txt_color'),
fontSize: 14,
}
})
//展示其他内容
}
}
}
}
/**
* 底部按钮
*/
@Component
export struct AIBottomMenu {
@Prop aiBean: AIChatBean
createMessage: (question: string) => void = (question) => {
}
@State collectStatus: string = "F"
@State feedBackStatus: string = "F"
@Consume feedBackTypeList: AIFeedBackBean[]
aboutToAppear(): void {
this.collectStatus = this.aiBean.collect
this.feedBackStatus = this.aiBean.feedback
}
/**
* 收藏
*/
async collect() {
KnowledgeApi.collectAi(this.aiBean.chatId).then((result) => {
if (result.data?.collectStatus === "F") {
this.collectStatus = "F"
} else {
this.collectStatus = "T"
promptAction.showToast({
message: '收藏成功',
alignment: Alignment.Center,
duration: 1500
});
}
})
}
dialogController: CustomDialogController | null = new CustomDialogController({
builder: AiFeedbackDialog({
aiBean: this.aiBean,
feedBackTypeList: this.feedBackTypeList,
updateFeedBack: () => {
this.updateFeedBack()
},
cancel: () => {
this.onCancel()
}
}),
autoCancel: true,
onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
dismissDialogAction.dismiss()
}
if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
dismissDialogAction.dismiss()
}
},
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: 0 },
customStyle: false,
cornerRadius: {
topLeft: 20,
topRight: 20,
bottomLeft: 0,
bottomRight: 0
},
width: '100%',
height: '60%',
borderWidth: 0,
borderStyle: BorderStyle.Dashed, //使用borderStyle属性,需要和borderWidth属性一起使用
borderColor: Color.Blue, //使用borderColor属性,需要和borderWidth属性一起使用
backgroundColor: Color.White,
})
/**
* 反馈
*/
async feedBack() {
if (this.feedBackStatus === "F") {
//弹出框
if (this.dialogController != null) {
this.dialogController.open()
}
} else {
this.cancelFeedBack()
}
}
/**
* 取消意见反馈
*/
async cancelFeedBack() {
KnowledgeApi.cancelSaveFeedback(this.aiBean.chatId).then(() => {
this.feedBackStatus = "F"
})
}
updateFeedBack() {
this.feedBackStatus = "T"
}
onCancel(): void {
if (this.dialogController != null) {
this.dialogController?.close()
}
}
build() {
Row({ space: 12 }) {
if (this.aiBean.interrupt == "T" || this.aiBean.interrupt == "S") {
//渲染暂停与点赞按钮
Text() {
ImageSpan($r('app.media.ai_re_try')).width(20).aspectRatio(1).verticalAlign(ImageSpanAlignment.CENTER)
Span('重试').fontSize(12).fontColor($r('app.color.ai_info_more'))
}.textAlign(TextAlign.Center).padding({
left: 10,
right: 10,
top: 4,
bottom: 4
})
.border({
width: 1,
color: $r('app.color.ai_info_more'),
radius: 30
}).onClick(() => {
this.createMessage(this.aiBean.question)
})
} else {
//渲染全部按钮
if (this.aiBean.answerFlag == "end") {
Text() {
ImageSpan($r('app.media.ai_share')).width(20).aspectRatio(1).verticalAlign(ImageSpanAlignment.CENTER)
Span('分享').fontSize(12).fontColor($r('app.color.ai_info_more'))
}.textAlign(TextAlign.Center)
.padding({
left: 10,
right: 10,
top: 4,
bottom: 4
})
.border({
width: 1,
color: $r('app.color.ai_info_more'),
radius: 30
})
Text() {
ImageSpan(this.collectStatus === "T" ? $r('app.media.ai_save_on') : $r('app.media.ai_save'))
.width(20)
.aspectRatio(1)
.verticalAlign(ImageSpanAlignment.CENTER)
Span('收藏').fontSize(12).fontColor($r('app.color.ai_info_more'))
}.textAlign(TextAlign.Center)
.padding({
left: 10,
right: 10,
top: 4,
bottom: 4
})
.border({
width: 1,
color: $r('app.color.ai_info_more'),
radius: 30
}).onClick(() => {
this.collect()
})
}
}
Image(this.feedBackStatus === "T" ? $r('app.media.ai_zan_on') : $r('app.media.ai_zan'))
.width(30)
.aspectRatio(1)
.padding(4)
.border({
width: 1,
color: $r('app.color.ai_info_more'),
radius: 30
})
.onClick(() => {
this.feedBack()
})
}.justifyContent(FlexAlign.Start).width('100%').padding({ top: 12 })
}
}
@CustomDialog
struct AiFeedbackDialog {
@Prop aiBean: AIChatBean
controller?: CustomDialogController
@Prop feedBackTypeList: AIFeedBackBean[]
cancel: () => void = () => {
}
updateFeedBack: () => void = () => {
}
@State text: string = ""
@State isCommit: boolean = false
@State selectType: AIFeedBackBean | null = null
clickType(feedBackId?: string): void {
this.feedBackTypeList.forEach((item) => {
if (feedBackId) {
if (item.id === feedBackId) {
item.select = true
this.selectType = item
} else {
item.select = false
}
} else {
item.select = false
this.selectType = null
}
})
let listJson = JSON.stringify(this.feedBackTypeList)
this.feedBackTypeList = []
this.feedBackTypeList = JSON.parse(listJson)
this.resetBtnStatus()
}
/**
* 重置按钮状态
*/
resetBtnStatus() {
if (this.selectType) {
this.isCommit = true
if (this.selectType.name === "其他") {
if (this.text) {
this.isCommit = true
} else {
this.isCommit = false
}
} else {
this.isCommit = true
}
} else {
this.isCommit = false
}
}
/**
* 意见反馈
*/
async feedBack() {
if (this.isCommit === true) {
KnowledgeApi.saveFeedBack(this.aiBean.chatId, this.selectType?.id?.toString()!, this.text).then(() => {
promptAction.showToast({
message: '反馈已提交',
alignment: Alignment.Center,
duration: 1500
});
this.cancel()
//状态刷新
this.updateFeedBack()
})
}
}
build() {
Stack() {
Column() {
Text('反馈').fontSize(16).fontColor($r('app.color.black')).fontWeight(600).padding(12)
Text('请选择对回答不满意的原因·')
.fontSize(12)
.fontColor($r('app.color.txt_tips_color'))
.padding({ left: 12, bottom: 12 })
List({ space: 12 }) {
ForEach(this.feedBackTypeList, (item: AIFeedBackBean) => {
ListItem() {
feedBackType({
item: item,
clickType: (feedBackId?: string) => this.clickType(feedBackId)
})
}
}
)
}.margin({ left: 12 })
TextArea({
text:
this.text,
placeholder:
'你的反馈将帮助我们更好地改进...'
})
.placeholderColor($r('app.color.txt_tips_color'))
.fontSize(14)
.onChange((value: string) => {
this.text = value
this.resetBtnStatus()
})
.constraintSize({
maxHeight: 100,
minHeight: 100,
})
.borderRadius(8)
.padding(12)
.backgroundColor($r('app.color.bg_f5f5'))
.margin(12)
Button('提交', { type: ButtonType.Normal, stateEffect: true })
.width('95%')
.backgroundColor(this.isCommit ? $r('app.color.theme_color') : $r('app.color.bg_B4D9F0'))
.borderRadius(8)
.margin({ left: 12, right: 12 })
.alignSelf(ItemAlign.Center)
.onClick(() => {
this.feedBack()
})
}.width('100%').alignItems(HorizontalAlign.Start)
Image($r('app.media.ai_feedback_close'))
.width(16)
.aspectRatio(1)
.position({ right: 1, top: 1 })
.onClick(() => {
this.cancel()
})
.margin({ right: 12 })
}
.width
('100%'
).padding({ top: 12 })
}
}
@Component
export struct feedBackType {
@Prop item: AIFeedBackBean
clickType: (feedBackId?: string) => void = (feedBackId) => {
}
build() {
Text(this.item.name)
.fontSize(14)
.fontColor(this.item.select ? $r('app.color.theme_color') : $r('app.color.black'))
.padding(12)
.constraintSize({ minWidth: 120 })
.backgroundColor(this.item.select ? $r('app.color.bg_E6F2FA') : $r('app.color.bg_f5f5'))
.borderWidth(1)
.borderColor(this.item.select ? $r('app.color.theme_color') : $r('app.color.line_D0'))
.borderRadius(8)
.textAlign(TextAlign.Center)
.onClick(() => {
this.item.select = !this.item.select
if (this.item.select) {
this.clickType(this.item.id!)
} else {
this.clickType()
}
})
}
}
/**
* 应该是不开发
*/
@Component
export struct AISendCommunity {
@Prop aiBean: AIChatBean
build() {
}
}
/**
* 关键词
*/
@Component
export struct AIKeyWords {
@Prop aiBean: AIChatBean
build() {
if (this.aiBean.answerFlag == "end") {
Column() {
Text() {
ImageSpan($r('app.media.ai_keyword'))
.width(25)
.aspectRatio(1)
.verticalAlign(ImageSpanAlignment.CENTER)
.margin({ right: 12 })
Span('关键词').fontSize(14).fontWeight(700).fontColor($r('app.color.ai_other_info'))
}.padding({ top: 12, bottom: 12 })
Flex({ space: { main: LengthMetrics.vp(10), cross: LengthMetrics.vp(10) }, wrap: FlexWrap.Wrap }) {
ForEach(this.aiBean.keywords, (title: string) => {
Text(title)
.fontColor($r('app.color.ai_other_info'))
.fontSize(14)
.padding({
left: 12,
right: 12,
top: 8,
bottom: 8
})
.backgroundColor($r('app.color.ai_keyword_bg'))
.borderRadius(30)
})
}
}.width('100%').alignItems(HorizontalAlign.Start).padding({ top: 12 })
}
}
}
/**
* 相关问题
*/
@Component
export struct AIKnowledgeOtherQuestion {
@Prop aiBean: AIChatBean
createMessage: (question: string) => void = (question) => {
}
build() {
if (this.aiBean.answerFlag == "end") {
Column() {
Text() {
ImageSpan($r('app.media.ai_question'))
.width(25)
.aspectRatio(1)
.verticalAlign(ImageSpanAlignment.CENTER)
.margin({ right: 12 })
Span('相关问题').fontSize(14).fontWeight(700).fontColor($r('app.color.ai_other_info'))
}.padding({ top: 12, bottom: 12 })
List() {
ForEach(this.aiBean.relatedQuestions, (item: string) => {
ListItem() {
Column() {
Row() {
Text(item)
.padding(12)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontColor($r('app.color.ai_other_info'))
.layoutWeight(1)
Image($r('app.media.ai_other')).width(24).aspectRatio(1)
}.width('100%')
Divider().vertical(false).strokeWidth(1).color('#F0ECE7').width('100%')
}.width('100%').alignItems(HorizontalAlign.Start)
}.onClick(() => {
this.createMessage(item)
})
})
}
}.width('100%').alignItems(HorizontalAlign.Start).padding({ top: 12 })
}
}
}
/**
* 开启新话题
*/
@Component
export struct LineNew {
@Prop aiBean: AIChatBean
build() {
if (this.aiBean.answerFlag == "end") {
Row() {
Divider()
.vertical(false)
.strokeWidth(0.5)
.color($r('app.color.ai_line'))
.width("35%")
Text("开启新话题")
.padding(12)
.fontSize(14)
.textAlign(TextAlign.Center)
.fontColor($r('app.color.ai_other_txt'))
.width("30%")
Divider()
.vertical(false)
.strokeWidth(0.5)
.color($r('app.color.ai_line'))
.width("35%")
}
.width('100%')
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceAround)
.margin({ top: 12 })
.padding({ left: 12, right: 12 })
}
}
}