import { Component, OnInit, ViewChild } from '@angular/core'; import { IonTextarea, IonCheckbox, IonList, IonButton, IonContent, IonHeader, IonInput, IonTitle, IonToolbar, IonItem, IonLabel, IonRadioGroup, IonRadio, IonDatetimeButton, IonDatetime, IonModal, IonAlert, IonBackButton, IonButtons, IonIcon, IonSelectOption, IonSelect, IonSpinner } from '@ionic/angular/standalone'; import { CloudQuery, CloudObject, Pointer } from '../../lib/ncloud'; // 确保路径正确 import { CommonModule, DatePipe } from '@angular/common'; // 导入 CommonModule import { FormsModule } from '@angular/forms'; // 导入 FormsModule import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng'; import { AlertController } from '@ionic/angular'; import { CloudUser } from '../../lib/ncloud'; import { UserService } from '../lib/user.service'; import { subscriptionLogsToBeFn } from 'rxjs/internal/testing/TestScheduler'; // 定义接口以确保类型安全 interface Questionnaire { objectId: string; createdAt: string; QuestionnaireId: string; title: string; status: string; questions: string[]; // 修改为字符串数组 } interface Question { objectId: string; createdAt: string; QuestionId: string; questionnaireId: string; // 修改为字符串 questionText: string; options: string[]; // 组 } interface Option { objectId: string; createdAt: string; OptionId: string; questionId: string; // 修改为字符串 optionText: string; isSelected: boolean; } interface QuestionnaireResult { objectId: string; createdAt: string; QuestionnaireResultId: string; userId: Pointer; questionnaireId: Pointer; answers: Pointer[]; } interface UserInterestProfile { objectId: string; createdAt: string; userId: String; QuestionnaireId: String; interestTags: String[]; content: String; } interface QuestionWithOptions extends Question { optionsData: Option[]; } @Component({ selector: 'app-interest-search', templateUrl: './interest-search.component.html', styleUrls: ['./interest-search.component.scss'], standalone: true, imports: [IonTextarea, IonCheckbox, IonList, IonButton, IonContent, IonHeader, IonInput, IonTitle, IonToolbar, IonItem, IonLabel, IonRadioGroup, IonRadio, IonDatetimeButton, IonDatetime, IonModal, CommonModule, FormsModule, IonDatetime, IonModal, IonAlert, IonBackButton, IonButtons, MarkdownPreviewModule, IonIcon, IonSelectOption, IonSelect, IonSpinner, ] }) export class InterestSearchComponent implements OnInit { //初始化当前用户 currentUser: CloudUser | null = null; // 声明 currentUser 属性,类型为 CloudUser 或 null // 固定字段 name: string = ''; birthday: string = ''; maxDate = new Date().toISOString().split('T')[0]; minDate = '1900-01-01'; //记录当前问卷的Id currentQuestionnaireId: string = ''; // 动态问卷数据 questionnaire: Questionnaire | null = null; questionsWithOptions: QuestionWithOptions[] = []; answers: { [questionId: string]: string } = {}; // 存储用户答案 //新增AI分析部分的变量 aiAnalysisResult: { interestTags: string[], content: string } | null = null; // AI 分析结果 isComplete: boolean = false; // 定义完成状态属性,用来标记是否补全完成 @ViewChild(IonModal) modal!: IonModal; // 引入 IonModal 以控制其打开和关闭 modalIsOpen: boolean = false; // 使用 isOpen 控制 Modal 的显示状态 modalContent: string = ''; // 保存弹窗的内容 gender: string = ''; age: number | null = null; occupation: string = ''; @ViewChild(IonDatetime) datetime!: IonDatetime; isAnalyzing: boolean = false; // 添加分析状态标志 // 修改问卷ID数组 questionnaires = ['q1', 'q2', 'q3']; // 确保这些ID在数据库中存在 constructor(private userService: UserService) { //this.currentUser = new CloudUser(); } // 定义方法,用于获取 组件选择的值 onDateTimeChange(event: any) { if (event && event.detail && event.detail.value) { // console.log(event.detail) // console.log(event.detail.value) this.birthday = event.detail.value.split('T')[0]; // 只保留日期部分 // 计算年龄 const birthDate = new Date(this.birthday); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age--; } this.age = age; console.log('Selected date:', this.birthday); console.log('Calculated age:', this.age); } } alertButtons = ['确定']; async ngOnInit() { try { // 在组件初始化时获取当前用户信息 this.loadCurrentUser(); console.log("加载当前用户信息"); // 重置所有状态 this.resetAllData(); console.log("刷新所有数据"); // 加载新问卷 this.currentQuestionnaireId = this.getRandomQuestionnaire(); await this.loadQuestionnaireData(this.currentQuestionnaireId); console.log("加载新问卷"); } catch (error) { console.error('初始化问卷失败:', error); const toast = document.createElement('ion-toast'); toast.message = '加载问卷失败,请重试'; toast.duration = 2000; toast.position = 'top'; toast.color = 'danger'; document.body.appendChild(toast); await toast.present(); } } // 添加重置所有数据的方法 private resetAllData() { // 重置基本信息 this.name = ''; this.gender = ''; this.age = null; this.birthday = ''; this.occupation = ''; // 重置问卷相关数据 this.answers = {}; this.questionsWithOptions = []; this.questionnaire = null; // 重置其他状态 this.modalIsOpen = false; this.modalContent = ''; this.isAnalyzing = false; this.aiAnalysisResult = null; } // 保留这个新的 getRandomQuestionnaire 方法 getRandomQuestionnaire(): string { // 随机选择一个问卷ID const randomIndex = Math.floor(Math.random() * this.questionnaires.length); const selectedId = this.questionnaires[randomIndex]; console.log('随机选择的问卷索引:', randomIndex, '问卷ID:', selectedId); return selectedId; } // 保留这个新的 loadQuestionnaireData 方法 async loadQuestionnaireData(questionnaireId: string) { try { console.log('开始加载问卷:', questionnaireId); // 清空现有数据 this.questionsWithOptions = []; this.answers = {}; this.questionnaire = null; const questionnaireQuery = new CloudQuery("Questionnaire"); questionnaireQuery.equalTo("QuestionnaireId", questionnaireId); const questionnaireObj = await questionnaireQuery.first(); if (questionnaireObj) { const questionnaireData = questionnaireObj.data as Questionnaire; this.questionnaire = { ...questionnaireData, objectId: String(questionnaireObj.id) }; console.log("加载到的问卷数据:", this.questionnaire); if (this.questionnaire.questions && this.questionnaire.questions.length > 0) { await this.loadQuestions(this.questionnaire.questions); } else { throw new Error('问卷中没有问题'); } } else { throw new Error(`未找到 QuestionnaireId 为 ${questionnaireId} 的问卷`); } } catch (error) { console.error("加载问卷数据时出错:", error); throw error; } } // 保留这个新的 saveProgress 方法 async saveProgress() { try { const surveyProgress = new CloudObject("SurveyProgress"); surveyProgress.set({ userId: { __type: "Pointer", className: "_User", objectId: "user1" }, questionnaireId: this.questionnaire?.QuestionnaireId, // 保存当前问卷ID answers: this.answers, personalInfo: { name: this.name, gender: this.gender, age: this.age, birthday: this.birthday, occupation: this.occupation }, lastUpdated: new Date(), isCompleted: false }); await surveyProgress.save(); const toast = document.createElement('ion-toast'); toast.message = '进度保存成功'; toast.duration = 2000; toast.position = 'top'; toast.color = 'success'; document.body.appendChild(toast); await toast.present(); } catch (error) { console.error('保存进度失败:', error); const toast = document.createElement('ion-toast'); toast.message = '保存失败,请重试'; toast.duration = 2000; toast.position = 'top'; toast.color = 'danger'; document.body.appendChild(toast); await toast.present(); } } async loadQuestions(questionIds: string[]) { this.questionsWithOptions = []; // 初始化问题列表 for (const questionId of questionIds) { try { const questionQuery = new CloudQuery("Question"); questionQuery.equalTo("QuestionId", questionId); const questionObj = await questionQuery.first(); if (questionObj) { const question = questionObj.data as Question; // 异步加载选项并立即显示问题 this.questionsWithOptions.push({ ...question, optionsData: [] }); this.loadOptions(question.options).then((options) => { const index = this.questionsWithOptions.findIndex( (q) => q.QuestionId === question.QuestionId ); if (index !== -1) { this.questionsWithOptions[index].optionsData = options; } }); // 可选:每加载一个题,立即触发渲染 console.log("已加载问题:", question); } } catch (error) { console.error(`加载问题 ID ${questionId} 时出错:`, error); } } } async loadOptions(optionIds: string[]): Promise { try { if (!optionIds || optionIds.length === 0) return []; const optionQuery = new CloudQuery("Option"); optionQuery.containedIn("OptionId", optionIds); // 批量查询 const optionObjs = await optionQuery.find(); return optionObjs.map((optionObj: any) => optionObj.data as Option); } catch (error) { console.error("加载选项时出错:", error); return []; } } // 保存功能(可选) async save() { try { // 实现保存逻辑,例如保存到本地存储或发送到后台 console.log("保存的答案:", this.answers); console.log("姓名:", this.name); console.log("生日:", this.birthday); } catch (error) { console.error("保存答案时出错:", error); } } // 提交功能 async submit() { try { if (!this.questionnaire) { console.error("未加载问卷数据。"); return; } // 显示加载状态 this.isAnalyzing = true; this.modalIsOpen = true; // 创建一个数组保存选的 OptionId const answersArray: string[] = []; // 遍历每个问题,获取用户选择的选�� for (const question of this.questionsWithOptions) { const selectedOptionId = this.answers[question.QuestionId]; if (selectedOptionId) { // 将选中的 OptionId 存入 answersArray answersArray.push(selectedOptionId); } } // 创建一个新的 QuestionnaireResult 对象 const questionnaireResult = new CloudObject("QuestionnaireResult"); // 设置 QuestionnaireResult 的属性 questionnaireResult.set({ QuestionnaireResultId: `qr_${new Date().getTime()}`, // 生成唯一的 QuestionnaireResultId userId: { __type: "Pointer", className: "_User", objectId: "user1" }, // 替换为实际的用户ID // 使用 Pointer 类型的引用方式来设置 questionnaireId questionnaireId: { __type: "Pointer", className: "Questionnaire", objectId: this.questionnaire.objectId }, answers: answersArray // 将选中的 OptionId 数组存入 answers 字段 }); // 保存 QuestionnaireResult 对象 await questionnaireResult.save(); console.log("问卷提交成功。"); // 构建用于 AI 模型分析提示词 const aiPrompt = this.createAiPrompt(answersArray); // 调用 AI 模型分析,强制等待结果 const aiResponse = await this.callAiModel(aiPrompt); // 如果 AI 响应有效,执行以下逻辑 if (aiResponse) { this.aiAnalysisResult = aiResponse; // 保存 AI 响应结果 await this.saveAnalysisResult(aiResponse); // 保存到数据库 // 准备显示结果 this.modalContent = this.formatContent( JSON.stringify({ interestTags: this.aiAnalysisResult.interestTags, content: this.aiAnalysisResult.content }) ); // 关闭加载状态 this.isAnalyzing = false; } } catch (error) { console.error("提交问卷时出错:", error); this.isAnalyzing = false; // 确保出错时也关闭加载状态 this.modalIsOpen = false; // 显示错误提示 const toast = document.createElement('ion-toast'); toast.message = '分析失败,请重试'; toast.duration = 2000; toast.position = 'top'; toast.color = 'danger'; document.body.appendChild(toast); await toast.present(); } } // 生成 AI 模型的提示词 createAiPrompt(answersArray: string[]): string { const questionTexts = this.questionsWithOptions.map(q => q.questionText); const optionTexts = answersArray.map(optionId => { // 找到对应的 Option,并提取其 optionText const option = this.questionsWithOptions .flatMap(q => q.optionsData) // 从每个问题的 optionsData 获取 Option 对象 .find(o => o.OptionId === optionId); return option ? option.optionText : ''; // 返回选项文本 }); return ` 您是一名专业的兴趣分析师,请根据用户填写的问卷内容以及选项分析用户的兴趣并且生成以下格式的响应: { "interestTags": ["标签1", "标签2", "标签3", "标签4"], // 生成用户最感兴趣的四个标签(如:书法、绘画、摄影) "content": "标签描述" // 针对每个标签生成简洁、生动的描述,帮助用户更清楚了解兴趣特点。描述可包括用户行为、倾向和相关建议。 } 请根据以下信息进行分析: 问题:${questionTexts.join(',')} 选项:${optionTexts.join(',')} 意: - 仅选择用户**最感兴趣**的四个标签。 - 生成的描述需要具体、生动,反映用户的兴趣深度或行为倾向。 - 请忽略与用户兴趣无关的内容。 - 标签和描述应通俗易懂,适合用户直接阅读。 `; } async callAiModel(prompt: string): Promise<{ interestTags: string[], content: string }> { try { const completion = new FmodeChatCompletion([ { role: "system", content: "您是一个专业的兴趣分析助手。" }, { role: "user", content: prompt } ]); let fullContent = ''; let count = 0; return new Promise((resolve, reject) => { completion.sendCompletion().subscribe({ next: (message: any) => { if (message.content) { try { console.log('Received content:', message.content); fullContent = message.content; // 判断消息是否完成 if (message?.complete) { this.isComplete = true; } // 如果消息完成且内容符合 JSON 格式,则解析 if (this.isComplete) { const cleanedContent = fullContent.trim(); // 检查是否有效的 JSON 格式 if (cleanedContent.startsWith('{') && cleanedContent.endsWith('}')) { try { // 清理掉换行符和多余空格 let finalContent = cleanedContent.replace(/[\r\n]+/g, ''); // 去掉换行符 finalContent = finalContent.replace(/\s+/g, ' '); // 去掉多余的空格 console.log(finalContent); // 解析 JSON const parsedResponse = JSON.parse(finalContent); // 如果解析成功并且格式正确 if (parsedResponse && parsedResponse.interestTags && parsedResponse.content) { const { interestTags, content } = parsedResponse; // 如果 content 是对象类型,转化成字符串 let contentStr = ''; if (typeof content === 'string') { contentStr = content; // 如果已经是字符串,直接使用 } else if (typeof content === 'object') { // 如果对,转换为 JSON 字符串 contentStr = JSON.stringify(content, null, 2); // 美化 JSON 字符串格式 count = 0; this.isComplete = false; } resolve({ interestTags: Array.isArray(interestTags) ? interestTags : [], content: contentStr }); } else { reject(new Error("AI 返回的内容格式不正确")); } } catch (err) { console.log(fullContent); console.error("解析 AI 响应失败:", err); reject(new Error("解 AI 响应失败")); } } else { reject(new Error("返回的内容不是有效的 JSON 格式")); } } } catch (err) { console.error("处理消息时出错:", err); reject(new Error("处理消息时出错")); } } else { if (count !== 0) { console.error("AI 返回的消息为空"); reject(new Error("AI 返回的消息为空")); } count = 1; } }, error: (err) => { console.error("AI 模型调用失败:", err); reject(new Error("AI 模型调用失败")); }, complete: () => { // 可以在这里处理完成后的操作 console.log("AI 请求完成"); } }); }); } catch (error) { console.error("AI 模型调用失败:", error); throw new Error("AI 模型调用失败"); } } // 格式化 AI 响应内容 formatContent(content: string): string { try { const contentObj = JSON.parse(content); // 构建兴趣标签 HTML const tagsHtml = contentObj.interestTags.map((tag: string) => `
${tag}
`).join(''); // 处理分析内容,将内容按句号分段并添加样式 const contentText = contentObj.content; const paragraphs = contentText .replace(/\"/g, '') // 移除转义字符如 \" .replace(/\n/g, '') // 移除换行符 \n .replace(/,/g, '') // 移除, .replace(/{/g, '') // 移除{ .replace(/}/g, '') // 移除} /// 在每个句号 "。" 后插入换行
.split('。') .filter((p: string) => p.trim()) .map((p: string) => `

${p}。

`) .join(''); // 返回完整的 HTML 结构 return `

您的兴趣标签

${tagsHtml}

个性化分析报告

${paragraphs}
`; } catch (error) { console.error('格式化 AI 响应时出错:', error); return '分析结果格式错误。'; } } // 显示分析结果 showAnalysisResult() { if (this.aiAnalysisResult) { this.modalContent = this.formatContent( JSON.stringify({ interestTags: this.aiAnalysisResult.interestTags, content: this.aiAnalysisResult.content }) ); this.modalIsOpen = true; // 打开 Modal } } // 关闭 Modal closeModal() { if (!this.isAnalyzing) { // 只有在不是分析状态时才允许关闭 this.modalIsOpen = false; } } /* // 加载当前用户 private async loadCurrentUser() { // 你可以通过 sessionToken 来检查用户是否已经登录 try { const user = await this.currentUser?.current(); if (user) { console.log('当前用户:', user); this.currentUser = user; } else { console.log('用户未登录'); this.currentUser = null; } } catch (error) { console.error('获取当前用户失败:', error); this.currentUser = null; } }*/ // 加载当前用户 private loadCurrentUser() { this.currentUser = this.userService.getCurrentUser(); if (this.currentUser) { console.log('当前用户:', this.currentUser); } else { console.log('用户未登录'); } } // 保存 AI 分析结果到数据库 async saveAnalysisResult(aiResponse: { interestTags: string[], content: string }) { try { // 获取当前用户对象 if (!this.currentUser || !this.currentUser.id) { console.log("用户未登录或没有用户ID"); return; } // 获取当前用户的 objectId const userObjectId = this.currentUser.id; if (!userObjectId) { console.log("无法获取用户的 objectId"); return; } // 创建并保存用户兴趣分析数据 const userInterestProfile = new CloudObject("UserInterestProfile"); userInterestProfile.set({ userId: { __type: "Pointer", className: "_User", objectId: userObjectId }, // 使用 currentUser 的 objectId QuestionnaireId: this.questionnaire?.QuestionnaireId, interestTags: aiResponse.interestTags, content: aiResponse.content }); await userInterestProfile.save(); console.log("分析结果保存"); } catch (error) { console.error('保存分析结果时出错:', error); } } async cancelDate() { await this.datetime.cancel(true); } async confirmDate() { await this.datetime.confirm(true); } async loadSavedProgress() { try { const progressQuery = new CloudQuery("SurveyProgress"); progressQuery.equalTo("userId", { __type: "Pointer", className: "_User", objectId: "user1" }); // progressQuery.order("-lastUpdated"); // 使用 order 方法,负号表示降序 const savedProgress = await progressQuery.first(); if (savedProgress) { const progressData = savedProgress.data; // 恢复个人信息 this.name = progressData['personalInfo']['name']; this.gender = progressData['personalInfo']['gender']; this.age = progressData['personalInfo']['age']; this.birthday = progressData['personalInfo']['birthday']; this.occupation = progressData['personalInfo']['occupation']; // 恢复答案 this.answers = progressData['answers']; console.log('已加载保存的进度'); } } catch (error) { console.error('加载保存的进度失败:', error); } } // 修改刷新问卷的方法 async refreshQuestionnaire() { try { /* // 显示加载提示 const loadingToast = document.createElement('ion-toast'); loadingToast.message = '正在更新问卷...'; loadingToast.duration = 1000; loadingToast.position = 'top'; document.body.appendChild(loadingToast); await loadingToast.present();*/ // 重置状态 this.answers = {}; this.questionsWithOptions = []; this.questionnaire = null; //当前的问卷Id let randomQuestionnaireId: string; // 重新加载其他的问卷 do { randomQuestionnaireId = this.getRandomQuestionnaire(); // 获取随机的问卷ID console.log(`随机生成的问卷ID:${randomQuestionnaireId}`); } while (randomQuestionnaireId === this.currentQuestionnaireId); // 如果相同则继续生成 this.currentQuestionnaireId = randomQuestionnaireId; console.log(`现在的问卷ID:${this.currentQuestionnaireId}`) await this.loadQuestionnaireData(randomQuestionnaireId); /* // 显示成功提示 const toast = document.createElement('ion-toast'); toast.message = '问卷已更新'; toast.duration = 2000; toast.position = 'top'; toast.color = 'success'; document.body.appendChild(toast); toast.present().catch((err) => console.error('展示 toast 时出错:', err));*/ } catch (error) { console.error('更新问卷失败:', error); const toast = document.createElement('ion-toast'); toast.message = '更新问卷失败,请重试'; toast.duration = 2000; toast.position = 'top'; toast.color = 'danger'; document.body.appendChild(toast); await toast.present(); } } }