前端AI对话功能实现攻略

一、对话内容渲染

在前端页面的 AI 对话场景中,对话内容的渲染效果直接影响用户的阅读体验和交互效率。合理选择对话格式、优化流式对话呈现、嵌入自定义内容以及实现语音播报等功能,是提升整体体验的关键。

对话格式选择

MarkDown
  • 作为一种轻量级标记语言,语法简洁易懂,能快速实现文本的加粗、斜体、列表、链接等格式渲染。在 AI 对话中,若回复内容以文字为主,且需要简单的排版区分(如强调重点信息、罗列步骤等),MarkDown 是不错的选择。通过轻量的前端插件支持即可实现渲染,对移动端 H5 的性能影响较小,适合追求轻量化的场景
渲染插件-常规选择
  • 当对话内容包含更丰富的样式,如复杂的表格、代码块、图片混排等,常规的 MarkDown 可能无法满足需求,此时可选择专业的渲染插件。例如,markdown-it、showdown.js、react-markdown、vue-markdown等等,能灵活配置渲染规则,支持自定义标签。这类插件兼容性较好,能适配多数移动浏览器,适合对对话样式有一定要求的场景。
渲染插件-编辑能力
  • 若业务场景要求用户能编辑 AI 生成的对话内容(如修改文本、调整格式后保存或分享),则需要选择具备编辑能力的渲染插件。
    • TinyMCE是一款功能强大的富文本编辑器,支持实时预览、格式刷、表格编辑等功能,且能与 AI 对话接口无缝集成,实现 “生成 - 编辑 - 提交” 的闭环。
    • CKEditor提供了丰富的插件生态,可根据需求扩展图片上传、代码编辑等功能,适合对编辑体验要求较高的场景。
    • lexical 是一个功能强大的JavaScript库,专为构建富文本编辑器而设计。它采用独特的架构,允许开发者以灵活的方式创建和定制文本编辑体验。可以用于处理用户输入的文本,对其进行语法分析、语义理解预处理等操作,同时支持流畅的文本显示与交互,为AI对话功能提供稳定且高效的文本处理基础
扩展场景
  • 如何对ai回答的内容实现编辑功能?
  • 如何对ai回答中的片段实现再润色功能?

流式对话

流式对话能让 AI 的回复内容逐字或逐句呈现,减少用户等待感,提升交互流畅度。实现流式对话一般使用 SSE(Server-Sent Events)与后端建立长连接,后端在生成内容的过程中持续向前端推送数据。前端接收到数据后,需实时更新对话界面。

一般有两种方式可以发起SSE长连接请求

使用EventSource对象

EventSource是浏览器原生支持的 SSE API,使用简单且兼容性良好。示例代码如下:

const eventSource = new EventSource('/your-api-url');
eventSource.onmessage = function(event) {const data = JSON.parse(event.data);// 处理后端推送的数据,更新对话界面// ......
};
eventSource.onerror = function(error) {console.error('SSE连接错误:', error);eventSource.close();
};

使用EventSource时,默认会自动重连,适合对连接稳定性要求高的场景。但它仅支持 HTTP/HTTPS 协议,且无法自定义请求头,以及传递复杂的请求参数。

使用fetch API 模拟 SSE

通过fetch发起 GET 请求,并手动处理响应流,可实现类似 SSE 的效果。此方式更灵活,能自定义请求方法、请求头,但需要开发者自行处理连接管理和错误重连逻辑:

async function createSSEStream() {const response = await fetch('/your-api-url', {method: 'GET', // 也可以是Postheaders: {'Content-Type': 'text/event-stream',// 可添加自定义请求头参数}});const reader = response.body.getReader();const decoder = new TextDecoder('utf-8');// while响应流式数据while (true) {const { done, value } = await reader.read();if (done) break;const text = decoder.decode(value, { stream: true });const lines = text.split('\n').filter(line => line.trim() !== '');for (const line of lines) {if (line.startsWith('data:')) {const data = JSON.parse(line.slice(5).trim());// 处理后端推送的数据,更新对话界面// ......}}}
}

使用fetch模拟 SSE 时,可根据业务需求灵活配置请求参数,例如添加身份验证信息、调整请求超时时间等,适用于对请求定制化要求较高的复杂场景。

场景扩展
  • 如何中断长连接

对话嵌入自定义内容-echarts

在 AI 对话中嵌入图表(如数据可视化、趋势分析图)能让信息更直观易懂,ECharts 是实现这一功能的常用工具。

echarts提示词
  • 前端需要向 AI 传递明确的提示信息,说明需要生成图表的类型(如折线图、柱状图)、数据维度(如时间、数值)、标题等内容。例如,提示词可以是 “请根据用户问题,返回图表数据,x 轴为日期,y 轴为活跃人数,标题为‘xxxx表’,数据格式为echarts折线图的option, 以JSON 格式输出”。
渲染echarts
  • 一般将echarts提示词调整稳定以后,需要结合你选择的md渲染插件,定义一套规则来渲染echarts,推荐使用规则:```echart {option} ```,以下有两个实践中的实例
    • markdown-it(自定义代码块规则)
// <template>
// 	<div
// 		class="message-wrap"
// 		v-html="renderedContent"
// 	/>
// </template>
import { nextTick, ref, watchEffect } from 'vue'
import Markdown from 'markdown-it'
import type { Options } from 'markdown-it'
import highlight from 'highlight.js'
import * as echarts from 'echarts'const props = defineProps<Props>()
const renderedContent = ref('')const echartMap = ref(new Map())
const echartRendered = ref<string[]>([])
const mdOptions: Options = {linkify: true,typographer: true,breaks: true,langPrefix: 'language-',// 代码高亮highlight(str, lang) {if (lang && highlight.getLanguage(lang)) {try {return '<pre class="hljs"><code>' + highlight.highlight(lang, str, true).value + '</code></pre>'} catch (__) {}}return ''},
}const md = new Markdown(mdOptions)
const defaultRender = md.renderer.rules.fence
md.renderer.rules.think = (tokens, idx) => {return `<div class="think-block">${md.utils.escapeHtml(tokens[idx].content)}</div>`
}
md.renderer.rules.fence = (tokens, idx, options, env, self) => {const token = tokens[idx]if (token.info.trim() === 'echart') {const chartId = `chart-${props?.id}-${idx}`try {if (!echartMap.value.has(chartId)) {const option = JSON.parse(token.content)console.log('解析图表配置成功')echartMap.value.set(chartId, option)}return `<div id='${chartId}' style='width:calc(100vw - 12px * 2 - 16px * 2);height:calc((100vw - 12px * 2 - 16px * 2) * 0.8)'></div>`} catch (error) {console.error('解析图表配置失败', token.content, error)return `<div id='${chartId}' class='chart-loading' style='width:calc(100vw - 12px * 2 - 16px * 2);height:calc((100vw - 12px * 2 - 16px * 2) * 0.8)' >图表加载中...</div>`}}return defaultRender(tokens, idx, options, env, self)
}// 添加think标签渲染规则watchEffect(() => {renderedContent.value = md.render(props?.content)nextTick(() => {// 等待 DOM 更新后初始化图表echartMap.value.forEach((option, id) => {const container = document.getElementById(id)const width = container?.getBoundingClientRect().widthif (!container) {return}const chart = echarts.init(container, '', {height: width * 0.8,devicePixelRatio: 2,})echartRendered.value.push(id)chart.setOption({ ...option, animation: false, animationDurationUpdate: 0 })})})
})
  • react-markdown自定义代码块规则
const MarkdownContent = () => {const resize = useRef();const [resizeMap, setResizeMap] = useState([]); // 多图表尺寸动态优化useEffect(() => {if (!resize.current) {resize.current = () => {resizeMap.forEach((resize) => resize());};window.addEventListener('resize', resize.current);}return () => {if (resize.current) {window.removeEventListener('resize', resize.current);resize.current = null;}};}, [resizeMap]);const [renderMap, setRenderMap] = useState({}); // 解决图表频繁闪动、以及只有第一个图表生效的问题useEffect(() => {const map = [];Object.keys(renderMap).forEach((echartId) => {const item = renderMap[echartId];if (!item.rendered) {const echart = echarts.init(item.dom, '', {devicePixelRatio: 2,height: 300,width,});echart.setOption(item.option);item.rendered = true;map.push(echart.resize);}});setResizeMap([...resizeMap, ...map]);}, [renderMap]);const createEchartDom = (echartId, option) => {const dom = document.createElement('div');dom.id = echartId;dom.style.width = '100%';dom.style.maxWidth = '100%';dom.style.height = '300px';// 解决图表频繁闪动、以及只有第一个图表生效的问题setRenderMap({...renderMap,[echartId]: {dom,option,rendered: false,},});return dom;};const codeRender = (props: any) => {const { children, className, ...rest } = props;const match = /language-(\w+)/.exec(className || '');if (match && match[1] === 'echart') {try {const startLine = props.node.position.start.line;const echartId = `${id}-${startLine}`;const option = {...JSON.parse(children),animation: false,animationDurationUpdate: 0,};let domif (!renderMap[echartId]) {dom = createEchartDom(echartId, option);} else {dom = renderMap[echartId].dom}return (<divstyle={{width: '100%',height: `${300}px`,}}ref={(node) => node?.appendChild(dom)}/>);} catch {return (// 图表规则解析失败<divstyle={{width: '100%',height: `${300}px`,display: 'flex',justifyContent: 'center',alignItems: 'center',border: '1px solid #dcdfe6',borderRadius: 8,background: '#f5f7fa',}}>图表加载中...</div>);}}return  <code {...rest} className={className}>{children}</code>};return (<Markdowncomponents={{code: (props: any) => codeRender(props, width),}}>{content}</Markdown>);
};
流式图表渲染
  • 上面两步实现了在ai对话中实现图表渲染的能力,但是通常情况下对话过程是流式,会出现以下几个问题:
    • 图表规则解析失败:通常发生在 option 处于正在生成中的情况下,解决办法就是加一个图表加载样式
    • 图表会频繁闪动:通常发送在 option 已经生成完成,但是回答仍然在生成中的情况下,原因markdown渲染插件会生成新的echart容器,频繁卸载挂载dom元素,解决办法将已经生成好的option 和其绑定的图表容器缓存起来,每次markdown渲染复用图表容器
    • 只有第一个图表生效:通常发送在一个会话或一个回答中出现多个图表的情况下,解决办法就是配合生成独立的id,并配合缓存渲染图表
场景扩展
  • 如何实现ai回答中的渲染思考内容?

语音播报

语音播报能让用户在不便查看屏幕时获取对话内容,提升使用场景的灵活性。

长文本低延迟播报优化
  • 对于长文本的语音播报,需解决延迟和卡顿问题。可采用分段播报策略,将长文本按标点符号或固定长度分割成多个片段,并发加载文本的语音数据,同时使用 audio 逐段播报,减少等待时间。另外,可根据网络状况动态调整分段长度,在网络较差时减小片段长度,避免因数据传输延迟导致播报中断。还可以通过缓存已播报过的文本语音,当用户重复收听时直接调用缓存,提升响应速度
  • 参考方法:
/** 语音播放 */
export const useSpeech = content => {const audioDom = document.createElement('audio')const audioRef = useRef(audioDom)const { read } = useSpeechWithSse() // read为文本转音频请求方法 返回promiseconst [isPlaying, setIsPlaying] = useState(false)const [playEnded, setPlayEnded] = useState(true)const [isLoading, setIsLoading] = useState(false)const [reedList, setReedList] = useState([])const [readIndex, setReadIndex] = useState(0)const pause = useCallback(() => {if (audioRef.current) {audioRef.current.pause()setIsPlaying(false)setPlayEnded(true)setIsLoading(false)setReedList([])}}, [])const speech = useCallback(async () => {setReadIndex(0)try {setIsLoading(true)// 分割成多个片段let strIndex = 0let curText = ''let textlist = []while (strIndex < content.length) {curText = `${curText}${content[strIndex]}`if ([',', '。', ';', '?', ',', '!'].includes(content[strIndex]) ||curText.endsWith('\n')) {const formatedText = curText.replace(/[\n#*-]+/g, '').trim()if (formatedText) {textlist.push(formatedText)curText = ''}}strIndex++}if (curText) {textlist.push(curText)curText = ''}strIndex = 0setIsPlaying(true)// 对文本片段的请求并发做出限制,500毫秒添加一个请求const tempReedList = []const timer = setInterval(() => {if (textlist.length === 0) {clearInterval(timer)} else {const nextText = textlist.shift()tempReedList.push(read({ text: nextText }))setReedList(tempReedList)}}, 500)} catch (error) {console.error('播放失败:', error)} finally {// setIsLoading(false);}}, [read, content, setIsLoading, audioRef])const handleRead = useCallback(() => {if (isPlaying) {pause()} else {speech()}}, [isPlaying, speech, pause])// 监听reedList 依次播报语音片段useEffect(() => {const loadRead = async () => {if (playEnded && isPlaying && reedList.length > readIndex) {setPlayEnded(false)const curReadUrl = await reedList[readIndex]setReadIndex(readIndex + 1)if (curReadUrl && audioRef.current) {audioRef.current.src = curReadUrlsetIsLoading(false)}}if (reedList.length > 0 && reedList.length === readIndex) {setPlayEnded(true)setIsPlaying(false)setReadIndex(0)setReedList([])}}loadRead()}, [playEnded, isPlaying, reedList])useEffect(() => {// 处理音频播放结束const handleEnded = () => {setPlayEnded(true)}const handleOnload = () => {setPlayEnded(false)audioRef.current?.play()}// 处理音频播放错误const handleError = e => {console.error('音频播放错误:', e)setPlayEnded(true)setIsPlaying(false)setIsLoading(false)}const audio = audioRef.currentif (audio) {audio.addEventListener('ended', handleEnded)audio.addEventListener('error', handleError)audio.addEventListener('loadeddata', handleOnload)}return () => {if (audio) {audio.removeEventListener('ended', handleEnded)audio.removeEventListener('error', handleError)audio.removeEventListener('loadeddata', handleOnload)URL.revokeObjectURL(audio.src)}}}, [])return { handleRead, isPlaying, isLoading }
}
场景扩展
  1. 如何实现回答边生成边播报语音?

二、对话输入框

对话输入框作为用户与 AI 交互的核心入口,在移动端 H5
页面设计中需兼顾便捷性高效性。从交互设计角度,通过语音输入与文字输入双模式,降低用户输入成本,提升交互效率。在视觉层面,可以将输入的部分文本定制化样式功能,例如提供预设选项,优化用户输入体验。

富文本格式输入

集成富文本编辑器,支持用户输入加粗、斜体、列表、链接等多样化格式内容。在 AI 对话场景中,用户可通过富文本输入详细描述需求,AI 根据格式化后的文本进行更精准的理解与回复,提升交互效率。

  • 以下是通过lexical 富文本插件实现的效果:

具有定制样式、交互功能的文本

在ai对话中,输入框内支持预设的问题模板(定制的文本节点样式与交互)是提升用户输入体验的关键。

  • 以下是通过lexical 富文本插件实现的效果:

隐藏提示词

高程度定制化的ai对话应用,往往需要给用户输入问题补充提示词,同时要避免提示词干扰用户体验,所以需要隐藏提示词

  1. 可以将提示词以 CSS 样式方式隐藏;
  2. 也可以在用户触发对话时,通过 JavaScript 动态将提示词注入拼接;

语音识别

语音识别功能极大提升了 H5 页面的交互便捷性,尤其契合移动端用户碎片化、多场景的使用需求。在复杂的移动网络环境与多变的用户输入场景下,通过多重技术优化实现高效交互

快速识别
  • 借助高性能语音识别 API,实现毫秒级响应。通过优化网络请求与数据处理流程,减少语音数据上传、识别及结果返回的耗时,让用户感受 “即说即现” 的流畅体验。
场景扩展
  • 如何做到逐字、词识别?

停止生成

在 AI 生成内容的过程中,当用户发现 AI 输出内容与预期不符、生成方向偏离主题,或临时调整创作思路时,可通过点击 “停止生成” 按钮。点中断AI 生成进程,避免无效内容的持续输出。同时,系统会保留已生成的内容片段,用户可基于此进行修改、补充或重新发起生成指令,实现高效且个性化的内容创作体验。

eventSource (sse) 停止链接
  • 可以通过调用 EventSource 实例的close()方法来停止与服务器的连接,不再接收新的事件流数据。
const eventSource = new EventSource('your_sse_url')
// 需要停止链接时执行
eventSource.close()
// 果希望在关闭后进行一些清理操作,可以通过监听close事件实现
eventSource.addEventListener('close', function() { console.log('SSE connection closed'); })
fetch (sse) 停止链接
  • 使用<font style="color:rgb(0, 0, 0);">fetch</font>模拟 SSE 时,通常是通过不断轮询获取数据来模拟流式传输。要停止链接,在<font style="color:rgb(0, 0, 0);">fetch</font>请求中传入<font style="color:rgb(0, 0, 0);">signal</font>参数来终止请求
const controller = new AbortController(); 
const signal = controller.signal; 
fetch('your_url', { signal }).then(response => response.text()).then(data => console.log(data))
// 需要停止时调用,  请求将被立即终止,并抛出AbortError异常
controller.abort()
场景扩展
  • 如何重新生成回答?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93709.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93709.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解Redis持久化:让你的数据永不丢失

1 Redis持久化概述 1.1 什么是Redis持久化 Redis作为一个高性能的内存数据库,默认情况下数据存储在内存中,这意味着一旦服务器重启或发生故障,内存中的数据将会丢失。为了保证数据的持久性和可靠性,Redis提供了持久化机制,将内存中的数据保存到磁盘中。 持久化是Redis实…

IC验证 AHB-RAM 项目(二)——接口与事务代码的编写

目录准备工作接口相关代码编写事务相关代码编写准备工作 DVT&#xff08;Design and Verification Tools&#xff09;是一款专门为 IC 验证打造的 IDE 插件&#xff0c;可以理解为智能的 Verilog/System Verilog 编辑器&#xff0c;在 VS Code、Eclipse 软件中使用。 接口相关…

基于Spring Boot的智能民宿预订与游玩系统设计与实现 民宿管理系统 民宿预订系统 民宿订房系统

&#x1f525;作者&#xff1a;it毕设实战小研&#x1f525; &#x1f496;简介&#xff1a;java、微信小程序、安卓&#xff1b;定制开发&#xff0c;远程调试 代码讲解&#xff0c;文档指导&#xff0c;ppt制作&#x1f496; 精彩专栏推荐订阅&#xff1a;在下方专栏&#x1…

大模型的底层运算线性代数

深度学习的本质是用数学语言描述并处理真实世界中的信息&#xff0c;而线性代数正是这门语言的基石。它不仅提供了高效的数值计算工具&#xff0c;更在根本上定义了如何以可计算、可组合、可度量的方式表示和变换数据。 1 如何描述世界&#x1f4ca; 真实世界的数据&#xff08…

Rust 中 i32 与 *i32 的深度解析

Rust 中 &i32 与 *i32 的深度解析 在 Rust 中&#xff0c;&i32 和 *i32 是两种完全不同的指针类型&#xff0c;它们在安全性、所有权和使用方式上有本质区别。以下是详细对比&#xff1a; 核心区别概览 #mermaid-svg-rCa8lLmHB7MK9P6K {font-family:"trebuchet ms…

【PyTorch项目实战】OpenNMT本地机器翻译框架 —— 支持本地部署和自定义训练

文章目录一、OpenNMT&#xff08;Neural Machine Translation&#xff0c;NMT&#xff09;1. 概述2. 核心特性3. 系统架构4. 与其他翻译工具的区别二、基于 OpenNMT-py 的机器翻译框架1. 环境配置&#xff08;以OpenNMT-py版本为例&#xff09;&#xff08;1&#xff09;pip安装…

基于prompt的生物信息学:多组学分析的新界面

以前总以为综述/评论是假大空&#xff0c;最近在朋友的影响下才发现&#xff0c;大佬的综述/评论内容的确很值得一读&#xff0c;也值得分享的。比如这篇讲我比较感兴趣的AI辅助生信分析的&#xff0c;相信大家都是已经实践中用上了&#xff0c;看看大佬的评论&#xff0c;拓宽…

Nacos-8--分析一下nacos中的AP和CP模式

Nacos支持两种模式来满足不同场景下的需求&#xff1a;AP模式&#xff08;强调可用性&#xff09;和CP模式&#xff08;强调一致性&#xff09;。 这两种模式的选择主要基于CAP理论&#xff0c;该理论指出在一个分布式系统中&#xff0c;无法同时保证一致性&#xff08;Consist…

水闸安全监测的主要核心内容

水闸安全监测是指通过一系列技术手段和管理措施&#xff0c;对水闸的结构状态、运行性能及环境条件进行实时或定期的观测与评估&#xff0c;以确保水闸在设计寿命期内的安全性和可靠性。其核心目标是及时发现潜在的安全隐患&#xff0c;防止事故发生&#xff0c;保障水利工程的…

嵌入式系统学习Day19(数据结构)

数据结构的概念&#xff1a; 相互之间存在一种或多种特定关系的数据元素的集合。数据之间关系&#xff1a;逻辑关系&#xff1a;集合&#xff0c;线性&#xff08;1对1&#xff0c;中间位置的值有且仅有一个前驱&#xff0c;一个后继&#xff09;&#xff0c;树&#xff08;1对…

Pandas中数据清理、连接数据以及合并多个数据集的方法

一、简介1.数据清理的重要性&#xff1a;在进行数据分析前&#xff0c;需进行数据清理&#xff0c;使每个观测值成一行、每个变量成一列、每种观测单元构成一张表格。2.数据组合的必要性&#xff1a;数据整理好后&#xff0c;可能需要将多张表格组合才能进行某些分析&#xff0…

JavaSSM框架从入门到精通!第二天(MyBatis(一))!

一、 Mybatis 框架1. Mybatis 框架简介Mybatis 是 apache 的一个开源项目&#xff0c;名叫 iBatis &#xff0c;2010 年这个项目由 apache 迁移到了 google&#xff0c;并命名为 Mybatis&#xff0c;2013 年迁移到了 GitHub&#xff0c;可以在 GitHub 下载源码。2. Mybatis 的下…

Linux下Mysql命令,创建mysql,删除mysql

在 Linux 系统下&#xff0c;您可以通过命令行来创建和删除 MySQL 数据库。以下是详细的操作步骤&#xff0c;包括创建和删除数据库、用户&#xff0c;以及常见的相关管理命令。1. 登录 MySQL在执行任何 MySQL 操作之前&#xff0c;需要先登录 MySQL。1.1 使用 root 用户登录 M…

假设检验的原理

假设检验是统计学中用于判断样本数据是否支持某个特定假设的方法。其核心思想是通过样本数据对总体参数或分布提出假设&#xff0c;并利用统计量来判断这些假设的合理性。假设检验的基本步骤如下&#xff1a;1. 假设&#xff08;Hypothesis&#xff09;在统计学中&#xff0c;假…

信号、内存共享等实现

信号&#xff08;signal&#xff09;#include <signal.h> #include <stdio.h> #include <unistd.h>void handler(int sig) {printf("收到信号: %d\n", sig); }int main() {signal(SIGUSR1, handler); // 注册用户自定义信号printf("进程 PI…

《从日常到前沿:AI 在教育、医疗、制造业的真实落地案例》文章提纲

引言&#xff1a;AI 落地的多元图景​简述 AI 从实验室走向实际应用的发展趋势​说明选择教育、医疗、制造业的原因 —— 覆盖民生与基础产业&#xff0c;落地场景具有代表性​AI 在教育领域的落地案例​个性化学习&#xff1a;如某在线教育平台利用 AI 分析学生学习数据&#…

决策树(1)

一、树模型与决策树基础决策树概念&#xff1a;从根节点开始一步步走到叶子节点得出决策&#xff0c;所有数据最终都会落到叶子节点&#xff0c;既可用于分类&#xff0c;也可用于回归。树的组成根节点&#xff1a;第一个选择点。非叶子节点与分支&#xff1a;中间决策过程。叶…

电视系统:开启视听新时代

在当今数字化浪潮席卷的时代&#xff0c;电视领域正经历着一场深刻的变革&#xff0c;而电视系统无疑是这场变革中的耀眼明星。简单来讲&#xff0c;电视系统就是互联网协议电视&#xff0c;它宛如一座桥梁&#xff0c;巧妙地利用宽带有线电视网&#xff0c;将多媒体、互联网、…

字节开源了一款具备长期记忆能力的多模态智能体:M3-Agent

猫头虎AI分享&#xff5c;字节开源了一款具备长期记忆能力的多模态智能体&#xff1a;M3-Agent 近年来&#xff0c;多模态大模型的发展迅猛&#xff0c;但如何赋予智能体类似人类的长期记忆能力&#xff0c;一直是研究中的核心挑战。字节跳动开源的 M3-Agent&#xff0c;正是面…

第十六届蓝桥杯青少组C++省赛[2025.8.10]第二部分编程题(6、魔术扑克牌排列)

参考程序&#xff1a;#include<bits/stdc.h> using namespace std; long long dp[105]; long long c(int n) {dp[0] 1;for(int i1; i< n; i){for(int j0; j<i; j){dp[i] dp[j] * dp[i -1-j];}}return dp[n]; } int main() {int n;cin >> n;cout <<c(n…