【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制

智能语音控制系统部分架构图

我今天主要总结业务中最核心的接入识别部分,第一次做肯定会遇到一些问题总结给大家参考

新手必看|微信小程序与Spring Boot后端交互全流程(踩坑+解决+完整代码)

作为编程新手,第一次做「小程序+后端」联调时,很容易被各种网络问题、字段匹配问题、环境配置问题卡住。本文以「智能设备语音控制小程序」为例,完整记录从0到1实现小程序与Spring Boot后端交互的全过程------包括环境配置、代码编写、遇到的每一个问题及解决方案,全程迭代式讲解,新手也能跟着一步步复刻。

最新效果如图


真机调试需要关闭防火墙

一、前置准备:明确需求与环境

1. 核心需求

实现一个简单的语音控制小程序:

  • 小程序端:录音→调用百度语音转文字→将指令(如"打开客厅灯")传给后端;
  • 后端:解析指令→下发指令到远程设备→后端异步返回指令处理结果→小程序同步设备状态。

2. 必备工具/环境

类型 工具/版本 新手备注
后端 JDK 8+、Spring Boot 2.7+、IDEA 确保JDK环境变量配置正确
前端 微信开发者工具、小程序账号 无需认证,测试阶段用测试号即可
网络 同一局域网(电脑+手机) 真机测试必须满足此条件
第三方 百度语音识别API Key/Secret 免费申请,用于语音转文字

二、第一步:后端(Spring Boot)开发(新手友好版)

1. 创建Spring Boot项目

打开IDEA,新建Spring Boot项目,勾选核心依赖:Spring Web(web开发)、Spring AI大模型集成、Lombok(简化代码)。

2. 核心代码编写(迭代1:基础结构)

(1)定义枚举(指令动作/状态)

新手容易忽略枚举的序列化问题,先定义核心枚举:

java 复制代码
// 指令动作枚举(打开/关闭)
public enum CommandType {
    OPEN,  // 打开
    CLOSE  // 关闭
}

// 指令状态枚举(执行中/成功/失败)
public enum CommandStatus {
    EXECUTING, // 执行中
    SUCCESS,   // 执行成功
    FAIL       // 执行失败
}
(2)定义交互DTO(前后端数据传输对象)
java 复制代码
// 后端接收小程序请求的DTO
@Data
public class CommandProcessRequest {
    private String voiceText;  // 小程序传的语音指令文本
    private String userId;     // 用户ID(测试用固定值)
    private String aiModelType;// AI模型类型(测试用baidu)
}

// 后端返回给小程序的DTO
@Data
public class CommandProcessResponse {
    private String commandId;    // 指令ID
    private String deviceCode;   // 设备编码(如客厅灯=LT001)
    private String deviceName;   // 设备名称(如客厅主灯)
    private CommandType actionType; // 动作类型(OPEN/CLOSE)
    private LocalDateTime executedAt; // 执行时间
    private CommandStatus status;     // 指令状态
}
(3)定义统一返回结果(核心!新手必写)

前后端交互必须有统一的返回格式,否则小程序解析会混乱:

java 复制代码
// 统一返回结果类
@Data
public class Result<T> {
    private int code;    // 状态码(200=成功,500=失败)
    private String msg;  // 提示信息(success/fail)
    private T data;      // 业务数据

    // 成功返回方法
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMsg("success"); // 注意:小写,后续小程序要匹配
        result.setData(data);
        return result;
    }

    // 失败返回方法
    public static <T> Result<T> fail(String msg) {
        Result<T> result = new Result<>();
        result.setCode(500);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }
}
(4)编写Controller(接口入口)
java 复制代码
@RestController
@RequestMapping("/api/commands")
@RequiredArgsConstructor
public class CommandController {

    // 模拟业务服务(新手可先注释,后续替换为真实逻辑)
    // private final CommandProcessApplicationService commandProcessApplicationService;

    /**
     * 小程序调用的核心接口:处理语音指令
     */
    @PostMapping("/process")
    public Result<CommandProcessResponse> processCommand(@RequestBody CommandProcessRequest request) {
        // 1. 参数校验(新手必做,避免空指针)
        if (request.getVoiceText() == null || request.getUserId() == null) {
            return Result.fail("语音文本和用户ID不能为空");
        }

        // 2. 模拟后端处理(真实场景替换为解析指令、调用设备逻辑)
        CommandProcessResponse response = new CommandProcessResponse();
        response.setCommandId(UUID.randomUUID().toString().replace("-", "")); // 随机指令ID
        response.setDeviceCode("LT001"); // 客厅灯编码
        response.setDeviceName("客厅主灯");
        response.setActionType(CommandType.OPEN); // 模拟解析为"打开"
        response.setExecutedAt(LocalDateTime.now());
        response.setStatus(CommandStatus.SUCCESS); // 模拟执行成功

        // 3. 返回统一结果
        return Result.success(response);
    }
}
(5)跨域配置(新手最容易忘的坑!)

小程序调用后端会触发跨域拦截,必须添加跨域配置:

java 复制代码
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 开发阶段允许所有来源(上线后改为指定域名)
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*"); // 允许所有请求头
        config.addAllowedMethod("*"); // 允许所有请求方法(GET/POST等)
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config); // 所有接口生效
        return new CorsFilter(source);
    }
}
(6)配置枚举序列化(避免返回对象)

Spring Boot默认会把枚举序列化为{name:"OPEN", ordinal:0},小程序无法解析,需配置返回字符串:

java 复制代码
@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        return new Jackson2ObjectMapperBuilder()
                .modules(new SimpleModule()
                        // 所有枚举序列化时返回字符串(如OPEN/CLOSE)
                        .addSerializer(Enum.class, new ToStringSerializer()));
    }
}

3. 启动后端并测试

启动Spring Boot项目,默认端口8080(本文用8889,需在application.yml修改):

yaml 复制代码
server:
  port: 8889 # 后端端口

启动后,在电脑浏览器访问http://localhost:8889,若显示"Whitelabel Error Page"(无404/无法访问),说明后端启动成功。

三、第二步:小程序前端开发(迭代1:基础结构)

1. 创建小程序项目

打开微信开发者工具,新建"不使用云服务"的小程序项目,选择"JavaScript-基础模板"。

2. 核心配置与代码

(1)页面结构(pages/voiceControl/voiceControl.wxml)
xml 复制代码
<view class="container">
  <!-- 录音按钮 -->
  <button bindtap="startVoiceControl" wx:if="{{!isRecording}}">按住录音</button>
  <button bindtap="startVoiceControl" wx:else>停止录音</button>
  
  <!-- 指令输入框(备用,测试用) -->
  <input placeholder="手动输入指令(如打开客厅灯)" bindinput="inputVoiceText" value="{{voiceText}}"></input>
  <button bindtap="executeVoiceCommand">执行指令</button>
  
  <!-- 结果提示 -->
  <view class="result" wx:if="{{showResult}}">{{resultText}}</view>
  
  <!-- 设备状态展示 -->
  <view class="device">
    <text>客厅主灯:</text>
    <text wx:if="{{deviceList.livingRoomLight.switch}}">打开</text>
    <text wx:else>关闭</text>
  </view>
</view>
(2)核心逻辑(pages/voiceControl/voiceControl.js)
javascript 复制代码
Page({
  data: {
    voiceText: '',
    // 设备列表:补充code字段匹配后端设备编码(LT001/KT001)
    deviceList: {
      livingRoomLight: { 
        status: true, 
        switch: false, 
        code: 'LT001',    // 匹配后端客厅灯编码
        name: '客厅主灯'  
      },
      bedroomAir: { 
        status: true, 
        switch: true, 
        code: 'KT001',    // 匹配后端空调编码
        name: '卧室空调'  
      }
    },
    showResult: false,
    resultText: '',
    isRecording: false,
    recorderManager: null,
    tempFilePath: '',
    userId: 'wx_user_001', // 测试用固定用户ID
    baseApiUrl: 'http://192.168.1.3:8889', // 替换为你的电脑局域网IP
    commandId: ''
  },

  onLoad() {
    // 初始化录音管理器
    this.setData({
      recorderManager: wx.getRecorderManager()
    });
    this.initRecorder();
    // 启动时检测后端连接(方便排查)
    this.checkBackendConnection();
  },

  // 检测后端连接是否可用
  checkBackendConnection() {
    const that = this;
    wx.request({
      url: `${that.data.baseApiUrl}/api/commands/process`,
      method: 'POST',
      header: {
        'Content-Type': 'application/json; charset=utf-8'
      },
      data: { voiceText: '测试', userId: 'test', aiModelType: 'baidu' },
      timeout: 3000,
      success() {
        that.showResultToast('后端服务连接正常');
      },
      fail(err) {
        console.error('后端连接检测失败:', err);
        that.showResultToast('⚠️ 后端连接失败,请检查:\n1.后端是否启动\n2.IP/端口是否正确\n3.跨域配置是否生效');
      }
    });
  },

  // 初始化录音
  initRecorder() {
    const recorderManager = this.data.recorderManager;
    const that = this;

    // 录音开始回调
    recorderManager.onStart(() => {
      that.setData({ isRecording: true, resultText: '正在录音...' });
    });

    // 录音停止回调(获取临时文件路径)
    recorderManager.onStop((res) => {
      that.setData({
        isRecording: false,
        tempFilePath: res.tempFilePath
      });
      // 录音停止后,调用百度语音转文字
      that.voiceToText(res.tempFilePath);
    });

    // 录音错误回调
    recorderManager.onError((err) => {
      that.setData({ isRecording: false });
      that.showResultToast(`录音失败:${err.errMsg}`);
    });
  },

  // 开始/停止录音
  startVoiceControl() {
    const isRecording = this.data.isRecording;
    const recorderManager = this.data.recorderManager;

    if (isRecording) {
      recorderManager.stop(); // 停止录音
    } else {
      // 申请录音权限
      wx.authorize({
        scope: 'scope.record',
        success: () => {
          // 配置录音参数(百度语音识别要求wav格式)
          recorderManager.start({
            format: 'wav',
            sampleRate: 16000,
            numberOfChannels: 1,
            encodeBitRate: 64000,
            duration: 6000 // 最长录音6秒
          });
        },
        fail: () => {
          wx.showModal({
            title: '需要录音权限',
            content: '请前往设置开启麦克风权限',
            confirmText: '去设置',
            success: (res) => {
              if (res.confirm) wx.openSetting();
            }
          });
        }
      });
    }
  },

  // 手动输入指令
  inputVoiceText(e) {
    this.setData({
      voiceText: e.detail.value
    });
  },

  // 百度语音转文字
  voiceToText(tempFilePath) {
    const that = this;
    // 替换为你的百度API Key/Secret
    const apiKey = '你的百度API Key';
    const secretKey = '你的百度Secret Key';

    // 第一步:获取百度Access Token
    wx.request({
      url: 'https://aip.baidubce.com/oauth/2.0/token',
      method: 'POST',
      header: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      data: {
        grant_type: 'client_credentials',
        client_id: apiKey,
        client_secret: secretKey
      },
      success: (tokenRes) => {
        if (tokenRes.statusCode !== 200 || tokenRes.data.error) {
          const errMsg = tokenRes.data.error_description || 'Token获取失败';
          that.showResultToast(errMsg);
          return;
        }
        const accessToken = tokenRes.data.access_token;

        // 第二步:读取录音文件并转base64
        const fs = wx.getFileSystemManager();
        fs.readFile({
          filePath: tempFilePath,
          encoding: 'binary',
          success: (binaryRes) => {
            const fileBinary = binaryRes.data;
            const fileLen = fileBinary.length;
            // 二进制转base64(百度语音识别要求)
            const speechBase64 = wx.arrayBufferToBase64(new Uint8Array(fileBinary.split('').map(char => char.charCodeAt(0))));

            // 第三步:调用百度语音识别接口
            wx.request({
              url: 'https://vop.baidu.com/server_api',
              method: 'POST',
              header: {
                'Content-Type': 'application/json; charset=utf-8'
              },
              data: JSON.stringify({
                "format": "wav",
                "rate": 16000,
                "channel": 1,
                "cuid": "wx-miniprogram-123456789",
                "token": accessToken,
                "dev_pid": 1537, // 普通话识别
                "len": fileLen,
                "speech": speechBase64
              }),
              success: (transRes) => {
                console.log('百度语音识别结果:', transRes.data);
                if (transRes.data.err_no === 0 && transRes.data.result) {
                  const text = transRes.data.result[0];
                  that.setData({ voiceText: text });
                  that.showResultToast(`识别成功:${text}`);
                  // 识别成功后调用后端接口
                  that.callBackendCommandApi(text);
                } else {
                  const errMsg = transRes.data.err_msg || '识别失败';
                  that.showResultToast(`识别失败:${errMsg}(错误码:${transRes.data.err_no})`);
                }
              },
              fail: (err) => {
                console.error('语音识别请求失败:', err);
                that.showResultToast(`识别请求失败:${err.errMsg}`);
              }
            });
          },
          fail: (fileErr) => {
            console.error('读取录音文件失败:', fileErr);
            that.showResultToast(`读取录音失败:${fileErr.errMsg}`);
          }
        });
      },
      fail: (tokenErr) => {
        that.showResultToast(`获取Token失败:${tokenErr.errMsg}`);
      }
    });
  },

  // 调用后端接口处理指令
  callBackendCommandApi(voiceText) {
    const that = this;
    const { userId, baseApiUrl } = this.data;

    // 参数校验
    if (!userId) {
      that.showResultToast('用户未登录,请先登录');
      return;
    }
    if (!voiceText.trim()) {
      that.showResultToast('指令内容不能为空');
      return;
    }

    wx.showLoading({ title: '指令处理中...' });

    // 调用后端接口
    wx.request({
      url: `${baseApiUrl}/api/commands/process`,
      method: 'POST',
      header: {
        'Content-Type': 'application/json; charset=utf-8'
      },
      data: {
        voiceText: voiceText,
        userId: userId,
        aiModelType: 'baidu'
      },
      timeout: 5000,
      success: (res) => {
        wx.hideLoading();
        console.log('后端返回结果:', JSON.stringify(res.data));

        // 匹配后端统一返回格式(code=200且msg=success为成功)
        if (res.data && res.data.code === 200 && res.data.msg === 'success') {
          const commandResponse = res.data.data;
          that.setData({ commandId: commandResponse.commandId });
          // 同步设备状态
          that.syncDeviceStatusFromBackend(commandResponse);
        } else {
          const errMsg = res.data?.msg || '指令处理失败';
          that.showResultToast(`指令处理失败:${errMsg}`);
        }
      },
      fail: (err) => {
        wx.hideLoading();
        console.error('调用后端接口失败:', err);
        // 精准提示错误原因(新手友好)
        let errTips = '连接后端失败:';
        if (err.errMsg.includes('ERR_CONNECTION_REFUSED')) {
          errTips += '\n1. 后端服务未启动(8889端口)';
          errTips += '\n2. 确认IP是电脑局域网IP(192.168.1.3)';
          errTips += '\n3. 关闭电脑防火墙/检查跨域配置';
        } else if (err.errMsg.includes('timeout')) {
          errTips += '请求超时,请检查网络';
        } else {
          errTips += err.errMsg;
        }
        that.showResultToast(errTips);
      }
    });
  },

  // 同步后端返回的设备状态
  syncDeviceStatusFromBackend(commandResponse) {
    const that = this;
    const { deviceCode, actionType, status, deviceName } = commandResponse;

    // 匹配后端设备编码与本地设备(LT001 → livingRoomLight)
    let deviceKey = '';
    const deviceList = that.data.deviceList;
    for (const key in deviceList) {
      if (deviceList[key].code === deviceCode) {
        deviceKey = key;
        break;
      }
    }

    // 未匹配到设备
    if (!deviceKey) {
      that.showResultToast(`未识别设备:${deviceName || deviceCode}`);
      return;
    }

    // 解析动作(OPEN=打开,CLOSE=关闭)
    const isOpen = actionType === 'OPEN';
    const actionText = isOpen ? '打开' : '关闭';
    // 解析状态
    let statusText = '';
    switch (status) {
      case 'EXECUTING':
        statusText = '(执行中)';
        break;
      case 'SUCCESS':
        statusText = '(执行成功)';
        // 执行成功才更新本地设备状态
        const newDeviceList = { ...deviceList };
        newDeviceList[deviceKey].switch = isOpen;
        that.setData({ deviceList: newDeviceList });
        break;
      case 'FAIL':
        statusText = '(执行失败)';
        break;
      default:
        statusText = '';
    }

    // 提示结果
    that.showResultToast(`${deviceName || deviceList[deviceKey].name}${actionText}${statusText}`);
  },

  // 手动执行指令(测试用)
  executeVoiceCommand() {
    const { voiceText } = this.data;
    if (!voiceText.trim()) {
      this.showResultToast('请输入/说出控制指令');
      return;
    }
    this.callBackendCommandApi(voiceText);
  },

  // 结果提示框
  showResultToast(text) {
    this.setData({
      showResult: true,
      resultText: text
    });
    // 3秒后隐藏提示
    setTimeout(() => {
      this.setData({
        showResult: false
      });
    }, 3000);
  }
});
(3)关闭小程序域名校验(开发阶段)

新手容易被"合法域名"拦截,开发阶段临时关闭:

  1. 打开微信开发者工具 → 点击右上角「详情」;
  2. 在「本地设置」中,勾选「不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书」。

四、迭代2:联调踩坑与解决(核心!新手必看)

写完基础代码后,联调时会遇到各种问题,以下是我遇到的所有问题及解决方案,按"出现频率"排序:

坑1:小程序提示"连接后端失败:ERR_CONNECTION_REFUSED"

现象

小程序控制台打印:errMsg: "request:fail errcode:-102 cronet_error_code:-102 error_msg:net::ERR_CONNECTION_REFUSED"

原因
  • 后端服务未启动;
  • 后端端口被占用(8889);
  • 小程序baseApiUrl用了localhost(小程序模拟器/真机的localhost指向自身,而非电脑);
  • 电脑防火墙拦截了8889端口。
解决步骤
  1. 确认后端已启动:IDEA中启动Spring Boot项目,确保无报错;
  2. 检查端口占用:
    • Windows:CMD输入netstat -ano | findstr :8889,若有结果,关闭占用进程;
    • Mac/Linux:终端输入lsof -i :8889,kill占用进程;
  3. 替换baseApiUrl:将localhost改为电脑局域网IP (如192.168.1.3);
  4. 关闭电脑防火墙:
    • Windows:设置→更新和安全→Windows安全中心→防火墙和网络保护→关闭所有网络的防火墙;
    • Mac:系统设置→网络→防火墙→关闭。

坑2:小程序解析后端结果时,始终提示"指令处理失败"

现象

后端日志显示处理成功,但小程序提示"指令处理失败"。

原因
  • 小程序解析逻辑匹配success布尔值,但后端返回的是code=200+msg=success
  • msg大小写不匹配(后端返回success,小程序判断SUCCESS)。
解决步骤
  1. 修正小程序解析逻辑:将if (res.data.success)改为if (res.data && res.data.code === 200 && res.data.msg === 'success')
  2. 统一msg大小写:确保后端返回msg=success(小写),小程序也匹配小写。

坑3:真机测试提示"请求超时/无法连接"

现象

模拟器中能正常访问后端,但真机扫码后提示"连接后端失败:请求超时"。

原因
  • 真机和电脑未连同一Wi-Fi;
  • 电脑用了有线网络,小程序baseApiUrl填的是有线IP(真机连无线,无法访问);
  • 路由器开启了"设备隔离",阻止设备间通信。
解决步骤
  1. 确保真机和电脑连同一个无线Wi-Fi(电脑切换为无线,不要用有线);
  2. 重新获取电脑的无线局域网IP (而非有线IP),替换baseApiUrl
  3. 关闭电脑防火墙(尤其是"公用网络防火墙");
  4. 用手机浏览器测试:输入http://电脑无线IP:8889,能访问则网络正常。

坑4:设备状态无法同步(客厅灯开关不变)

现象

后端返回deviceCode=LT001,但小程序的客厅灯开关始终为"关闭"。

原因

小程序本地设备用livingRoomLight标识,后端用LT001,无映射关系。

解决步骤

在小程序deviceList中补充code字段,匹配后端设备编码:

javascript 复制代码
deviceList: {
  livingRoomLight: { 
    status: true, 
    switch: false, 
    code: 'LT001', // 匹配后端LT001
    name: '客厅主灯'  
  }
}

坑5:后端返回的枚举是对象(而非字符串)

现象

小程序控制台打印actionType: {name:"OPEN", ordinal:0},无法解析。

原因

Spring Boot默认将枚举序列化为对象,小程序需要字符串。

解决步骤

添加JacksonConfig配置类(见后端开发部分),让枚举序列化返回字符串。

坑6:百度语音转文字失败(err_no=500)

现象

小程序提示"识别失败:server error(错误码:500)"。

原因
  • API Key/Secret错误;
  • 录音格式不匹配(百度要求wav/16000采样率);
  • 录音文件转base64错误。
解决步骤
  1. 核对百度API Key/Secret(确保未填错);
  2. 确认录音参数:format: 'wav'sampleRate: 16000
  3. 检查base64转换逻辑(代码中已封装,直接复用即可)。

五、迭代3:最终验证

完成所有坑的修复后,验证流程:

  1. 启动后端服务(IDEA);

  2. 小程序模拟器中输入"打开客厅灯"→ 点击"执行指令";

  3. 控制台打印后端返回:

    json 复制代码
    {"code":200,"msg":"success","data":{"commandId":"xxx","deviceCode":"LT001","deviceName":"客厅主灯","actionType":"OPEN","executedAt":"2025-12-21T12:41:14.352356900","status":"SUCCESS"}}
  4. 小程序提示"客厅主灯打开(执行成功)",客厅灯开关变为"打开";

  5. 真机扫码测试:重复步骤2-4,功能正常。

六、新手建议(避坑总结)

  1. 网络优先:联调前先确保"电脑+真机同Wi-Fi""关闭防火墙""替换局域网IP",80%的问题是网络导致;
  2. 日志为王:遇到问题先看控制台日志(小程序→Network/Console,后端→IDEA控制台),日志会明确提示错误原因;
  3. 统一格式 :前后端约定好统一的返回格式(如code+msg+data),避免字段匹配混乱;
  4. 分步测试:先测试后端接口(Postman),再测试小程序调用后端,最后测试完整流程,分步定位问题;
  5. 测试完成后恢复配置:开启电脑防火墙、取消小程序域名校验勾选,避免安全风险。

七、完整代码仓库(可选)

为方便新手复用,我将完整的后端+前端代码整理到了Gitee(新手友好,带注释):

(注:"代码已在文中完整贴出,直接复制即可")

总结

小程序与后端交互的核心是"网络连通+数据格式匹配":网络问题靠"同Wi-Fi+局域网IP+关闭防火墙"解决,数据问题靠"统一返回格式+字段映射"解决。作为新手,不用怕踩坑------每一个坑都是对"网络通信""数据交互"的理解加深。跟着本文的迭代步骤,从基础代码到踩坑解决,你能完整掌握小程序与后端交互的核心逻辑。

相关推荐
NAGNIP8 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab10 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab10 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP13 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年13 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼14 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS14 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区15 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈15 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang15 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx