谷歌 antigravity 登录上了怎么解决,大佬求助
梯子开启了 tun 模式也不行
提示‘There was an unexpected issue setting up your account. Please try again later’
xiaohack博客专注前沿科技动态与实用技术干货分享,涵盖 AI 代理、大模型应用、编程工具、文档解析、SEO 实战、自动化部署等内容,提供开源项目教程、科技资讯日报、工具使用指南,助力开发者、AI 爱好者获取前沿技术与实战经验。
梯子开启了 tun 模式也不行
提示‘There was an unexpected issue setting up your account. Please try again later’
我为什么这么说呢?
我有一个脚本,第一运行是 8S,第二次是 0.3S,再次相差这么大只能是硬盘休眠了吧,不可能是其他原因吧.
我一般用阿里云和清华,还有其他高校镜像站没列出来,可以在下面的高校镜像站查看。
阿里云:
镜像下载、软件源、DNS 服务器、NTP 服务器
https://developer.aliyun.com/mirror/
公网访问地址
https://mirrors.aliyun.com/
ECS VPC 网络访问地址
http://mirrors.cloud.aliyuncs.com/
ECS 经典网络访问地址
http://mirrors.aliyuncs.com/
腾讯云:
https://mirrors.tencent.com/
公网访问地址:
http://mirrors.tencent.com/
内网访问地址:
http://mirrors.tencentyun.com/
华为云:
https://mirrors.huaweicloud.com/home
清华:
系统镜像、软件源、应用软件下载、字体下载
https://mirrors.tuna.tsinghua.edu.cn/
https://mirrors.tuna.tsinghua.edu.cn 自动选择
https://mirrors6.tuna.tsinghua.edu.cn 只解析 IPv6
https://mirrors4.tuna.tsinghua.edu.cn 只解析 IPv4
中科大:
系统镜像、软件源、软件下载
https://mirrors.ustc.edu.cn/
mirrors.ustc.edu.cn 自动解析
ipv4.mirrors.ustc.edu.cn IPv4 线路
ipv6.mirrors.ustc.edu.cn IPv6 线路
cernet.mirrors.ustc.edu.cn 教育网线路
chinanet.mirrors.ustc.edu.cn 电信线路
unicom.mirrors.ustc.edu.cn 联通线路
cmcc.mirrors.ustc.edu.cn 移动线路
rsync.mirrors.ustc.edu.cn Rsync 线路
北大:
https://mirrors.pku.edu.cn/
浙大:
https://mirrors.zju.edu.cn/
高校镜像:
https://mirrors.cernet.edu.cn/site
阿里云 Maven:
https://maven.aliyun.com/
飞致云(凌霞软件)docker:
https://docker.1panel.live
说明: https://bbs.fit2cloud.com/t/topic/5886
七牛云 golang:
https://goproxy.cn/
我在济南,现在开卡送 4 个月线上会员,但是还在纠结开不开
入侵排查:简单理解是针对权限维持技术的排查。
权限维持技术:攻击者入侵系统(Getshell)后,为了防止失去权限而通过持久化配置项保持权限的手段,包括注册表、计划任务、后面恶意账户等等。
1、收集系统主机的版本,右键此电脑->属性或者win+R->输入winver

会看到操作系统的版本,为什么要收集系统版本呢?因为根据版本就可以找到这个版本下的漏洞,如权限提升漏洞,可以根据提权漏洞的特点进行排查,如特点的日志等。
2、收集操作系统的其他信息,win+R->输入msinfo32
其中重点关注环境变量,另一种查看环境变量的方法是打开命令行输入set

3、关注环境变量的原因:环境变量劫持:我们都知道,命令行执行一条命令其实是执行一个.exe文件,而如果执行的.exe文件对应的命令不在当前目录,就会出现一种优先级,就是操作系统会找环境变量中指定的.exe文件,然后执行,而如果既不在目录,又不在环境变量中,就会提示不是内部命令。这样一来,想象一个场景,如果内部人员告诉你,自己执行ipconfig命令时,没有正常输出内容或者有安全设备提示有恶意外连等等时,你想拿正常的ipconfig.exe程序进行恶意文件分析,但是没有任何问题,就该注意是否有环境变量劫持了。这里简单做个实验,在环境变量中配置木马文件,
执行文件,并未出现正常内容,
并且C2中成功上线,
这里仅仅是简单的举例,这个例子很容易被发现异常,更隐蔽的做法是,也可以通过将正常的文件路径和木马路径绑在一起,效果就是正常程序和木马都运行了,那我们就可能在这一块儿很难发现恶意程序了。
4、网络连接信息收集,主要的命令netstat -ano、ipconfig /all、netstat -rn等等
这里重点关注分析状态为ESTABLISHED的IP地址,也可以借助威胁分析平台。netstat -rn是网络的路由信息
5、进程排查,主要方法是命令tasklist /v,可以看到进程的列表,
也可以在任务管理器->详细信息查看正在运行的进程
还有命令wmic process get * /value,格式化输出
这里比较重要的是CommandLine,它显示的是执行这个程序时所输入的完整命令,比如这个
为什么需要关注这个呢?我们知道,在C2工具生成有效载荷时,会有PowerShell Command,


执行成功后上线,
在通过wmic process get * /value查看,就能发现恶意进程。
这里也可以用条件表达式,直接过滤获取想看的可疑进程。
1、恶意用户:使用命令net user可以进行查看用户等等操作,这是添加了一个用户
我们也可以将用户进行隐藏,就是在用户后面加一个$符号,我们发现使用net user查询后并没有发现这个用户的信息,就是被隐藏了
但是这种方法对应其它命令和图形化注销时都能看到,因此这种隐藏用户还是比较鸡肋的。所以一些攻击者就会创建一些特定情况下存在的克隆账户,比如在域环境中存在hrbtgt用户,可能反而不会引起运维人员的关注。
2、排查:命令wmic useraccount get * /value,这个会显示所有用户的详细信息,包括刚刚创建的隐藏用户
另外通过注册表也可以看到用户,包括前面 提到的克隆账户其实就是把注册表中的信息进行调换,但是其实上面这条命令已经可以解决很多问题了。
1、注册表下的RUN子键和RUNONCE(只运行一次)子键开机启动项:
这里的启动项是通过管理员账户进行修改,但是在任意用户用户登录时运行,换句话说就是任意用户触发登录时都会运行,举个例子,重启电脑,进行登录,看会不会有cmd.exe运行
cmd.exe自启动
上面是其中一个目录,在注册表中还有一个其他目录也存在这个子键
那么区别显而易见了,就是前者是所以用户登录时触发,后者是只有当前用户登录时触发。另外,需要说明的是前者在前面说到了,只有管理员权限才能修改写入,普通用户只能修改后面的目录的内容。
2、services.msc服务文件:服务文件比较特殊的一点是它不需要用户登录,那么也就意味着它的可利用性更强,但是,创建服务文件的启动项必须是管理员权限才能创建的,同时当攻击者获取到权限时也会获取到system的权限。
这里配合C2做个示范,在C2工具中,有专门针对服务的程序,正常运行时运行不了,只能配合服务运行
这个命令就是进行服务启动项的命令(sc create 服务名称 binpath= "自启动的程序" start="auto" obj="服务运行时的权限")

就出现这个服务了,可以看到这个服务是没有描述的,除此之外,为了更加隐蔽,攻击者可能会添加其他字段来让这个服务更加隐蔽,这里讲排查,就不深入了
接下来进行服务启动
C2上线,并且权限很高,是system权限
另外重启一下看效果,这里重启就不需要登录,重启后上线
而且未登录
3、gpedit.msc本地组策略启动项:

点开启动->添加->添加恶意程序
这个依旧不需要登录,就可以上线C2
4、计划任务:命令schtasks
创建计划任务可以用命令schtasks /create /tn "计划任务名称" /tr "计划任务的所在位置" /sc minute /mo 1,表示一分钟执行一次
另外,计划任务图形化界面也可以操作
1、这个有很多,比如说火绒、D盾、windows安全基线核查加固助手
1、如果知道恶意程序的名称,可以直接去注册表搜索,持久化的东西大多数都可以用注册表搜出来,比如前面的服务文件、本地组策略文件和计划任务等等

2、那么如何找到恶意程序的名称呢?比较重要的一点就是确定时间线,我们可以在文件资源管理器中,指定日期或日期范围进行搜索
另外也可以用everything工具进行搜索,这个工具也有特定的语法可以筛选和过滤
对应入侵排查而言,这些仅仅可以解决一下基础的持久化的问题,而还有很多高端的不常见的甚至从未出现过的持久化手段需要更多的深入了解和学习的。
发版时间:v3.9.1 | 2026-01-28 JeecgBoot 平台提供了一套完善的AI应用管理系统模块,是一套类似 JeecgBoot低代码平台,可以应用在任何J2EE项目的开发中,支持信创国产化。尤其适合SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA)、企业资源计划系统(ERP)、客户关系管理系统(CRM)、AI知识库等,其半智能手工Merge的开发方式,可以显著提高开发效率70%以上,极大降低开发成本。 又是一个全栈式 AI 开发平台,快速帮助企业构建和部署个性化的 AI 应用。 信创兼容说明 微服务方式快速启动 AI流程编排 MCP和工具管理 AI知识库(支持各种文档格式,尤其markdown适配很好) AI工具箱 AI聊天助手 AI写文章 Online AI建表 欢迎吐槽,欢迎star~项目介绍
JeecgBoot是一款集成AI应用的,基于BPM流程的低代码平台,旨在帮助开发者快速实现低代码开发和构建、部署个性化的 AI 应用。 前后端分离架构Ant Design&Vue3,SpringBoot,SpringCloud,Mybatis,Shiro,强大的代码生成器让前后端代码一键生成,无需写任何代码! 成套AI大模型功能: AI模型、AI应用、知识库、AI流程编排、AI对话等; 引领AI低代码开发模式, 帮助Java项目解决80%的重复工作,让开发更多关注业务,提高效率,同时又不失灵活性!
源码下载
升级日志
本次升级对 AI 平台进行了全面增强,升级 LangChain4j 至 1.9.1,引入推理模型、多会话与流式调用能力;千问模型支持参数调整与联网搜索,新增 AI 绘画、文生图、图生图和海报生成等多模态能力;AI 应用升级为智能体,支持记忆、变量、插件、流程与 MCP;流程能力新增变量、循环、SQL、定时、知识库写入等节点;AI 聊天支持文件上传、Chat2BI 生成图表。并推出 AI 工具箱,覆盖 AI 海报、AI 简历、AI 写作、AI 生图等场景;
AI 平台升级日志
核心升级
大模型与多模态
AI 应用
AI 流程
AI 聊天与 BI
Chat2BI(AI生成图表)
AI工具箱
新增应用场景案例
平台功能升级
Online功能升级
Issues修复
技术交流
快速启动项目
AI应用平台介绍
Dify的AIGC应用开发平台+知识库问答,是一款基于LLM大语言模型AI应用平台和 RAG 的知识库问答系统。 其直观的界面结合了 AI 流程编排、RAG 管道、知识库管理、模型管理、对接向量库、实时运行可观察等,让您可以快速从原型到生产,拥有AI服务能力。 详细专题介绍,请点击查看适用项目
为什么选择 JeecgBoot?
开源界"小普元"超越传统商业平台。引领低代码开发模式(OnlineCoding-> 代码生成器 -> 手工MERGE),低代码开发同时又支持灵活编码, 可以帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高开发效率,节省成本,同时又不失灵活性。
技术架构:
前端
Node 20+ 版本以上、pnpm 要求9+ 版本以上后端
ChatGPT DeepSeek切换微服务架构图

微服务解决方案
Jeecg Boot 产品功能蓝图

系统功能架构图

开源版功能清单
├─AI应用平台
│ ├─AI模型管理
│ ├─AI应用管理
│ ├─AI知识库
│ ├─AI流程编排
│ ├─AI聊天助手(支持图片、文件)
│ ├─AI聊天助手支持嵌入第三方、支持移动端
│ ├─MCP插件管理
│ ├─提示词管理
│ ├─AI应用门户(汇总各种AI应用场景)
│ ├─支持各种常见模型ChatGPT和DeepSeek、ollama等
├─工具箱
│ ├─OCR识别
│ ├─AI 海报
│ ├─AI 写作
│ ├─AI 简历
├─AI辅助功能
│ ├─AI建表(Online表单)
│ ├─AI生成报表(Online报表)
│ ├─AI生成大屏
├─系统管理
│ ├─用户管理
│ ├─角色管理
│ ├─菜单管理
│ ├─权限设置(支持按钮权限、数据权限)
│ ├─表单权限(控制字段禁用、隐藏)
│ ├─部门管理
│ ├─我的部门(二级管理员)
│ └─字典管理
│ └─分类字典
│ └─系统公告
│ └─职务管理
│ └─通讯录
│ ├─多数据源管理
│ └─多租户管理(租户管理、租户角色、我的租户)
├─Online在线开发(低代码)
│ ├─Online在线表单
│ ├─Online代码生成器
│ ├─Online在线报表
│ ├─仪表盘设计器
│ ├─系统编码规则
│ ├─系统校验规则
├─积木报表设计器
│ ├─打印设计器
│ ├─数据报表设计
│ ├─图形报表设计(支持echart)
├─消息中心
│ ├─消息管理
│ ├─模板管理
├─代码生成器(低代码)
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
│ ├─代码生成器模板(提供4套模板,分别支持单表和一对多模型,不同风格选择)
│ ├─代码生成器模板(生成代码,自带excel导入导出)
│ ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)
│ ├─高级查询器(弹窗自动组合查询条件)
│ ├─Excel导入导出工具集成(支持单表,一对多 导入导出)
│ ├─平台移动自适应支持
│ ├─提供新版uniapp3的代码生成器模板
├─系统监控
│ ├─基于AK和SK认证鉴权OpenAPI功能
│ ├─Gateway路由网关
│ ├─性能扫描监控
│ │ ├─监控 Redis
│ │ ├─Tomcat
│ │ ├─jvm
│ │ ├─服务器信息
│ │ ├─请求追踪
│ │ ├─磁盘监控
│ ├─定时任务
│ ├─系统日志
│ ├─消息中心(支持短信、邮件、微信推送等等)
│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
│ ├─系统通知
│ ├─SQL监控
│ ├─swagger-ui(在线接口文档)
│─报表示例
│ ├─曲线图
│ └─饼状图
│ └─柱状图
│ └─折线图
│ └─面积图
│ └─雷达图
│ └─仪表图
│ └─进度条
│ └─排名列表
│ └─等等
│─大屏模板
│ ├─作战指挥中心大屏
│ └─物流服务中心大屏
│─常用示例
│ ├─自定义组件
│ ├─对象存储(对接阿里云)
│ ├─JVXETable示例(各种复杂ERP布局示例)
│ ├─单表模型例子
│ └─一对多模型例子
│ └─打印例子
│ └─一对多TAB例子
│ └─内嵌table例子
│ └─常用选择组件
│ └─异步树table
│ └─接口模拟测试
│ └─表格合计示例
│ └─异步树列表示例
│ └─一对多JEditable
│ └─JEditable组件示例
│ └─图片拖拽排序
│ └─图片翻页
│ └─图片预览
│ └─PDF预览
│ └─分屏功能
│─封装通用组件
│ ├─行编辑表格JEditableTable
│ └─省略显示组件
│ └─时间控件
│ └─高级查询
│ └─用户选择组件
│ └─报表组件封装
│ └─字典组件
│ └─下拉多选组件
│ └─选人组件
│ └─选部门组件
│ └─通过部门选人组件
│ └─封装曲线、柱状图、饼状图、折线图等等报表的组件(经过封装,使用简单)
│ └─在线code编辑器
│ └─上传文件组件
│ └─验证码组件
│ └─树列表组件
│ └─表单禁用组件
│ └─等等
│─更多页面模板
│ ├─各种高级表单
│ ├─各种列表效果
│ └─结果页面
│ └─异常页面
│ └─个人页面
├─高级功能
│ ├─提供单点登录CAS集成方案
│ ├─提供APP发布方案
│ ├─集成Websocket消息通知机制
│ ├─支持electron桌面应用打包(支持windows、linux、macOS三大平台)
│ ├─docker容器支持
│ ├─提供移动APP框架及源码(Uniapp3版本)支持H5、小程序、APP、鸿蒙Next
│ ├─提供移动APP低代码设计(Online表单、仪表盘)
系统效果预览
AI模型与应用管理











PC端






在线聊天&通知


Online开发(在线配置表单和报表)


图表示例





积木BI大屏







APP效果




PAD端



在线接口文档


积木报表








近期,我们围绕离线开发产品进行了一系列功能新增与优化,旨在为用户提供更智能、更高效的开发体验 。本次更新重点引入了离线AI“代码续写”功能,显著提升辅助编程效率;同时支持中英文自由切换,满足国际化业务需求 。在架构层面,新增了Doris SQL多计算引擎切换及业务流程跨工作流编排能力 。此外,我们还优化了Restful源端配置、实现了Python日志实时打印并强化了权限管控,全面赋能企业构建稳健的数据基座 。 在数据开发场景中引入AI辅助编程能力,可根据用户已输入的代码片段,智能预测并生成后续代码内容,提升开发效率。 为满足客户海外业务统一管理需求,产品界面新增中文/英文版本切换功能,完成国际化适配。 为提升业务数据管理效能,部分用户选择构建双集群环境,分别用于数据仓库建设与应用数据存储。在实际开发过程中,任务需根据具体的业务场景分发至相应集群执行。为此,离线数据开发相关功能已全面适配多集群架构,其支持范围涵盖以下内容: 针对跨工作流的任务依赖与全链路管理需求,系统引入“业务流程”单元。它打破了传统单一链路的局限,从业务维度整合多工作流任务,实现跨流编排、依赖管理与统一调度。 核心功能包括: ①任务整合与业务视图将分散在多个工作流中的相关任务统一纳入同一业务流程管理。自动形成可视化的业务链路视图,清晰展示任务间的业务逻辑关系。 ②跨流程依赖配置支持任务之间、任务与业务流程之间的灵活依赖配置。可实现跨工作流、跨流程的依赖管理,满足复杂业务链路调度需求。 ③调度与运行能力流程下的任务可独立配置调度策略,无需配置根节点即可直接提交。最终以“流程下的单个任务”为调度运行维度,实现灵活高效的执行控制。 ④补数据能力支持流程内任务的统一或独立补数操作,确保业务链路数据一致性。 新增对GaussDB 9.1计算引擎的支持,涵盖周期任务、语法提示、数据同步等功能;数据同步任务在源端和目标端均可选择GaussDB 9.1作为读写数据源。 当数据同步任务的目标端为inceptor时,支持一键自动创建目标表结构。 数据同步任务的源端与目标端均支持选择Kafka 2.x作为数据源,便于从Kafka进行周期性数据抽取。 优化表数据预览的权限逻辑,用户必须同时具备“表管理-查看”权限以及在数据地图中已申请获得的DQL权限,方可查看数据,确保权限边界清晰。 新增「数据地图外表权限管控」配置功能。该功能在兼容历史客户使用习惯的基础上,提供更严格的数据安全防护。用户可根据实际场景选择是否限制对未纳入数据地图表的操作权限,从而有效防止越权访问和潜在数据风险。 在控制台Spark集群内新增DataLake的hudi配置项后,SparkSQL任务支持对 Hudi 表执行完整的 DDL、DML、DQL 操作,用户可像操作 Hive 表一样直接进行 查询、创建、修改与写入,实现统一的 SQL 使用体验 新增对DMDB for Oracle计算引擎的支持,涵盖周期任务、整库同步、数据同步、手动任务、临时查询、语法提示、表查询、函数管理、存储过程、依赖推荐、任务上下游参数、代码模板、按项目或个人粒度绑定数据库账号、执行计划、数据导入等功能模块。 离线数据同步任务、Spark任务、PySpark任务、Spark SQL任务及Hive SQL任务全面适配AWS S3存储底座。 Spark3.5支持更多特性包括自适应执行、向量化优化,对Paimon湖仓有更好支持。为提升平台性能与兼容性,满足客户在离线计算场景中对SparkSQL的最新特性需求,平台计算引擎SparkSQL3.5进行适配 功能如下: 计算引擎支持对接SparkSQL3.5版本,支持创建任务、周期运行、补数据等操作;SparkSQL3.5支持对Paimon 1.2进行操作,包括DDL、DML、DQL语法 在构建相应连接jar包后,HiveSQL任务支持对Paimon表执行完整的DDL、DML、DQL操作。 系统可根据资产平台表血缘关系自动解析生成任务依赖。当用户手动配置的依赖存在多配或少配时,在提交任务时会进行弹窗提示,降低因依赖配置错误导致的运行时故障风险。 为满足交易类业务对小时级调度链路时效性的高要求,优化依赖匹配规则:支持“优先寻找同小时的上游实例”;若无同小时实例,则自动回退至最近时间的实例。新增“基于默认依赖周期的偏移”和“优先寻找同小时的上游实例”两种依赖方式选项。 适用于 3 类时间间隔场景(参考下方实例依赖图): 任务A与任务B间隔一致: 同小时匹配:B 11:30 → A 11:50;回退匹配:B 10:30 → A 前一天 14:50 任务A间隔小于任务B: 同小时匹配:B 15:30 → A 14:50;回退匹配:B 20:30 → A 16:50 任务A间隔大于任务B: 同小时匹配:B 10:50 → A 10:30;回退匹配:B 12:50 → A 10:30 在数据同步任务配置Restful数据源时,增加Path路径填写项,用户可直接在任务内填写完整URL,简化多接口配置流程。 任务脚本执行完成后,在日志中明确展示SQL运行的影响行数:对DML语句(如INSERT、UPDATE、DELETE),日志中返回实际影响的行数;对DDL语句(如 TRUNCATE、DROP、ALTER、CREATE),日志中统一返回 -1 优化Python任务执行机制,支持在任务运行过程中于页面实时打印输出日志和错误信息,改变此前需等待任务结束后才查看日志的状况。 在告警规则中新增该触发方式,当任务超过计划时间一定阈值仍未开始运行时,系统自动触发告警,便于及时发现调度阻塞。 新增为补数据任务和手动任务配置“最大并行实例数”默认值的能力,防止因误操作触发大量实例导致集群资源耗尽。 将操作设置中的众多配置项按“数据同步”、“SQL任务”、“调度”、“通用”四大模块进行重新分类展示,提升查找和配置效率。 针对本地上传大文件时页面卡顿、崩溃的问题,重构代码逻辑,采用分片上传技术,并将单个文件大小上限设置为500MB,提升上传稳定性和用户体验。 对Sql Parser进行RPC改造,显著提高解析服务的稳定性,减少因解析导致的IO飙高、进程异常及服务不可用情况。 优化数据同步任务中Redis的写入逻辑,在单并发场景下,写入3000万条数据的耗时从超过15分钟缩短至2分钟以内,极大提升了同步效率。 为解决多子产品中生命周期配置不一致可能导致数据误清理的问题,统一通过业务中心SDK维护表生命周期。系统自动判断表是否存在并执行插入或更新操作,确保配置一致性。 合并进入“数据开发”页面时对函数目录的重复接口调用,整合为一次性请求,解决因函数过多导致的页面加载缓慢问题。 减少页面打开时的不必要接口请求,优化请求数量,提升页面响应速度。 实现资产中心配置的Hive、Doris数据源脱敏规则在离线开发平台内同步生效,用户无需在两个产品内重复配置。 优化AI知识库的数据同步流程,支持配置多线程并行同步,显著缩短同步耗时,改善生产环境同步体验。一、功能新增
1.重点新增内容
1.1.离线AI功能新增「代码续写」功能

1.2.离线开发平台支持中英文切换

1.3.Doris SQL任务支持多计算引擎切换


1.4.新增业务流程类型,支持跨工作流任务编排


1.5.计算引擎与数据同步支持GaussDB 9.1

1.6.inceptor数据同步支持一键生成目标表

1.7.数据同步向导模式支持Kafka 2.x


1.8.数据地图DQL权限校验强化


1.9.Hive表权限管控功能


1.10.SparkSQL 3.2支持读写Hudi 0.15.0

2.其他新增内容
全面支持Doris 3.x作为计算引擎,覆盖周期任务、整库同步、数据同步、手动任务、临时查询、语法提示、表查询、函数管理、依赖推荐、任务上下游参数、代码模板、账号绑定、执行计划等功能。二、功能优化
1.重点功能优化说明
1.1.任务依赖配错提示功能

1.2.同小时任务依赖逻辑优化



1.3.同步任务Restful源端配置优化

1.4.脚本日志展示SQL影响行数

1.5.Python on Agent日志实时打印

1.6.告警规则新增“未按计划时间运行”触发条件

1.7.支持配置任务实例默认并发数

1.8.操作设置页面布局调整

2.其他功能优化
本次版本 新增函数对象转换能力,扩展了达梦等多数据库迁移适配范围,并提升了批量转换的处理效率,进一步降低企业级数据库迁移的复杂度与成本。 函数对象可随存储过程的迁移任务一键同步转换。该能力的加入,让 SQLShift[1] 从一款“存储过程迁移工具”升级为“核心业务逻辑对象全量迁移工具”。随之也带来三重提升: 降低迁移风险与人工成本 避免 函数对象 需人工逐个改写与反复校验,大幅减少因语法差异、返回值不一致引发的运行期错误。 提升非表对象整体迁移效率 函数对象与存储过程 可在同一时间中完成迁移与校验,缩短整体迁移周期。 避免 函数对象 缺失导致上层存储过程等对象无法编译或运行的问题,有效降低迁移后集中调试与返工压力,提升割接与上线的稳定性。 <iframe src="//player.bilibili.com/player.html?isOutside=true&aid=115970207123078&bvid=BV1Gg6LBAEf3&cid=35655845383&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe> 本次升级,SQLShift 扩展了多项数据库迁移组合。 新增 Oracle / OceanBase → 达梦 降低了迁移至达梦数据库的复杂度和人工成本,帮助企业快速完成数据库替换或国产化改造。 新增 PostgreSQL → OceanBase(Oracle 模式) 减少了跨数据库迁移中的人工调整工作量,加快了从 PostgreSQL 向 OceanBase 的迁移进程。 批量处理能力提升 支持同时上传多个 SQL 文件进行转换,提升大规模迁移场景下的处理效率。 👉 点击领取 你的转换额度,立即体验 SQLShift 智能化迁移带来的飞跃效率! 🧩 SQL 方言再多,转换也能一步到位,SQLShift 为你搞定!
一、核心特性
支持函数对象迁移

新增数据库迁移组合

二、其他更新
免费试用限时开放!

已经在虚拟机部署好Apache DolphinScheduler了,想尝试下在Flink新建一个Flink节点,然后用Flink消费Kafka数据。 Apache DolphinScheduler用的是单机部署,具体操作可以参考官方文档:DolphinScheduler | 文档中心(https://dolphinscheduler.apache.org/zh-cn/docs/3.3.2/guide/in...). 1、编辑环境变量: 增加Flink的路径 2、使环境变量生效: 因为用的是虚拟机,为了让外面的主机能够访问到虚拟机的网络,需要修改下配置文件 查看dolphinscheduler-daemon.sh文件: 修改dolphinscheduler_env.sh文件,新增JAVA、Flink路径: 启动应用,包括Zookeeper、Kafka、Flink以及Apache DolphinScheduler。 测试Flink、Apache DolphinScheduler是否能访问成功。 用Flink消费Kafka数据,然后打包上传到Apache DolphinScheduler,启动Flink任务: pom.xml FlinkKafkaConsumerExample.java 在Apache DolphinScheduler的任务实例看启动日志: 在虚拟机启动生产者,输出字符串,然后可以在Flink查看输出Kafka生产的消息: 原文链接:https://blog.csdn.net/Analyze_ing/article/details/156940553

配置好Flink的环境变量
sudo vim ~/.bashrc
#使环境变量生效
source ~/.bashrc
#查看环境变量
echo $Flink_HOME修改Kafka、Flink以及DolphinScheduler的配置文件
broker.id=0
listeners=PLAINTEXT://0.0.0.0:9092
#192.168.146.132修改成虚拟机ip
advertised.listeners=PLAINTEXT://192.168.146.132:9092
jobmanager.rpc.address: 0.0.0.0
jobmanager.bind-host: 0.0.0.0
jobmanager.cpu.cores: 1
jobmanager.memory.process.size: 1600m
taskmanager.bind-host: 0.0.0.0
taskmanager.host: 0.0.0.0
taskmanager.memory.process.size: 2048m
taskmanager.cpu.cores: 1

dolphinscheduler-daemon.sh可以看出,配置环境变量用的是bin/env文件夹下的dolphinscheduler_env.sh。
#修改成自己的JAVA、Flink路径
export JAVA_HOME=/data/jdk-11.0.29
export Flink_HOME=/data/Flink-1.18.1
关闭防火墙,启动应用
#关闭防火墙
sudo systemctl stop firewalld
# 在 Flink 根目录下,执行以下命令启动 Flink 集群
bin/start-cluster.sh
# 启动 ZooKeeper
bin/zookeeper-server-start.sh config/zookeeper.properties &
# 启动 Kafka 服务器
bin/Kafka-server-start.sh config/server.properties &
#创建 Kafka 主题
bin/Kafka-topics.sh --create --topic test --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1
#使用命令行生产者发送消息
bin/Kafka-console-producer.sh --topic test --bootstrap-server localhost:9092
#消费
bin/Kafka-console-consumer.sh --topic test --from-beginning --bootstrap-server localhost:9092
# 启动 Standalone Server 服务
bash ./bin/dolphinscheduler-daemon.sh start standalone-server测试


编写样例
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>Flink-Kafka-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<Flink.version>1.18.1</Flink.version>
<scala.binary.version>2.12</scala.binary.version>
<Kafka.version>3.6.0</Kafka.version>
</properties>
<dependencies>
<!-- Flink核心依赖 -->
<dependency>
<groupId>org.apache.Flink</groupId>
<artifactId>Flink-java</artifactId>
<version>${Flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.Flink</groupId>
<artifactId>Flink-streaming-java</artifactId>
<version>${Flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.Flink</groupId>
<artifactId>Flink-clients</artifactId>
<version>${Flink.version}</version>
</dependency>
<!-- 连接器基础依赖 -->
<dependency>
<groupId>org.apache.Flink</groupId>
<artifactId>Flink-connector-base</artifactId>
<version>${Flink.version}</version>
</dependency>
<!-- Kafka连接器(关键修改点) -->
<dependency>
<groupId>org.apache.Flink</groupId>
<artifactId>Flink-connector-Kafka</artifactId>
<version>3.1.0-1.18</version>
</dependency>
<dependency>
<groupId>org.apache.Kafka</groupId>
<artifactId>Kafka-clients</artifactId>
<version>${Kafka.version}</version>
</dependency>
<!-- 日志依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>aliyun</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>apache-releases</id>
<url>https://repository.apache.org/content/repositories/releases/</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>org.apache.Flink:force-shading</exclude>
<exclude>com.google.code.findbugs:jsr305</exclude>
<exclude>org.slf4j:*</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>import org.apache.Flink.api.common.functions.FlatMapFunction;
import org.apache.Flink.api.java.tuple.Tuple2;
import org.apache.Flink.api.java.utils.ParameterTool;
import org.apache.Flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.Flink.streaming.api.datastream.DataStream;
import org.apache.Flink.streaming.api.functions.ProcessFunction;
import org.apache.Flink.streaming.api.functions.sink.RichSinkFunction;
import org.apache.Flink.util.Collector;
import org.apache.Flink.streaming.connectors.Kafka.FlinkKafkaConsumer;
import org.apache.Flink.api.common.serialization.SimpleStringSchema;
import org.apache.Kafka.clients.consumer.ConsumerConfig;
import org.apache.Kafka.common.serialization.StringDeserializer;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
public class FlinkKafkaConsumerExample {
private static volatile int messageCount = 0;
private static volatile boolean shouldStop = false;
public static void main(String[] args) throws Exception {
// 设置执行环境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// Kafka 配置
Properties properties = new Properties();
properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.146.132:9092"); // Kafka broker 地址
properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test-group"); // 消费者组
properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// 创建 Kafka 消费者
FlinkKafkaConsumer<String> KafkaConsumer = new FlinkKafkaConsumer<>("test", new SimpleStringSchema(), properties);
KafkaConsumer.setStartFromEarliest(); // 从最早的消息开始消费
DataStream<String> stream = env.addSource(KafkaConsumer);
// 处理数据:分词和计数
DataStream<Tuple2<String, Integer>> counts = stream
.flatMap(new Tokenizer())
.keyBy(value -> value.f0)
.sum(1);
counts.addSink(new RichSinkFunction<Tuple2<String, Integer>>() {
@Override
public void invoke(Tuple2<String, Integer> value, Context context) {
System.out.println(value);
messageCount++;
// 检查是否达到停止条件
if (messageCount >= 2 && !shouldStop) {
System.out.println("Processed 2 messages, stopping job.");
shouldStop = true; // 设置标志位,表示应该停止
}
}
});
// 执行作业并获取 JobClient
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
// 启动作业并获取 JobClient
org.apache.Flink.core.execution.JobClient jobClient = env.executeAsync("Flink Kafka WordCount");
System.out.println("Job ID: " + jobClient.getJobID());
// 监测条件并取消作业
while (!shouldStop) {
Thread.sleep(100); // 每100毫秒检查一次
}
// 达到停止条件时取消作业
if (shouldStop) {
System.out.println("Cancelling the job...");
jobClient.cancel().get(); // 取消作业
}
} catch (Exception e) {
e.printStackTrace();
}
});
// 在主线程中等待作业结束
future.join(); // 等待作业完成
}
// Tokenizer 类用于将输入字符串转化为单词
public static final class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
String[] tokens = value.toLowerCase().split("\\W+");
for (String token : tokens) {
if (token.length() > 0) {
out.collect(new Tuple2<>(token, 1));
}
}
}
}
}






一切的开始,源于一次令人惊叹的 AI CodeReview 体验。 在那之前,我对 AI 的辅助能力还停留在“代码补全”的印象中。但看到 AI 能够精准地指出代码逻辑中的隐患、提出优雅的重构建议后,我意识到:时代变了。这不仅仅是一个工具的升级,更是一种开发模式的变革。 受到这次冲击后,我开始较为深度地在日常开发中使用 AI IDE,尝试将更多的任务交付给它。经过一段时间的摸索与实践,我总结了一些心得,并在团队分享会上进行了汇报。今天,我想把这些思考落实成文,与大家分享。 对于当前的 AI 模型能力,我持非常积极的态度。在大量的实战中,我悟出了一个关键道理: 基于“闭环验证”的理念,我重构了自己的开发流水线。现在的完整流程如下: 测试驱动与自我运行 : 为了更直观地说明,举一个最近的实战需求: feat(order-warn-text) 。 需求背景 :支付成功率报警文案中的链接,需要改为“可点击”状态。 传统做法 : AI 辅助下的做法 : AI 不仅仅是帮你少敲几个键盘的助手,它完全可以胜任更复杂的“开发-测试-验证”全流程。 关键在于我们如何给它下达指令,以及如何设计“验收标准”。当我们学会利用单元测试和自动化流程让 AI 实现“自我闭环”时,我们的生产力将得到质的飞跃。 未来已来,拥抱变化,让我们做更聪明的开发者。 注:本文基于自己的心得,使用 AI 扩展而来。原文如下: 「AI 探索」 在看到 AI CodeReview 的令人惊叹的效果后,较为深度地开始使用 AI IDE 完成了不少任务,总结了心得并在分享会进行了团队内分享【AI 探索】从 CodeReview 到全流程闭环:我的 AI 辅助开发实践心得
引言:惊鸿一瞥后的深度拥抱
核心理念:构建“自我闭环验证”能力
只要让 AI 具备“自我闭环验证”的能力,它就能高效地帮我们完成更多事情。
很多时候,我们不敢放手让 AI 做事,是因为担心它写出“看似正确实则无法运行”的代码。一旦我们将 验证环节 也交给 AI,让它不仅负责“写”,还负责“证”,信任链条就打通了。我的 AI 开发新流程
在这个流程中,我从“代码编写者”转变为了“需求定义者”和“最终验收者”。实战案例:让 AI 自己证明自己
我需要找到拼接字符串的地方,修改 HTML 标签,然后启动本地服务,造数据触发报警,查看效果。
结论 :
在这个过程中,我完全不需要关心它是如何拼接字符串的,也不需要费力去启动整个服务。 只要它能通过测试输出让我信服的结果(比如那段 HTML),我就认可它的工作。总结
写在最后
对于当前 AI 模型能力持较为积极态度,意识到只要让 AI 做到自我闭环验证的能力就可以更高效的让 AI 完成更多事情
业务代码通过 AI 开发完成后,确保代码风格没有问题,让 AI 编写接口测试、单元测试并自我运行,通过测试覆盖率进行自我验证,提交到 Gitlab 后通过 AI 进行 CodeReview 的完整开发流程
例如 feat(order-warn-text): 支付成功率报警,文案链接改为可点击 这个需求,我只需要确定应该在哪里修改相关内容,用精确的语言告知 AI 我想要实现的目标和效果,并让他编写相关的单元测试,该单元测试能够生成 HTML 让我再检查一下是否真的可点击,就不需要我再关心他是如何实现这个需求的了
在科技演进的长周期中,一项技术从“效率工具”走向“社会底座”,往往意味着其角色已经发生结构性变化。进入 2026 年,人工智能正处于这样的转折点。相比前几年作为企业创新亮点的应用形态,AI 正逐步演化为一种通用型基础设施,开始承担类似算力、网络与操作系统的底层支撑职能。 这种变化并非概念升级,而是源于交付方式、成本结构以及组织使用方式的同步转变。 在行业实践中,工具型技术通常用于解决局部、离散的问题,需要明确的使用入口和操作主体;而基础设施则具备泛在性、稳定性与低感知度,其价值体现在持续支撑上层系统的运行,而非单点能力的展示。 到 2026 年,AI 已不再以独立模块存在,而是被原生嵌入到操作系统、数据平台与业务流程之中,成为默认可调用的系统能力。 1. 交互成本的显著降低 随着多模态模型和自然语言接口的成熟,用户不再需要理解模型结构或提示技巧即可完成复杂指令。AI 的使用方式逐渐标准化,使其具备“即用即得”的特征。 2. 自主运行能力成为常态 行业中已普遍观察到,AI 从被动响应转向持续执行任务流,能够在后台完成跨系统协作与状态维护。“智能体来了”不再只是概念,而是企业系统中真实存在的一种运行形态。 3. 推理成本的结构性下降 在专用硬件与模型压缩技术推动下,推理的边际成本持续降低。AI 不再是需要单独核算 ROI 的高成本模块,而逐步成为企业 IT 架构中的基础性消耗项。 对开发者而言,重心正在从实现具体功能,转向对业务规则与执行边界的定义。应用构建更多体现为对智能能力的编排,而非代码逻辑的堆叠。 对企业而言,关注点从“采购 AI 产品”转为“流程是否可被 AI 驱动”。业务流程的数字化程度,开始直接决定 AI 基础设施能够释放的价值上限。 对终端用户而言,AI 的存在感持续降低。多数智能行为通过系统默认完成,用户往往只感知结果,而不再感知技术本身。 在基础设施化趋势下,AI 的建设思路也随之转变: 2026 年的一个显著变化在于,AI 正逐渐从“显性的创新能力”转变为“隐性的运行背景”。它不再以改变世界的姿态出现,而是成为世界正常运转的一部分。 在这一阶段,真正的差异化不再来自是否使用 AI,而来自是否能够在这一基础之上,构建新的业务逻辑与组织能力。一、从工具到基础设施的界限变化
二、推动基础设施化的三项关键变化
三、价值链角色的重新分配
四、建设逻辑的变化
五、结语
随着AI的广泛应用,GitHub Copilot、Cursor等AI Coding Agents 已经像空气一样,渗透进开发者的日常。自动化生成代码、智能补全、一键找 Bug……听起来,程序员似乎终于要从繁重的体力活中解脱,迎来效率的跃升。 然而,AI 的加入,真的缩短了我们的开发周期吗? 最近,关于 AI 究竟是“提效神器”还是“效率黑洞”的讨论,正成为行业关注的焦点。我们拆解了多项深度调研与实验数据,发现了一个事实:AI Coding并没有真正缩短开发周期,它只是把“坑”换了个地方。 传统软件开发中,调试和测试阶段通常占据了很大比例的时间。 根据经典的软件工程研究,集成、测试和调试阶段通常占据项目总工时的 30% 到 40% [1]。也有估算指出,开发者在验证和调试上花费的时间甚至高达 35% 到 50% [2]。 这意味着,在传统的手工编程时代,编码阶段和调试阶段时间比例大约是6:4。虽然编码看似占主要部分,但开发者依然需要花费近乎一半的时间去调试和修复问题。 当 AI Coding Agent 介入后,开发者本以为写代码的时间会大幅缩减,从而带动整体效率起飞。但实际情况远比想象中复杂。 几项近期的对比实验揭示了 AI Coding Agent 的“双面性”: 2025 年 Stack Overflow 的开发者调查给出了答案:66% 的开发者发现 AI 生成的代码“几乎正确,但又不完全正确”。这种“似是而非”的状态极大地增加了校对负担。更有 45.2%的受访者直言:调试 AI 生成的代码比调试人类写的代码更耗时 [5]。这些数据表明,虽然AI可以快速生成代码片段,但开发者往往需要花更多时间检查、修改和调试AI输出。 既然 AI 写代码效率如此高,为什么整体进度却快不起来?我们总结了五个核心“陷阱”: METR研究者观察发现,AI 建议的方向通常是对的,但在细节上却经常“掉链子”。这种“差一点就对”的代码需要开发者进行极其细致的逐行检查,这大大增加了调试时间 [6]。 实验录像显示,使用 AI 的开发者频繁地在调试和清理 AI 输出的代码上耗费时间。AI 确实“写”得快,但由于不可控的错误和不贴合上下文的部分,开发者不得不反复阅读和修正 [7]。 这是一种全新的时间消耗。AI辅助工具依赖自然语言提示,开发者在使用过程中为了让 AI 理解意图,需要精心构思提示词,同时也会将时间花在撰写有效提示或等待AI生成结果上 [7]。 AI 生成的代码有时缺乏风格一致性和上下文理解,导致维护难度增加。资深开发者反馈,AI往往生成冗长或与项目惯例不符的代码,导致他们必须“多读几遍才能看懂” [8]。数据也表明,高度依赖AI生成代码的项目可能引入更多bug和复杂度,略微降低交付速度[9]。 Cerbos博客分析指出,AI Coding Agent 会带来“表面速度”幻觉。让开发者感觉进展神速,但实际上,开发者在AI辅助环境下从传统的键盘敲击转移到更多思考和验证上,这虽然减轻了初期的编写负担,但并未减少总体工作量[8]。 下表对比了几项研究和调查中有关开发与调试时间的关键数据: 结论显而易见:目前的 AI Coding Agent 并没有显著缩短开发周期,而是将时间开销转移到了“代码验证”和“提示词工程”上。开发者普遍需要投入额外时间来审查、测试和修复AI生成的代码;同时,为了得到符合预期的输出,他们还需花费心力在有效提示设计上。 当前AI辅助开发的主要效益体现在繁琐任务自动化和认知负担减轻(如生成样板代码和文档),但在处理核心逻辑和复杂 Bug 时,人类的深度参与依然不可替代。 未来,想要真正降低Debug时间,一方面需要提高AI代码质量与可预测性,例如改进提示技巧和学习工具配合,以减少人工二次检查的需求;另一方面,由于信息传递时总是存在衰减,无论人还是AI在编程时不可避免留下Bug,因此需要有更强的Debug工具来辅助解决这些问题。在那个时代到来之前,程序员们可能还得继续在AI挖的坑里,苦练“找茬”的本领。 [1] Pressman,R.S. (2000). Software engineering: A practitioner's approach. [2] ACM Queue. (2017). Developer time allocation in software development. [3] Peng,S,et al. (2023). The Impact of AI on Developer Productivity. [4] Becker,J,et al.(2025).Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity. [5] Stack Overflow. (2025). 2025 Developer Survey. [6] Reuters.(2025). AI slows down some experienced software developers. [7] Fortune.(2026). Does AI increase workplace productivity? [8] Dziuba,L.(2025). The Productivity Paradox of AI Coding Assistants. [9] Munteanu,N.(2025). Developer productivity statistics with AI coding tools (2025 report).传统开发的“黄金比例”
AI介入的“效率悖论”
为什么资深开发者的效率反而下降了?
深度拆解:Debug时间变长了
1. “几乎正确”的幻觉
2. 额外的校对和调试工作
3. 提示词工程(Prompt Engineering)
4. 代码质量与可读性危机
5. 认知负荷的转移
维度 传统开发场景 AI辅助的后变化 数据来源 集成、测试和调试 约30%–40% — Pressman 验证和调试 约占35%–50% — ACM Queue 简单任务 — 完成时间减少55.8%(提速55.8%) GitHub Copilot RCT 复杂任务 — 完成时间增加19%(减速19%) METR RCT 开发者调研 — 45.2%认为调试AI代码更耗时;66%认为代码“差不多但不完全对” stack Overflow 总结:开发周期真的变短了吗?
作为一名长期深耕于外包公司的前端工程师,我大部分的项目都是使用 Vue2;此前学习的 Vue3 与 React,却始终没有机会在实际项目中落地实践。为了避免陷入颓废、被行业淘汰的困境,我计划着手搭建个人后台管理项目,全程记录使用 Next.js 的搭建流程,同时结合官方文档与 AI 工具,一步步完成项目落地,既巩固技术,也给自己的成长留下印记。 我这边开发环境选的是 我现在用的 可以使用 因为项目是使用 这边我使用的 安装时,你将看到以下提示 使用 查看 启动项目,测试是否运行成功: 项目正常启动后,在浏览器中访问 若页面能正常显示,且控制台不报任何异常,则项目创建启动成功。 App Router 是 如果你想快速体验多路由,还可以创建: 至此,我们完成了项目的初始化和代码重构工作,包括: 下一篇文章里,我们来重点对 做好这些配置,能帮项目规避语法错误、提前揪出类型问题,避免后续写业务时踩坑;还能提升代码可读性和可维护性,贴合 我也是个跟着文档和AI交流一步步摸索的菜鸟,如果你对本文讲的项目初始化、路由这些内容有疑问,或者实操时踩了坑,欢迎在评论区留言。咱们一起交流避坑. 本文由mdnice多平台发布前言

0 开发环境及依赖版本
开发环境
Node.js + pnpm组合。版本管理工具用的是 Volta,它最方便的地方就是能给不同项目配置不同的Node版本,不用来回切换麻烦。
具体用法很简单,常用命令贴在这:# 将 Node.js 安装为默认版本,安装最新的 LTS(长期支持)版本的 Node.js。
volta install node
# 安装特定版本
volta install node@16
volta install node@16.14.2
# 特定的 Node.js 版本固定到您的项目
volta pin node@16.14.2pnpm 的话,直接用 npm install -g pnpm 命令安装就行。Node.js和 pnpm都是最新版本,做技术嘛,就得追着最新的来,后续用到的其他技术栈也会保持最新,同时兼顾好兼容性,避免出现版本不匹配的问题。node -v、pnpm -v 查看版本号。
Next 官方脚手架创建项目,默认给你配置好了最新的、可兼容的版本,其他的依赖直接上新版!咱使用的版本号如下:依赖 版本 描述 next 16.1.5 Next.js 框架 react 19.2.3 React 核心 react-dom 19.2.3 React DOM 渲染 typescript ^5 静态类型检查 eslint ^9 代码检查 eslint-config-next 16.1.5 Next.js ESLint 规则 tailwindcss ^4 原子化 CSS 框架 @tailwindcss/postcss ^4 Tailwind CSS 编译 1. 初始化项目
1.1 创建项目
Next.js 官方推荐的 create-next-appnpx create-next-app@latest? What is your project named? » my-app # 项目名称
? Would you like to use the recommended Next.js defaults? » - Use arrow-keys. Return to submit. # 推荐的Next.js默认值吗,
> Yes, use recommended defaults - TypeScript, ESLint, Tailwind CSS, App Router # 是的,使用推荐的默认值-TypeScript、ESLint、Tailwind CSS、App Router
No, reuse previous settings # 否,重复使用以前的设置
No, customize settings # 否,自定义设置,我选这个
? Would you like to use TypeScript? » No / Yes # 你想使用TypeScript吗? Yes
? Which linter would you like to use? » - Use arrow-keys. Return to submit.# 你想选择哪种代码检查工具
> ESLint # 选择主流
Biome
None
? Would you like to use React Compiler? # 您想使用React编译器吗?
» No / Yes # Yes
? Would you like to use Tailwind CSS? # 您想使用Tailwind CSS 吗
» No / Yes # Yes
? Would you like your code inside a `src/` directory? # 你想把代码放在`src/`目录中吗
» No / Yes # Yes
? Would you like to use App Router? (recommended) 您想使用App Router吗?
» No / Yes # Yes
? Would you like to customize the import alias (`@/*` by default)? # 是否要自定义导入别名(默认为“@/*”)
» No / Yes # No1.2 安装依赖
VScode 打开前面创建的项目 my-app,打开终端,输入 pnpm install 安装项目所需依赖。
1.3 启动项目
package.json 配置文件{
...
"scripts": {
"dev": "next dev", // 启动开发环境服务器
"build": "next build", // 为生产环境构建 / 打包项目
"start": "next start", // 启动生产环境服务器
"lint": "eslint" // 运行代码检查工具
},
...
} pnpm devhttp://localhost:3000/
2. 调整项目结构
2.1 项目文件 / 文件夹作用全解析
my-app/
├─ .next/ # Next.js 开发 / 打包时自动生成的临时缓存目录
├─ node_modules # 项目所有第三方依赖包的存放目录
├─ public/ # 静态资源(图片、favicon)
├─ src/
│ ├─ app/ # App Router 的核心路由目录
│ │ ├─ layout.tsx
│ │ ├─ page.tsx
│ ├─ components/ # 可复用组件(尽量小、可组合)
│ ├─ hooks/ # 自定义 hooks(useAuth, useToast)
│ ├─ lib/ # 数据客户端、工具函数(prisma client, supabase client)
│ ├─ styles/ # globals, tailwind css entry
│ ├─ types/ # 全局类型声明
│ └─ utils/ # 小工具
├─ .env.local # 本地环境变量(不要提交)
├─ next.config.js # Next.js 项目的全局配置文件
├─ postcss.config.js # PostCSS 工具的配置文件
├─ eslint.config.mjs # ESLint 代码检查工具的配置文件
├─ tsconfig.json # TypeScript 配置文件
├─ package.json # 项目核心配置文件
└─ README.md # 项目核心配置文件2.2 创建测试页面
文件系统路由,即「文件 / 文件夹的路径 = 页面的 URL 路径」。我们来创建一个 /test 测试页面:src/app 目录下,新建一个名为 test 的文件夹。page.tsx 的文件(这是 App Router 中 “页面文件” 的固定命名)。page.tsx 中写入测试代码:// src/app/test/page.tsx
export default function TestPage() {
return (
<div style={{ padding: '2rem' }}>
<h1>这是一个测试页面</h1>
<p>访问路径:/test</p>
</div>
);
}
2.3 配置更多路由
src/app/page.tsx 就是默认的首页(访问路径 /),可以修改这个文件来定制首页内容。src/app/blog/[id]/page.tsx,就能实现动态路由 /blog/123([id] 是动态参数)。src/app/layout.tsx 是全局布局文件,所有页面都会继承这个布局(比如导航栏、页脚可以写在这里,不用每个页面重复写)。END
ESLint + TypeScript 进行配置 —— 主要是 .eslint.config.mjs 和 tsconfig.json 这两个核心文件,了解每个配置项的含义和作用。Next.js 16 + TS 5.x 的适配需求。
编者按: 如果你正在为边缘计算、本地部署或资源受限场景寻找高效的语言模型解决方案,你是否曾困惑:在众多小型语言模型(SLM)中,哪一个才是微调的最佳起点?是否真的存在“小而强”的模型,能在微调后媲美甚至超越规模大数十倍的教师模型? 近期,distil labs 团队进行了一项严谨的基准研究,或许能为你提供数据驱动的答案。他们在 8 类任务(涵盖分类、信息抽取、开卷与闭卷问答)上,对 12 个主流小型模型(包括 Qwen3、Llama、Gemma、Granite、SmolLM 等系列)进行了统一微调与评估,并对比了其与 120B 参数教师模型(GPT-OSS-120B)的性能差异。 作者 | Distil Labs 编译 | 岳扬 经过微调的小型语言模型(SLM)可以胜过规模大得多的模型:微调后的 Qwen3-4B 在 8 项基准测试中的 7 项上表现能够超越或战平 GPT-OSS-120B(一个比它模型规模大 30 倍的教师模型),剩下的一项差距也不到 3 个百分点。在 SQuAD 2.0 数据集上,微调后的学生模型甚至比教师模型高出 19 分。这意味着你只需极低的成本,就能在自己的硬件上实现前沿模型级别的准确率。 微调后性能最佳的模型:Qwen3 系列模型在微调后始终表现最强,其中 4B 版本整体表现最优。如果你的目标是在特定任务上获得最高准确率,Qwen3-4B 就是你的首选。 最具可微调性(🐟-ble)(微调收益最大):小型模型从微调中获得的提升远超大型模型。 如果你受限于使用非常小的模型(1B–3B),也不必担心 —— 它们能从微调中获益最多,能够大幅缩小与更大模型之间的性能差距。 如果你正在构建需要在设备端、本地或边缘侧运行的 AI 应用,你很可能问过自己:我该微调哪个小型语言模型(SLM)?目前 SLM 领域选择众多(Qwen、Llama、Gemma、Granite、SmolLM),每个系列都提供多种模型规模的版本。选错基础模型可能意味着有数周时间在浪费计算资源,或者得到的模型始终无法达到生产质量要求。 我们进行了一项系统的基准测试,用数据来回答这个问题。借助 distil labs 平台,我们在 8 个不同的任务上(分类、信息抽取、开卷问答、闭卷问答)微调了 12 个模型,然后将它们的性能相互比较,并与用于生成合成训练数据的教师大模型进行对比。 本文回答了四个实际问题: 我们评估了以下模型: 针对每个模型,我们测量了: 我们的 8 项基准测试涵盖分类(TREC、Banking77、Ecommerce、Mental Health)、文档理解(docs)以及问答任务(HotpotQA、Roman Empire QA、SQuAD 2.0)。 为了实现公平测量,我们分别计算了每个模型在各个基准测试上的排名,然后计算所有任务上的平均排名,并以 95% 置信区间作为误差棒(error bars)绘制在图中。平均排名越低,表示整体性能越好。 冠军:Qwen3-4B-Instruct-2507(平均排名:2.25) Qwen3 系列占据了排行榜前列,其中 Qwen3-4B-Instruct-2507 摘得桂冠。值得注意的是,这款 4B 模型的表现甚至超过了更大的 Qwen3-8B,这表明在蒸馏任务中,Qwen3 的较新版本(2025 年 7 月 25 日更新的版本)比之前的 8B SLM 效果更好。 核心结论:如果你希望获得效果最好的微调模型,并且拥有支持约 4B 参数规模模型微调的 GPU 显存,那么 Qwen3-4B-Instruct-2507 是你的首选。 冠军: Llama-3.2-1B-Instruct(平均排名:3.44) 这里我们测量的是可微调性(tunability) —— 即从基础性能到微调后性能的提升幅度(finetuned_score - base_score)。一个高度可微调的模型初始表现可能较弱,但经过微调后提升显著。 有趣的是,可微调性排名与模型大小的排序正好相反。像 Llama-3.2-1B 和 Qwen3-0.6B 这样的小型模型,从微调中获得的提升最大。而规模最大的模型(如 Qwen3-8B、granite-3.3-8b)在可微调性排名中接近垫底 —— 这并非因为它们表现差,而是因为它们起点相对较高,进步空间相对有限。 核心结论:如果你受限于使用极小的模型(<2B 参数),不必灰心。这些模型从微调中获益最大,并且能够显著缩小与更大模型之间的性能差距。 冠军: Qwen3-8B (平均排名: 1.75) 在未经任何微调的情况下,哪个模型开箱即用的表现最好? 正如预期,基础性能与模型大小呈正相关。8B 模型占据了榜首位置,其中 Qwen3-8B 在所有基准测试中都展现出非常稳定的性能(标准差最低)。 核心结论:如果你需要在不进行微调的情况下在零样本/小样本场景下也获得较优的性能,大模型仍是你的最佳选择。但请记住 —— 经过微调后,这种优势会减弱。 是的。Qwen3-4B-Instruct-2507 在 8 项基准测试中的 7 项上达到或超越了教师模型。 经过微调的 4B 学生模型在 6 项基准测试上超越了 120B+ 参数的教师模型,在 1 项(HotpotQA)上持平,仅在 1 项(Banking77)上略微落后(差距在误差范围内)。提升最显著的是 SQuAD 2.0 闭卷问答任务,学生模型比教师模型高出 19 个百分点 —— 这充分证明,微调比单纯依赖提示词(prompting)能更有效地将领域知识注入模型。 核心结论:一个经过适当微调的 4B 参数模型,可以媲美甚至超越规模达其 30 倍的模型。这意味着推理成本可降低约 30 倍,并且能够完全在本地部署运行。 基于我们的基准测试结果,以下是选择基础模型的建议: 本次基准测试只是一个起点,我们正在积极努力让这些结果更加可靠: 每个模型都在使用我们蒸馏流程生成的合成数据进行微调(有关数据合成过程的详细信息,请参见《Small Expert Agents from 10 Examples》[1])。针对每个基准测试,我们使用教师模型(GPTOss-120B)生成了 10,000 条训练样本。 微调采用 distil labs 的默认配置[2]:训练 4 个 epoch,学习率 5e-5,使用线性学习率调度器,以及 rank 为 64 的 LoRA。 所有模型均使用完全相同的超参数进行训练。评估在训练和合成数据生成过程中均未接触过的预留测试集上进行。 并非所有小型模型的性能都差不多,但经过微调后,它们之间的差距会大幅缩小。我们的基准测试表明,Qwen3-4B-Instruct-2507 在整体微调性能上表现最佳,不仅能媲美 120B+ 参数的教师模型,还能在单块消费级 GPU 上部署运行。在资源极度受限的环境中,像 Llama-3.2-1B 这样的小模型展现出卓越的可微调性,能够大幅缩小与大模型的性能差距。 核心结论:微调比基础模型的选择更重要。一个经过良好微调的 1B 模型,可以胜过仅靠提示词(prompting)驱动的 8B 模型。 END 本期互动内容 🍻 ❓你在微调小型语言模型时,最看重的是“开箱即用的强基础能力”,还是“微调后巨大的提升空间”?为什么? 文中链接 [1]https://www.distillabs.ai/blog/small-expert-agents-from-10-ex... [2]https://docs.distillabs.ai/how-to/input-preparation/config 原文链接: https://www.distillabs.ai/blog/we-benchmarked-12-small-langua...01 TL;DR
02 引言
03 实验方法
04 问题一:哪个模型在微调后效果最好?


05 问题二:哪个模型最具可微调性?(即微调后提升最大)


06 问题三:哪个模型的基础性能最强?(即未经微调前)


07 问题四:我们表现最好的学生模型,真的能媲美教师模型吗?


08 实用建议

09 后续我们将进行的工作
10 训练细节
11 结论
兄弟们,2026年了,AI Agent(智能体)绝对是最火的技术方向之一。什么是智能体?简单说就是:能感知、能思考、能行动的AI程序。 鸿蒙6在AI这块可以说是下了血本: 今天V哥就手把手带你做一个多模态AI智能助手,它能: 废话不多说,直接上代码! 在 ic_robot.svg: ic_voice.svg: 兄弟们,这套代码是V哥实战中总结出来的,完整实现了一个能用的AI智能体。当然,实际项目中你还需要: AI智能体的核心不是技术多牛逼,而是用户体验做得好! 关注V哥不迷路!前行路上不犯怵!大家好,我是V哥!今天要跟大家分享一个超级干货——如何在鸿蒙6(API21)上开发一个真正能用的AI智能体。不是那种玩具级别的Demo,而是能语音对话、能理解你意图、还能帮你干活的智能助手!
一、为什么要在鸿蒙上做AI智能体?
二、项目架构设计
┌─────────────────────────────────────────────────────────────────┐
│ AI智能体架构(V哥设计) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 语音输入 │───▶│ 语音识别 │───▶│ 意图理解 │ │
│ │ (ASR) │ │ Engine │ │ Engine │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 语音输出 │◀───│ 回复生成 │◀───│ 对话管理 │ │
│ │ (TTS) │ │ (LLM) │ │ Agent │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 任务执行 │ │
│ │ Actions │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘三、项目创建与配置
步骤1:创建项目
DevEco Studio → New Project
→ Empty Ability (Stage模型)
→ Project name: VGeAIAgent
→ Bundle name: com.vge.aiagent
→ Compile SDK: 5.0.0(API 12) 或更高步骤2:配置 module.json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:mic_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.INTERNET",
"reason": "$string:net_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:sync_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
}
}四、核心代码实现
1. 消息数据模型 (model/MessageModel.ets)
// entry/src/main/ets/model/MessageModel.ets
/**
* V哥设计的消息模型
* 支持多种消息类型,为后续扩展预留空间
*/
// 消息角色
export enum MessageRole {
USER = 'user', // 用户消息
ASSISTANT = 'assistant', // AI助手消息
SYSTEM = 'system' // 系统消息
}
// 消息类型
export enum MessageType {
TEXT = 'text', // 文本消息
VOICE = 'voice', // 语音消息
ACTION = 'action', // 执行动作
THINKING = 'thinking' // 思考中
}
// 意图类型
export enum IntentType {
CHAT = 'chat', // 闲聊
OPEN_APP = 'open_app', // 打开应用
SET_ALARM = 'set_alarm', // 设置闹钟
SET_REMINDER = 'set_reminder', // 设置提醒
QUERY_WEATHER = 'query_weather', // 查询天气
QUERY_TIME = 'query_time', // 查询时间
CONTROL_DEVICE = 'control_device',// 控制设备
UNKNOWN = 'unknown' // 未知意图
}
// 消息实体
export class Message {
id: string = '';
role: MessageRole = MessageRole.USER;
type: MessageType = MessageType.TEXT;
content: string = '';
timestamp: number = 0;
intent?: IntentType;
intentParams?: Record<string, string>;
isStreaming?: boolean; // 是否流式输出中
constructor(init?: Partial<Message>) {
this.id = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.timestamp = Date.now();
if (init) {
Object.assign(this, init);
}
}
}
// 对话上下文(用于多轮对话)
export class ConversationContext {
messages: Message[] = [];
maxHistory: number = 10; // 最多保留10轮对话
addMessage(message: Message): void {
this.messages.push(message);
// 超过限制则移除最早的消息
if (this.messages.length > this.maxHistory * 2) {
this.messages = this.messages.slice(-this.maxHistory * 2);
}
}
getHistory(): Message[] {
return this.messages;
}
clear(): void {
this.messages = [];
}
// 转换为LLM API需要的格式
toAPIFormat(): Array<{role: string, content: string}> {
return this.messages
.filter(m => m.type === MessageType.TEXT)
.map(m => ({
role: m.role,
content: m.content
}));
}
}2. 意图识别引擎 (engine/IntentEngine.ets)
// entry/src/main/ets/engine/IntentEngine.ets
import { IntentType } from '../model/MessageModel';
/**
* V哥的意图识别引擎
* 使用规则+关键词匹配,生产环境可接入NLU模型
*/
interface IntentRule {
intent: IntentType;
keywords: string[];
patterns: RegExp[];
extractor?: (text: string) => Record<string, string>;
}
export class IntentEngine {
private static instance: IntentEngine;
private rules: IntentRule[] = [];
private constructor() {
this.initRules();
}
static getInstance(): IntentEngine {
if (!IntentEngine.instance) {
IntentEngine.instance = new IntentEngine();
}
return IntentEngine.instance;
}
/**
* 初始化意图规则
*/
private initRules(): void {
this.rules = [
// 打开应用
{
intent: IntentType.OPEN_APP,
keywords: ['打开', '启动', '运行', '开启'],
patterns: [
/打开(.+?)(?:应用|app|APP)?$/,
/启动(.+)/,
/帮我开(.+)/
],
extractor: (text: string) => {
const appNames: Record<string, string> = {
'相机': 'com.huawei.camera',
'相册': 'com.huawei.photos',
'设置': 'com.huawei.settings',
'日历': 'com.huawei.calendar',
'计算器': 'com.huawei.calculator',
'备忘录': 'com.huawei.notes',
'音乐': 'com.huawei.music',
'视频': 'com.huawei.video',
'浏览器': 'com.huawei.browser',
'微信': 'com.tencent.mm',
'支付宝': 'com.eg.android.AlipayGphone',
'抖音': 'com.ss.android.ugc.aweme'
};
for (const [name, bundleName] of Object.entries(appNames)) {
if (text.includes(name)) {
return { appName: name, bundleName: bundleName };
}
}
return {};
}
},
// 设置闹钟
{
intent: IntentType.SET_ALARM,
keywords: ['闹钟', '叫我', '提醒我起床', '定个闹钟'],
patterns: [
/(\d{1,2})[点::](\d{0,2}).*(?:闹钟|叫我|起床)/,
/(?:明天|后天)?(?:早上|上午|中午|下午|晚上)?(\d{1,2})[点::]?(\d{0,2})?.*(?:闹钟|叫我)/,
/设.*闹钟.*(\d{1,2})[点::](\d{0,2})?/
],
extractor: (text: string) => {
const timeMatch = text.match(/(\d{1,2})[点::](\d{0,2})?/);
if (timeMatch) {
const hour = timeMatch[1];
const minute = timeMatch[2] || '00';
return { hour, minute };
}
return {};
}
},
// 设置提醒
{
intent: IntentType.SET_REMINDER,
keywords: ['提醒我', '别忘了', '记得'],
patterns: [
/(\d+)(?:分钟|小时)后提醒我(.+)/,
/提醒我(.+)/,
/(\d{1,2})[点::](\d{0,2})?提醒我(.+)/
],
extractor: (text: string) => {
// 提取时间和内容
const minuteMatch = text.match(/(\d+)分钟后提醒我(.+)/);
if (minuteMatch) {
return {
delayMinutes: minuteMatch[1],
content: minuteMatch[2]
};
}
const hourMatch = text.match(/(\d+)小时后提醒我(.+)/);
if (hourMatch) {
return {
delayMinutes: String(parseInt(hourMatch[1]) * 60),
content: hourMatch[2]
};
}
const contentMatch = text.match(/提醒我(.+)/);
if (contentMatch) {
return { content: contentMatch[1] };
}
return {};
}
},
// 查询天气
{
intent: IntentType.QUERY_WEATHER,
keywords: ['天气', '下雨', '温度', '气温', '穿什么'],
patterns: [
/(.+?)(?:的)?天气/,
/(?:今天|明天|后天).*(?:天气|下雨|温度)/,
/要不要带伞/
],
extractor: (text: string) => {
const cityMatch = text.match(/(.{2,4}?)(?:的)?天气/);
if (cityMatch && !['今天', '明天', '后天', '这里', '现在'].includes(cityMatch[1])) {
return { city: cityMatch[1] };
}
return { city: '北京' }; // 默认城市
}
},
// 查询时间
{
intent: IntentType.QUERY_TIME,
keywords: ['几点', '时间', '日期', '星期几', '今天几号'],
patterns: [
/现在几点/,
/什么时间/,
/今天.*(?:几号|星期几|周几)/
],
extractor: () => ({})
},
// 控制设备
{
intent: IntentType.CONTROL_DEVICE,
keywords: ['打开灯', '关灯', '开灯', '空调', '电视', '窗帘'],
patterns: [
/(打开|关闭|开|关)(.+?)(?:灯|空调|电视|窗帘)/,
/把(.+?)(打开|关闭|开|关)/,
/(.+?)(?:调到|设置为?)(\d+)度/
],
extractor: (text: string) => {
const actionMatch = text.match(/(打开|关闭|开|关)(.+)/);
if (actionMatch) {
return {
action: actionMatch[1].includes('开') ? 'on' : 'off',
device: actionMatch[2]
};
}
return {};
}
}
];
}
/**
* 识别用户意图
*/
recognize(text: string): { intent: IntentType; params: Record<string, string>; confidence: number } {
const normalizedText = text.toLowerCase().trim();
for (const rule of this.rules) {
// 关键词匹配
const keywordMatch = rule.keywords.some(kw => normalizedText.includes(kw));
// 正则匹配
const patternMatch = rule.patterns.some(pattern => pattern.test(normalizedText));
if (keywordMatch || patternMatch) {
const params = rule.extractor ? rule.extractor(normalizedText) : {};
const confidence = keywordMatch && patternMatch ? 0.95 : 0.75;
console.info(`[IntentEngine] 识别结果: ${rule.intent}, 置信度: ${confidence}`);
return {
intent: rule.intent,
params,
confidence
};
}
}
// 默认为闲聊
return {
intent: IntentType.CHAT,
params: {},
confidence: 0.5
};
}
}3. 大模型对话服务 (service/LLMService.ets)
// entry/src/main/ets/service/LLMService.ets
import { http } from '@kit.NetworkKit';
import { ConversationContext, Message, MessageRole } from '../model/MessageModel';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* V哥的LLM服务封装
* 支持多种大模型API,这里以通用格式为例
*/
// LLM配置接口
interface LLMConfig {
apiUrl: string;
apiKey: string;
model: string;
maxTokens: number;
temperature: number;
}
// API请求格式
interface ChatCompletionRequest {
model: string;
messages: Array<{ role: string; content: string }>;
max_tokens: number;
temperature: number;
stream: boolean;
}
// API响应格式
interface ChatCompletionResponse {
id: string;
choices: Array<{
message: {
role: string;
content: string;
};
finish_reason: string;
}>;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}
export class LLMService {
private static instance: LLMService;
private config: LLMConfig;
private systemPrompt: string;
private constructor() {
// 默认配置(实际使用时替换为你的API信息)
this.config = {
apiUrl: 'https://api.openai.com/v1/chat/completions', // 或其他兼容API
apiKey: 'your-api-key-here', // 替换为你的API Key
model: 'gpt-3.5-turbo',
maxTokens: 2048,
temperature: 0.7
};
// 系统提示词 - V哥精心调教
this.systemPrompt = `你是一个运行在鸿蒙系统上的AI智能助手,名叫"小V助手"。
你的特点:
1. 友好、幽默、专业
2. 回答简洁有力,不啰嗦
3. 能理解用户意图,给出实用建议
4. 熟悉鸿蒙生态和华为设备
5. 在适当时候使用emoji增加亲和力
你可以帮用户:
- 回答各种问题
- 闲聊解闷
- 提供建议和帮助
- 解释技术概念
请用中文回复,保持回答在100字以内(除非用户明确要求详细解释)。`;
}
static getInstance(): LLMService {
if (!LLMService.instance) {
LLMService.instance = new LLMService();
}
return LLMService.instance;
}
/**
* 更新配置
*/
updateConfig(config: Partial<LLMConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* 发送对话请求
*/
async chat(userMessage: string, context: ConversationContext): Promise<string> {
// 构建消息历史
const messages: Array<{ role: string; content: string }> = [
{ role: 'system', content: this.systemPrompt },
...context.toAPIFormat(),
{ role: 'user', content: userMessage }
];
const requestData: ChatCompletionRequest = {
model: this.config.model,
messages: messages,
max_tokens: this.config.maxTokens,
temperature: this.config.temperature,
stream: false
};
try {
const httpRequest = http.createHttp();
const response = await httpRequest.request(
this.config.apiUrl,
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`
},
extraData: JSON.stringify(requestData),
connectTimeout: 30000,
readTimeout: 60000
}
);
httpRequest.destroy();
if (response.responseCode === 200) {
const result = JSON.parse(response.result as string) as ChatCompletionResponse;
const content = result.choices[0]?.message?.content || '抱歉,我没有理解你的意思';
console.info(`[LLMService] 响应成功,Token使用: ${result.usage?.total_tokens}`);
return content;
} else {
console.error(`[LLMService] API错误: ${response.responseCode}`);
return this.getFallbackResponse(userMessage);
}
} catch (err) {
const error = err as BusinessError;
console.error(`[LLMService] 请求失败: ${error.code} - ${error.message}`);
return this.getFallbackResponse(userMessage);
}
}
/**
* 离线兜底回复(当API不可用时)
*/
private getFallbackResponse(userMessage: string): string {
const fallbackResponses: Record<string, string[]> = {
'你好': ['你好呀!有什么可以帮你的?', '嗨!我是小V助手,很高兴见到你!'],
'谢谢': ['不客气!随时为你服务~', '应该的,还有什么需要帮助的吗?'],
'再见': ['再见!期待下次聊天~', '拜拜,有事随时找我哦!'],
'你是谁': ['我是小V助手,运行在鸿蒙系统上的AI助手!', '我叫小V,是V哥打造的智能助手~'],
'你能做什么': ['我能陪你聊天、回答问题、帮你打开应用、设置提醒等等!试试看吧~',
'我可以:闲聊解闷、回答问题、控制设备、设置闹钟提醒...功能多多!']
};
// 关键词匹配
for (const [keyword, responses] of Object.entries(fallbackResponses)) {
if (userMessage.includes(keyword)) {
return responses[Math.floor(Math.random() * responses.length)];
}
}
// 默认回复
const defaultResponses = [
'我现在网络不太好,稍后再试试吧~',
'让我想想... 你能换个方式问我吗?',
'抱歉,我没太理解,能再说一遍吗?',
'网络开小差了,不过我们可以继续聊别的!'
];
return defaultResponses[Math.floor(Math.random() * defaultResponses.length)];
}
/**
* 流式对话(支持打字机效果)
*/
async chatStream(
userMessage: string,
context: ConversationContext,
onChunk: (chunk: string) => void,
onComplete: (fullText: string) => void
): Promise<void> {
// 简化实现:模拟流式输出
const response = await this.chat(userMessage, context);
let index = 0;
const interval = setInterval(() => {
if (index < response.length) {
onChunk(response[index]);
index++;
} else {
clearInterval(interval);
onComplete(response);
}
}, 30); // 每30ms输出一个字符
}
}4. 语音服务封装 (service/VoiceService.ets)
// entry/src/main/ets/service/VoiceService.ets
import { speechRecognizer } from '@kit.CoreSpeechKit';
import { textToSpeech } from '@kit.CoreSpeechKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
/**
* V哥的语音服务封装
* 整合ASR语音识别 + TTS语音合成
*/
export class VoiceService {
private static instance: VoiceService;
private asrEngine: speechRecognizer.SpeechRecognitionEngine | null = null;
private ttsEngine: textToSpeech.TextToSpeechEngine | null = null;
private isListening: boolean = false;
private constructor() {}
static getInstance(): VoiceService {
if (!VoiceService.instance) {
VoiceService.instance = new VoiceService();
}
return VoiceService.instance;
}
/**
* 请求麦克风权限
*/
async requestPermission(context: Context): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
const permissions: Permissions[] = ['ohos.permission.MICROPHONE'];
try {
const result = await atManager.requestPermissionsFromUser(context, permissions);
const granted = result.authResults.every(r => r === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED);
console.info(`[VoiceService] 麦克风权限: ${granted ? '已授权' : '被拒绝'}`);
return granted;
} catch (err) {
console.error('[VoiceService] 请求权限失败:', JSON.stringify(err));
return false;
}
}
/**
* 初始化语音识别引擎
*/
async initASR(): Promise<boolean> {
try {
const createParams: speechRecognizer.CreateEngineParams = {
language: 'zh-CN',
online: 1 // 1-在线识别 0-离线识别
};
this.asrEngine = await speechRecognizer.createEngine(createParams);
console.info('[VoiceService] ASR引擎初始化成功');
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[VoiceService] ASR初始化失败: ${error.code} - ${error.message}`);
return false;
}
}
/**
* 初始化语音合成引擎
*/
async initTTS(): Promise<boolean> {
try {
const createParams: textToSpeech.CreateEngineParams = {
language: 'zh-CN',
person: 0, // 发音人
online: 1 // 1-在线合成 0-离线合成
};
const extraParams: Record<string, Object> = {
style: 'normal',
speed: 1.0,
volume: 1.0,
pitch: 1.0
};
this.ttsEngine = await textToSpeech.createEngine(createParams);
console.info('[VoiceService] TTS引擎初始化成功');
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[VoiceService] TTS初始化失败: ${error.code} - ${error.message}`);
return false;
}
}
/**
* 开始语音识别
*/
async startListening(
onResult: (text: string, isFinal: boolean) => void,
onError: (error: string) => void
): Promise<void> {
if (!this.asrEngine) {
const success = await this.initASR();
if (!success) {
onError('语音识别引擎初始化失败');
return;
}
}
if (this.isListening) {
console.warn('[VoiceService] 已经在监听中');
return;
}
try {
// 设置回调
this.asrEngine!.setListener({
onStart: (sessionId: string) => {
console.info(`[VoiceService] 开始识别, sessionId: ${sessionId}`);
this.isListening = true;
},
onEvent: (sessionId: string, eventCode: number) => {
console.info(`[VoiceService] 事件: ${eventCode}`);
},
onResult: (sessionId: string, result: speechRecognizer.SpeechRecognitionResult) => {
const text = result.result;
const isFinal = result.isFinal;
console.info(`[VoiceService] 识别结果: ${text}, isFinal: ${isFinal}`);
onResult(text, isFinal);
},
onComplete: (sessionId: string) => {
console.info(`[VoiceService] 识别完成`);
this.isListening = false;
},
onError: (sessionId: string, errorCode: number, errorMessage: string) => {
console.error(`[VoiceService] 识别错误: ${errorCode} - ${errorMessage}`);
this.isListening = false;
onError(errorMessage);
}
});
// 开始识别
const recognitionParams: speechRecognizer.StartParams = {
sessionId: `session_${Date.now()}`,
audioInfo: {
audioType: 'pcm',
sampleRate: 16000,
soundChannel: 1,
sampleBit: 16
},
extraParams: {
vadBegin: 2000, // 静音检测开始时间
vadEnd: 3000, // 静音检测结束时间
maxAudioDuration: 60000 // 最大录音时长
}
};
await this.asrEngine!.startListening(recognitionParams);
} catch (err) {
const error = err as BusinessError;
console.error(`[VoiceService] 开始识别失败: ${error.code} - ${error.message}`);
onError(error.message);
}
}
/**
* 停止语音识别
*/
async stopListening(): Promise<void> {
if (this.asrEngine && this.isListening) {
try {
await this.asrEngine.finish(`session_stop_${Date.now()}`);
this.isListening = false;
console.info('[VoiceService] 停止识别');
} catch (err) {
console.error('[VoiceService] 停止识别失败:', JSON.stringify(err));
}
}
}
/**
* 语音合成(文字转语音)
*/
async speak(text: string, onComplete?: () => void): Promise<void> {
if (!this.ttsEngine) {
const success = await this.initTTS();
if (!success) {
console.error('[VoiceService] TTS引擎不可用');
onComplete?.();
return;
}
}
try {
// 设置回调
this.ttsEngine!.setListener({
onStart: (requestId: string) => {
console.info(`[VoiceService] 开始播放, requestId: ${requestId}`);
},
onProgress: (requestId: string, progress: number) => {
// 播放进度
},
onFinish: (requestId: string) => {
console.info(`[VoiceService] 播放完成`);
onComplete?.();
},
onError: (requestId: string, errorCode: number, errorMessage: string) => {
console.error(`[VoiceService] 播放错误: ${errorCode} - ${errorMessage}`);
onComplete?.();
}
});
// 合成参数
const speakParams: textToSpeech.SpeakParams = {
requestId: `speak_${Date.now()}`,
extraParams: {
speed: 1.0,
volume: 1.0,
pitch: 1.0
}
};
await this.ttsEngine!.speak(text, speakParams);
} catch (err) {
const error = err as BusinessError;
console.error(`[VoiceService] 语音合成失败: ${error.code} - ${error.message}`);
onComplete?.();
}
}
/**
* 停止语音播放
*/
async stopSpeaking(): Promise<void> {
if (this.ttsEngine) {
try {
await this.ttsEngine.stop();
console.info('[VoiceService] 停止播放');
} catch (err) {
console.error('[VoiceService] 停止播放失败:', JSON.stringify(err));
}
}
}
/**
* 释放资源
*/
async release(): Promise<void> {
try {
if (this.asrEngine) {
await this.asrEngine.shutdown();
this.asrEngine = null;
}
if (this.ttsEngine) {
await this.ttsEngine.shutdown();
this.ttsEngine = null;
}
console.info('[VoiceService] 资源释放完成');
} catch (err) {
console.error('[VoiceService] 释放资源失败:', JSON.stringify(err));
}
}
/**
* 获取监听状态
*/
getListeningState(): boolean {
return this.isListening;
}
}5. 任务执行器 (engine/ActionExecutor.ets)
// entry/src/main/ets/engine/ActionExecutor.ets
import { bundleManager, common, Want } from '@kit.AbilityKit';
import { IntentType } from '../model/MessageModel';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* V哥的任务执行器
* 根据意图执行具体操作
*/
interface ActionResult {
success: boolean;
message: string;
data?: object;
}
export class ActionExecutor {
private static instance: ActionExecutor;
private context: common.UIAbilityContext | null = null;
private constructor() {}
static getInstance(): ActionExecutor {
if (!ActionExecutor.instance) {
ActionExecutor.instance = new ActionExecutor();
}
return ActionExecutor.instance;
}
/**
* 设置上下文
*/
setContext(context: common.UIAbilityContext): void {
this.context = context;
}
/**
* 执行动作
*/
async execute(intent: IntentType, params: Record<string, string>): Promise<ActionResult> {
console.info(`[ActionExecutor] 执行意图: ${intent}, 参数: ${JSON.stringify(params)}`);
switch (intent) {
case IntentType.OPEN_APP:
return this.openApp(params);
case IntentType.SET_ALARM:
return this.setAlarm(params);
case IntentType.SET_REMINDER:
return this.setReminder(params);
case IntentType.QUERY_WEATHER:
return this.queryWeather(params);
case IntentType.QUERY_TIME:
return this.queryTime();
case IntentType.CONTROL_DEVICE:
return this.controlDevice(params);
default:
return {
success: false,
message: '暂不支持该操作'
};
}
}
/**
* 打开应用
*/
private async openApp(params: Record<string, string>): Promise<ActionResult> {
const bundleName = params.bundleName;
const appName = params.appName;
if (!bundleName) {
return {
success: false,
message: `抱歉,我不知道怎么打开"${appName || '这个应用'}"`
};
}
try {
const want: Want = {
bundleName: bundleName,
action: 'action.system.home',
entities: ['entity.system.home']
};
await this.context?.startAbility(want);
return {
success: true,
message: `已为你打开${appName}`
};
} catch (err) {
const error = err as BusinessError;
console.error(`[ActionExecutor] 打开应用失败: ${error.code} - ${error.message}`);
return {
success: false,
message: `打开${appName}失败,可能是应用未安装`
};
}
}
/**
* 设置闹钟
*/
private async setAlarm(params: Record<string, string>): Promise<ActionResult> {
const hour = parseInt(params.hour || '8');
const minute = parseInt(params.minute || '0');
try {
// 调用系统闹钟
const want: Want = {
action: 'ohos.want.action.setAlarm',
parameters: {
'ringtone': 'default',
'hour': hour,
'minute': minute
}
};
await this.context?.startAbility(want);
const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
return {
success: true,
message: `好的,已为你设置${timeStr}的闹钟`
};
} catch (err) {
console.error('[ActionExecutor] 设置闹钟失败:', JSON.stringify(err));
return {
success: false,
message: '设置闹钟失败,请手动设置'
};
}
}
/**
* 设置提醒
*/
private async setReminder(params: Record<string, string>): Promise<ActionResult> {
const content = params.content || '未命名提醒';
const delayMinutes = parseInt(params.delayMinutes || '10');
// 这里可以接入前面的日程提醒模块
return {
success: true,
message: `收到!${delayMinutes}分钟后提醒你:${content}`
};
}
/**
* 查询天气
*/
private async queryWeather(params: Record<string, string>): Promise<ActionResult> {
const city = params.city || '北京';
// 实际项目中对接天气API
// 这里返回模拟数据
const mockWeather = {
city: city,
temperature: Math.floor(Math.random() * 20) + 10,
weather: ['晴', '多云', '阴', '小雨'][Math.floor(Math.random() * 4)],
humidity: Math.floor(Math.random() * 40) + 40
};
return {
success: true,
message: `${city}今天${mockWeather.weather},气温${mockWeather.temperature}°C,湿度${mockWeather.humidity}%`,
data: mockWeather
};
}
/**
* 查询时间
*/
private queryTime(): ActionResult {
const now = new Date();
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
const dateStr = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
const weekStr = `星期${weekDays[now.getDay()]}`;
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
return {
success: true,
message: `现在是${dateStr} ${weekStr} ${timeStr}`
};
}
/**
* 控制设备
*/
private async controlDevice(params: Record<string, string>): Promise<ActionResult> {
const device = params.device || '设备';
const action = params.action === 'on' ? '打开' : '关闭';
// 实际项目中对接智能家居API
return {
success: true,
message: `好的,已${action}${device}`
};
}
}6. AI智能体核心 (engine/AIAgent.ets)
// entry/src/main/ets/engine/AIAgent.ets
import { Message, MessageRole, MessageType, ConversationContext, IntentType } from '../model/MessageModel';
import { IntentEngine } from './IntentEngine';
import { ActionExecutor } from './ActionExecutor';
import { LLMService } from '../service/LLMService';
import { VoiceService } from '../service/VoiceService';
/**
* V哥的AI智能体核心
* 整合所有能力,实现智能对话
*/
export class AIAgent {
private static instance: AIAgent;
private context: ConversationContext;
private intentEngine: IntentEngine;
private actionExecutor: ActionExecutor;
private llmService: LLMService;
private voiceService: VoiceService;
// 回调函数
private onMessageCallback?: (message: Message) => void;
private onStateChangeCallback?: (state: AgentState) => void;
private constructor() {
this.context = new ConversationContext();
this.intentEngine = IntentEngine.getInstance();
this.actionExecutor = ActionExecutor.getInstance();
this.llmService = LLMService.getInstance();
this.voiceService = VoiceService.getInstance();
}
static getInstance(): AIAgent {
if (!AIAgent.instance) {
AIAgent.instance = new AIAgent();
}
return AIAgent.instance;
}
/**
* 设置消息回调
*/
setOnMessage(callback: (message: Message) => void): void {
this.onMessageCallback = callback;
}
/**
* 设置状态回调
*/
setOnStateChange(callback: (state: AgentState) => void): void {
this.onStateChangeCallback = callback;
}
/**
* 处理用户输入(核心方法)
*/
async processInput(userInput: string): Promise<void> {
if (!userInput.trim()) return;
console.info(`[AIAgent] 处理用户输入: ${userInput}`);
// 1. 创建用户消息
const userMessage = new Message({
role: MessageRole.USER,
type: MessageType.TEXT,
content: userInput
});
this.context.addMessage(userMessage);
this.onMessageCallback?.(userMessage);
// 2. 意图识别
this.onStateChangeCallback?.(AgentState.THINKING);
const { intent, params, confidence } = this.intentEngine.recognize(userInput);
console.info(`[AIAgent] 意图识别: ${intent}, 置信度: ${confidence}`);
// 3. 根据意图决定处理方式
let response: string;
if (intent !== IntentType.CHAT && confidence >= 0.7) {
// 高置信度的功能意图,执行动作
userMessage.intent = intent;
userMessage.intentParams = params;
const result = await this.actionExecutor.execute(intent, params);
response = result.message;
// 如果是需要补充信息的场景,继续调用LLM
if (!result.success && intent !== IntentType.UNKNOWN) {
response = await this.llmService.chat(
`用户说"${userInput}",我尝试${this.getIntentDescription(intent)}但失败了。请给出友好的回复和建议。`,
this.context
);
}
} else {
// 闲聊或低置信度,调用大模型
response = await this.llmService.chat(userInput, this.context);
}
// 4. 创建助手回复
const assistantMessage = new Message({
role: MessageRole.ASSISTANT,
type: MessageType.TEXT,
content: response
});
this.context.addMessage(assistantMessage);
this.onMessageCallback?.(assistantMessage);
this.onStateChangeCallback?.(AgentState.IDLE);
// 5. 语音播报回复
await this.voiceService.speak(response);
}
/**
* 开始语音输入
*/
async startVoiceInput(): Promise<void> {
this.onStateChangeCallback?.(AgentState.LISTENING);
await this.voiceService.startListening(
(text: string, isFinal: boolean) => {
if (isFinal && text.trim()) {
this.processInput(text);
}
},
(error: string) => {
console.error('[AIAgent] 语音识别错误:', error);
this.onStateChangeCallback?.(AgentState.IDLE);
}
);
}
/**
* 停止语音输入
*/
async stopVoiceInput(): Promise<void> {
await this.voiceService.stopListening();
this.onStateChangeCallback?.(AgentState.IDLE);
}
/**
* 获取对话历史
*/
getHistory(): Message[] {
return this.context.getHistory();
}
/**
* 清空对话
*/
clearHistory(): void {
this.context.clear();
}
/**
* 获取意图描述
*/
private getIntentDescription(intent: IntentType): string {
const descriptions: Record<IntentType, string> = {
[IntentType.OPEN_APP]: '打开应用',
[IntentType.SET_ALARM]: '设置闹钟',
[IntentType.SET_REMINDER]: '设置提醒',
[IntentType.QUERY_WEATHER]: '查询天气',
[IntentType.QUERY_TIME]: '查询时间',
[IntentType.CONTROL_DEVICE]: '控制设备',
[IntentType.CHAT]: '闲聊',
[IntentType.UNKNOWN]: '理解意图'
};
return descriptions[intent] || '执行操作';
}
}
/**
* 智能体状态
*/
export enum AgentState {
IDLE = 'idle', // 空闲
LISTENING = 'listening', // 监听中
THINKING = 'thinking', // 思考中
SPEAKING = 'speaking' // 说话中
}7. 主界面 (pages/Index.ets)
// entry/src/main/ets/pages/Index.ets
import { Message, MessageRole, MessageType } from '../model/MessageModel';
import { AIAgent, AgentState } from '../engine/AIAgent';
import { ActionExecutor } from '../engine/ActionExecutor';
import { VoiceService } from '../service/VoiceService';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State messageList: Message[] = [];
@State inputText: string = '';
@State agentState: AgentState = AgentState.IDLE;
@State isVoiceMode: boolean = false;
private agent: AIAgent = AIAgent.getInstance();
private voiceService: VoiceService = VoiceService.getInstance();
private scroller: Scroller = new Scroller();
private context = getContext(this) as common.UIAbilityContext;
async aboutToAppear(): Promise<void> {
// 初始化
ActionExecutor.getInstance().setContext(this.context);
// 请求权限
await this.voiceService.requestPermission(this.context);
// 设置回调
this.agent.setOnMessage((message: Message) => {
this.messageList = [...this.messageList, message];
// 滚动到底部
setTimeout(() => {
this.scroller.scrollEdge(Edge.Bottom);
}, 100);
});
this.agent.setOnStateChange((state: AgentState) => {
this.agentState = state;
});
// 添加欢迎消息
const welcomeMessage = new Message({
role: MessageRole.ASSISTANT,
type: MessageType.TEXT,
content: '你好!我是小V助手 🤖\n\n我可以帮你:\n• 回答各种问题\n• 打开应用\n• 设置闹钟和提醒\n• 查询天气和时间\n• 控制智能设备\n\n试着对我说点什么吧!'
});
this.messageList.push(welcomeMessage);
}
/**
* 发送消息
*/
async sendMessage(): Promise<void> {
if (!this.inputText.trim()) return;
const text = this.inputText.trim();
this.inputText = '';
await this.agent.processInput(text);
}
/**
* 切换语音模式
*/
async toggleVoiceMode(): Promise<void> {
if (this.isVoiceMode) {
// 停止语音输入
await this.agent.stopVoiceInput();
this.isVoiceMode = false;
} else {
// 开始语音输入
this.isVoiceMode = true;
await this.agent.startVoiceInput();
}
}
/**
* 获取状态文本
*/
getStateText(): string {
switch (this.agentState) {
case AgentState.LISTENING:
return '正在听...';
case AgentState.THINKING:
return '思考中...';
case AgentState.SPEAKING:
return '说话中...';
default:
return '';
}
}
/**
* 格式化时间
*/
formatTime(timestamp: number): string {
const date = new Date(timestamp);
const hour = date.getHours().toString().padStart(2, '0');
const minute = date.getMinutes().toString().padStart(2, '0');
return `${hour}:${minute}`;
}
build() {
Column() {
// 顶部标题栏
Row() {
Column() {
Text('小V助手')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
if (this.agentState !== AgentState.IDLE) {
Row() {
LoadingProgress()
.width(14)
.height(14)
.color('#007DFF')
Text(this.getStateText())
.fontSize(12)
.fontColor('#007DFF')
.margin({ left: 4 })
}
.margin({ top: 2 })
}
}
.alignItems(HorizontalAlign.Start)
Blank()
// 清空对话按钮
Button() {
Image($r('app.media.ic_clear'))
.width(20)
.height(20)
.fillColor('#666666')
}
.width(40)
.height(40)
.backgroundColor('#F0F0F0')
.borderRadius(20)
.onClick(() => {
promptAction.showDialog({
title: '清空对话',
message: '确定要清空所有对话记录吗?',
buttons: [
{ text: '取消', color: '#666666' },
{ text: '确定', color: '#007DFF' }
]
}).then((result) => {
if (result.index === 1) {
this.messageList = [];
this.agent.clearHistory();
}
});
})
}
.width('100%')
.height(60)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
// 消息列表
List({ scroller: this.scroller, space: 16 }) {
ForEach(this.messageList, (message: Message) => {
ListItem() {
this.MessageBubble(message)
}
}, (message: Message) => message.id)
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#F5F5F5')
// 底部输入区域
Row() {
// 语音按钮
Button() {
Image(this.isVoiceMode ? $r('app.media.ic_keyboard') : $r('app.media.ic_voice'))
.width(24)
.height(24)
.fillColor(this.isVoiceMode ? '#FF3B30' : '#666666')
}
.width(44)
.height(44)
.backgroundColor(this.isVoiceMode ? '#FFE5E5' : '#F0F0F0')
.borderRadius(22)
.onClick(() => this.toggleVoiceMode())
if (this.isVoiceMode) {
// 语音输入状态
Column() {
if (this.agentState === AgentState.LISTENING) {
Row() {
ForEach([1, 2, 3, 4, 5], (i: number) => {
Column()
.width(4)
.height(12 + Math.random() * 20)
.backgroundColor('#007DFF')
.borderRadius(2)
.margin({ left: 4, right: 4 })
.animation({
duration: 300,
iterations: -1,
curve: Curve.EaseInOut
})
})
}
.justifyContent(FlexAlign.Center)
}
Text(this.agentState === AgentState.LISTENING ? '正在聆听...' : '点击麦克风开始说话')
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8 })
}
.layoutWeight(1)
.height(44)
.justifyContent(FlexAlign.Center)
} else {
// 文字输入框
TextInput({ placeholder: '输入消息...', text: this.inputText })
.layoutWeight(1)
.height(44)
.backgroundColor('#F5F5F5')
.borderRadius(22)
.padding({ left: 16, right: 16 })
.margin({ left: 8, right: 8 })
.onChange((value) => {
this.inputText = value;
})
.onSubmit(() => {
this.sendMessage();
})
// 发送按钮
Button() {
Image($r('app.media.ic_send'))
.width(24)
.height(24)
.fillColor(Color.White)
}
.width(44)
.height(44)
.backgroundColor(this.inputText.trim() ? '#007DFF' : '#CCCCCC')
.borderRadius(22)
.enabled(this.inputText.trim().length > 0)
.onClick(() => this.sendMessage())
}
}
.width('100%')
.height(70)
.padding({ left: 12, right: 12, top: 8, bottom: 16 })
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
}
/**
* 消息气泡组件
*/
@Builder
MessageBubble(message: Message) {
Column() {
if (message.role === MessageRole.USER) {
// 用户消息(右侧)
Row() {
Blank()
Column() {
Text(message.content)
.fontSize(15)
.fontColor(Color.White)
.lineHeight(22)
}
.padding(12)
.backgroundColor('#007DFF')
.borderRadius({
topLeft: 16,
topRight: 4,
bottomLeft: 16,
bottomRight: 16
})
.constraintSize({ maxWidth: '75%' })
// 用户头像
Image($r('app.media.ic_user'))
.width(36)
.height(36)
.borderRadius(18)
.margin({ left: 8 })
}
.width('100%')
.justifyContent(FlexAlign.End)
} else {
// AI消息(左侧)
Row() {
// AI头像
Stack() {
Circle()
.width(36)
.height(36)
.fill('#E6F2FF')
Image($r('app.media.ic_robot'))
.width(24)
.height(24)
}
.margin({ right: 8 })
Column() {
Text(message.content)
.fontSize(15)
.fontColor('#333333')
.lineHeight(22)
// 显示时间
Text(this.formatTime(message.timestamp))
.fontSize(11)
.fontColor('#999999')
.margin({ top: 4 })
}
.padding(12)
.backgroundColor(Color.White)
.borderRadius({
topLeft: 4,
topRight: 16,
bottomLeft: 16,
bottomRight: 16
})
.constraintSize({ maxWidth: '75%' })
.alignItems(HorizontalAlign.Start)
.shadow({
radius: 4,
color: 'rgba(0,0,0,0.05)',
offsetX: 0,
offsetY: 2
})
Blank()
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
}
}
}8. 快捷指令面板 (components/QuickCommands.ets)
// entry/src/main/ets/components/QuickCommands.ets
/**
* V哥设计的快捷指令面板
* 方便用户快速触发常用功能
*/
interface QuickCommand {
icon: Resource;
label: string;
command: string;
color: string;
}
@Component
export struct QuickCommands {
onCommand: (command: string) => void = () => {};
private commands: QuickCommand[] = [
{ icon: $r('app.media.ic_weather'), label: '查天气', command: '今天天气怎么样', color: '#FFB800' },
{ icon: $r('app.media.ic_time'), label: '查时间', command: '现在几点了', color: '#007DFF' },
{ icon: $r('app.media.ic_alarm'), label: '设闹钟', command: '明天早上7点叫我起床', color: '#34C759' },
{ icon: $r('app.media.ic_remind'), label: '提醒我', command: '10分钟后提醒我喝水', color: '#FF9500' },
{ icon: $r('app.media.ic_app'), label: '打开相机', command: '打开相机', color: '#AF52DE' },
{ icon: $r('app.media.ic_home'), label: '开灯', command: '打开客厅的灯', color: '#FF3B30' }
];
build() {
Column() {
Text('快捷指令')
.fontSize(14)
.fontColor('#999999')
.margin({ bottom: 12 })
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.commands, (cmd: QuickCommand) => {
Column() {
Stack() {
Circle()
.width(44)
.height(44)
.fill(cmd.color)
.opacity(0.15)
Image(cmd.icon)
.width(24)
.height(24)
.fillColor(cmd.color)
}
Text(cmd.label)
.fontSize(12)
.fontColor('#666666')
.margin({ top: 6 })
}
.width('30%')
.margin({ bottom: 16 })
.onClick(() => {
this.onCommand(cmd.command);
})
})
}
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
}
}五、资源文件准备
需要的图标资源
entry/src/main/resources/base/media/ 添加以下图标:文件名 用途 建议尺寸 ic_robot.svg AI头像 48x48 ic_user.svg 用户头像 48x48 ic_send.svg 发送按钮 24x24 ic_voice.svg 语音按钮 24x24 ic_keyboard.svg 键盘按钮 24x24 ic_clear.svg 清空按钮 24x24 ic_weather.svg 天气图标 24x24 ic_time.svg 时间图标 24x24 ic_alarm.svg 闹钟图标 24x24 ic_remind.svg 提醒图标 24x24 ic_app.svg 应用图标 24x24 ic_home.svg 智能家居图标 24x24 示例SVG
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<circle cx="24" cy="24" r="20" fill="#007DFF"/>
<circle cx="17" cy="20" r="3" fill="white"/>
<circle cx="31" cy="20" r="3" fill="white"/>
<path d="M16 30 Q24 36 32 30" stroke="white" stroke-width="2" fill="none"/>
<rect x="22" y="4" width="4" height="6" rx="2" fill="#007DFF"/>
<circle cx="24" cy="4" r="3" fill="#007DFF"/>
</svg><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
</svg>六、V哥总结:关键技术点
1. 意图识别的设计思路
用户输入 → 规则匹配(快速) → 高置信度直接执行
↓
低置信度 → 调用LLM兜底2. 对话管理的核心
// 多轮对话的关键:上下文管理
class ConversationContext {
messages: Message[] = [];
maxHistory: number = 10; // 控制历史长度,避免Token浪费
}3. 语音交互的最佳实践
4. 性能优化建议
七、V哥唠两句
如果你有搞到的 Google 学生订阅福利优惠,那 API 调用也可以用了。
不用之前的反代模式才能拿到 banana 等 API 调用啦。
Google 宣布:AI Pro / Ultra 订阅现在自带 GDP Premium 权益——每月送 GCP 代金:Pro 送 10 美金、Ultra 送 100 美金,可直接拿去跑 Gemini API 、Vertex AI 、Cloud Run 等,把聊天里的点子一路推到上线。
一句话:从“玩模型”到“上生产”,Google 帮你把中间那道付费墙拆了一块。
文章详情: https://blog.google/innovation-and-ai/technology/developers-tools/gdp-premium-ai-pro-ultra/
目前看的一个洛斐 Flow2 ,不知道品质如何,有没有推荐的其他品牌的键盘(之前两把 keychron 都用坏了),目前用的苹果的键盘,十分难受,希望就是机械键盘,不要太大,可以放背包带着走的,HHKB 这种价格太高,而且配列感觉适应起来麻烦。差不多 500-800 左右的即可,三模充电的。
截止到 utc 时间 2026.1.27, 以下帖子获得额外的活动奖励:
@287854442 <<一个大胆的预言:语音输入将成为绝对主流>>
@sillydaddy <<vibe coding 的最佳实践到底是什么?>>
@287854442 <<一个大胆的预言:语音输入将成为绝对主流>>
<<一个大胆的预言:语音输入将成为绝对主流>> 作者: @287854442
<<"AI 与编程" 几个月来高强度 vibe coding 的一点心得>> 作者: @mkq
<<AI 时代,笔记的结局是什么?>> 作者: @287854442
<<"AI 与编程" 近半年工作使用 Cursor 的感受>> 作者: @lenglengyuchen
<<vibe coding 的最佳实践到底是什么?>> 作者: @sillydaddy
<<我从来不是创造型人才 - AI 驱动编程下的迷思>> 作者: @Zhuzhuchenyan
<<未来 10 年程序员技术水平分布会是什么结构?>> 作者: @Kinnikuman
<<AI 狂热的冷思考>> 作者: @shoushen
<<AI 浪潮下,程序员教育该如何转型?>> 作者: @Dabney
<<大家都是如何使用 AI 提升工作效率的?>> 作者: @darktutu
<<我们真的应该完全放弃《古法编程》?>> 作者: @ybz
<<随着 AI 的发展,"眼高手低"会不会逐渐变成优势?>> 作者: @funtanstic
最后, 再次感谢所有参与分享与讨论的 V 友, 欢迎大家关注Joe's Talk, 让我们一起来建设新的无水板块.
3 个月前买过一把京东京造的智能锁 M30 Max ,这锁离了个大谱。 新锁第一次上电池,只坚持了 15 天就没电了,想着可能是因为新电池没满。充满装上只还是只坚持了 13 天,联系客服,说是可能电池问题,给补发了一个。换了新电池,新电池来了更夸张,只能坚持 10 天就没电了。再联系客服,客服反应说是可能设置有问题,找了师傅上门,师傅上门鼓捣一番下来,把联网功能,视频功能,除了开锁以外的所有功能全关了。本以为可以消停一下了,结果现在充满电后的电池只能用 8 天了。
现在又得新寄了一把一样一样的给我,说实话我打算换这个锁了,不知道有没有用过的,你们的电池是否正常?