普通视图

发现新文章,点击刷新页面。
昨天 — 2026年1月20日首页

Three.js 实战:使用 DOM/CSS 打造高性能 3D 文字

作者 烛阴
2026年1月20日 22:21

Three.js 实战:使用 DOM/CSS 打造高性能 3D 文字

在 Three.js 中渲染文字有多种方案,本文介绍一种高性能且灵活的方案:CSS2DRenderer。它能将 DOM 元素无缝嵌入 3D 场景,同时保留 CSS 的全部能力。

为什么选择 DOM/CSS 方案?

方案 优点 缺点
TextGeometry 真正的 3D 几何体 性能开销大,需加载字体文件
CSS2DRenderer 清晰锐利、CSS 全特性、高性能 无法被 3D 物体遮挡

CSS2DRenderer 的核心优势:

  • 文字永远清晰:浏览器原生渲染,不受 3D 缩放影响
  • CSS 全特性:阴影、渐变、动画、backdrop-filter 磨砂玻璃效果
  • 性能优异:DOM 渲染与 WebGL 渲染分离,互不干扰

核心实现

1. 初始化双渲染器

import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';

// WebGL 渲染器(渲染 3D 场景)
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

// CSS2D 渲染器(渲染 DOM 标签)
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(innerWidth, innerHeight);
Object.assign(labelRenderer.domElement.style, {
  position: 'absolute',
  top: '0',
  pointerEvents: 'none' // 关键:让鼠标事件穿透
});
document.body.appendChild(labelRenderer.domElement);

关键点pointerEvents: 'none' 让鼠标事件穿透 DOM 层,否则无法拖拽 3D 场景。

2. 创建 CSS2D 标签

const createLabel = (text, position) => {
  const div = document.createElement('div');
  div.className = 'label';
  div.textContent = text;
  const label = new CSS2DObject(div);
  label.position.copy(position);
  return label;
};

// 将标签添加到 3D 物体上
earth.add(createLabel('Earth', new THREE.Vector3(0, 1.5, 0)));

标签添加到 earth 网格后,会自动跟随地球的旋转和位移。

3. 双渲染器同步渲染

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);      // 渲染 3D 场景
  labelRenderer.render(scene, camera); // 渲染 DOM 标签
}

4. CSS 样式

.label {
  color: #FFF;
  font-family: 'Helvetica Neue', sans-serif;
  font-weight: bold;
  padding: 5px 10px;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 4px;
  backdrop-filter: blur(4px); /* 磨砂玻璃效果 */
}

backdrop-filter: blur() 实现的磨砂玻璃效果,在纯 WebGL 中需要复杂的后处理才能实现,而 CSS 一行代码搞定。

完整代码

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';

// 场景、相机
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
camera.position.z = 5;

// WebGL 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
document.body.appendChild(renderer.domElement);

// CSS2D 渲染器
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(innerWidth, innerHeight);
Object.assign(labelRenderer.domElement.style, {
  position: 'absolute',
  top: '0',
  pointerEvents: 'none'
});
document.body.appendChild(labelRenderer.domElement);

// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 创建地球
const earth = new THREE.Mesh(
  new THREE.SphereGeometry(1, 32, 32),
  new THREE.MeshStandardMaterial({ color: 0x2233ff, roughness: 0.5 })
);
scene.add(earth);

// 生成地球纹理
const canvas = Object.assign(document.createElement('canvas'), { width: 512, height: 512 });
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#1e90ff';
ctx.fillRect(0, 0, 512, 512);
ctx.fillStyle = '#228b22';
for (let i = 0; i < 20; i++) {
  ctx.beginPath();
  ctx.arc(Math.random() * 512, Math.random() * 512, Math.random() * 50 + 20, 0, Math.PI * 2);
  ctx.fill();
}
earth.material.map = new THREE.CanvasTexture(canvas);

// 创建标签工厂函数
const createLabel = (text, position) => {
  const div = document.createElement('div');
  div.className = 'label';
  div.textContent = text;
  const label = new CSS2DObject(div);
  label.position.copy(position);
  return label;
};

earth.add(createLabel('Earth', new THREE.Vector3(0, 1.5, 0)));

// 光源
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 3, 5);
scene.add(dirLight);

// 动画循环
(function animate() {
  requestAnimationFrame(animate);
  earth.rotation.y += 0.005;
  controls.update();
  renderer.render(scene, camera);
  labelRenderer.render(scene, camera);
})();

// 响应窗口变化
addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
  labelRenderer.setSize(innerWidth, innerHeight);
});

📂 核心代码与完整示例:     my-three-app

总结

如果你喜欢本教程,记得点赞+收藏!关注我获取更多Three.js开发干货

2026 年,值得前端全栈尝试的 NestJS 技术栈组合 😍😍😍

作者 Moment
2026年1月20日 22:08

我正在开发 DocFlow,它是一个完整的 AI 全栈协同文档平台。该项目融合了多个技术栈,包括基于 Tiptap 的富文本编辑器、NestJs 后端服务、AI 集成功能和实时协作。在开发过程中,我积累了丰富的实战经验,涵盖了 Tiptap 的深度定制、性能优化和协作功能的实现等核心难点。

如果你对 AI 全栈开发、Tiptap 富文本编辑器定制或 DocFlow 项目的完整技术方案感兴趣,欢迎加我微信 yunmz777 进行私聊咨询,获取详细的技术分享和最佳实践。

对很多想从前端转向全栈的同学来说,NestJS 是一个非常友好的选择:语法风格接近前端熟悉的 TypeScript,又借鉴了后端常见的模块化与依赖注入模式,可以在保留前端开发舒适度的同时,比较轻松地搭起一套“像样的后端服务”。

如果目标不仅是写几个简单接口,而是要扛起鉴权、实时协同、AI 能力,还要自己搭建和维护数据库、缓存、搜索、对象存储、监控这些基础设施,那么就需要一套偏“自托管、自运维”的 NestJS 技术栈组合。

这里推荐的技术栈选择标准主要有三点:

  1. NestJS 生态高度契合,有成熟的官方或社区集成;
  2. 能够支撑中大型文档、知识类应用的性能和复杂度,比如协同编辑、全文检索、RAG、任务队列等;
  3. 个人或小团队也能在合理成本内自行部署和维护,比如使用 Prisma + MySqlRedisElasticsearchMinIO 这类开源组件。

接下来就按照从框架、运行时到数据层、搜索、队列、AI 的顺序,分享一套适合前端转全栈使用的 NestJS 核心技术栈,代码也尽量贴近实战,方便直接改造复用。

NestJSTypeScript

NestJS 是整个后端的“框架壳子”,负责模块划分、依赖注入、装饰器等基础能力;TypeScript 则是地基,把很多原本要靠经验避免的错误提前到编译期发现,例如 Controller 入参、Service 返回值、配置对象等。

实际项目中,一般会开启严格模式,再结合全局的 ValidationPipeclass-transformer,把请求里的原始 JSON 自动转换成带类型的 DTO 实例,从而减少 any 的使用、避免脏数据流入业务层。

基本使用案例:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
    }),
  );
  await app.listen(3000);
}

bootstrap();

Fastify@nestjs/platform-fastify

Fastify 是一个高性能 HTTP 引擎,相比 Express 更轻量、更适合高并发场景;@nestjs/platform-fastify 负责把 Fastify 接进 Nest,让你在业务层依然只写标准的 Controller、Guard、Interceptor 等。

搭配 @fastify/helmet@fastify/rate-limit@fastify/cookie@fastify/secure-session@fastify/multipart@fastify/static,可以在统一框架里完成安全头、限流、会话、上传和静态资源托管。

基本使用案例:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000, '0.0.0.0');
}

bootstrap();

@nestjs/config

@nestjs/config 是 Nest 官方的配置模块,用来统一管理多环境配置。思路很简单:所有数据库地址、第三方秘钥、开关配置等,都通过 ConfigService 读取,而不是在代码里到处散落 process.env

推荐的做法是为不同领域写独立的配置工厂,然后在对应模块中注入使用。

基本使用案例:

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
  ],
})
export class AppModule {}
// some.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class SomeService {
  constructor(private readonly configService: ConfigService) {}

  getDbUrl() {
    return this.configService.get<string>('DATABASE_URL');
  }
}

PassportJWT

@nestjs/passport@nestjs/jwt 提供了一整套认证基础设施:Passport 统一管理各种认证策略(本地、JWT、OAuth 等),JWT 提供无状态令牌,让前后端分离、多端访问更容易管理。

常见流程是:通过本地策略验证用户名密码,登录成功后签发 JWT,后续请求通过 AuthGuard('jwt') 验证;接入第三方登录(如 GitHub)时,仅需增加新的 Passport 策略。

基本使用案例:

// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      secret: 'secret', // 实际项目请从 ConfigService 读取
    }),
  ],
  providers: [JwtStrategy],
  exports: [PassportModule, JwtModule],
})
export class AuthModule {}
// some.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('profile')
export class ProfileController {
  @UseGuards(AuthGuard('jwt'))
  @Get()
  getProfile() {
    return { ok: true };
  }
}

WebSocketSocket.IO

当系统需要即时通知、在线状态、协同编辑时,可以使用 @nestjs/websockets 搭配 @nestjs/platform-socket.iosocket.ioGateway 充当“长连接入口”,负责管理连接、房间和事件。

更高级的协同场景中,还可以引入 hocuspocusyjsy-prosemirror 来负责文档协同算法,Nest 只需要负责连接管理和权限校验。

基本使用案例:

// chat.gateway.ts
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
} from '@nestjs/websockets';
import { Server } from 'socket.io';

@WebSocketGateway()
export class ChatGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('message')
  handleMessage(@MessageBody() data: string) {
    this.server.emit('message', data);
  }
}

Prisma

Prisma 通过独立的 schema.prisma 文件定义模型,并生成强类型的 PrismaClient,非常适合在 Nest 的 Service 层中使用。它把数据库迁移、数据建模、类型安全绑在一起,能明显降低 SQL 错误和字段拼写错误的概率。

在 Service 中,直接注入封装好的 PrismaService,就可以用类型安全的方式进行增删改查。

基本使用案例:

// user.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/common/prisma/prisma.service';

@Injectable()
export class UserService {
  constructor(private readonly prisma: PrismaService) {}

  findAll() {
    return this.prisma.user.findMany();
  }
}

Redisioredis

Redis 是常见的缓存与中间层,而 ioredis 是稳定好用的 Node 客户端组合。它通常用于三个方向:缓存(加速读取)、分布式协调(锁、限流、防重复)、短期数据存储(会话、任务状态等)。

在 Nest 中一般会封装一个 RedisService,对外暴露 getsetincr 等方法,避免直接在业务里使用底层客户端。

基本使用案例:

// redis.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Redis } from 'ioredis';

@Injectable()
export class RedisService implements OnModuleInit {
  private client: Redis;

  onModuleInit() {
    this.client = new Redis('redis://localhost:6379');
  }

  async get(key: string) {
    return this.client.get(key);
  }

  async set(key: string, value: string, ttlSeconds?: number) {
    if (ttlSeconds) {
      await this.client.set(key, value, 'EX', ttlSeconds);
    } else {
      await this.client.set(key, value);
    }
  }
}

BullMQ

BullMQ 是基于 Redis 的任务队列,@nestjs/bullmq 让你可以用装饰器方式定义队列和消费者。它适合承载各种耗时任务,例如大文件解析、批量导入导出、调用外部 AI 接口等。

这样可以把重任务从 HTTP 请求中剥离,避免接口超时,用户只需拿到一个“任务已受理”的 ID。

基本使用案例:

消费者(处理任务):

// task.processor.ts
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';

@Processor('tasks')
export class TaskProcessor extends WorkerHost {
  async process(job: Job) {
    // 这里处理耗时任务
    console.log('processing job', job.id, job.data);
  }
}

生产者(投递任务):

// task.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';

@Injectable()
export class TaskService {
  constructor(@InjectQueue('tasks') private readonly queue: Queue) {}

  async createTask(payload: any) {
    const job = await this.queue.add('process', payload);
    return { jobId: job.id };
  }
}

Elasticsearch

Elasticsearch 在文档和知识类系统中通常用作结构化搜索与日志索引引擎。通过 @elastic/elasticsearch 客户端可以在 Nest 的 Service 里封装搜索接口,对外统一暴露“搜索文档”“搜索日志”等能力。

相对数据库原生查询,Elasticsearch 更擅长复杂查询、聚合统计、模糊搜索与搜索结果排序,是提升搜索体验的关键组件。

基本使用案例:

封装搜索 Service:

// search.service.ts
import { Injectable } from '@nestjs/common';
import { Client } from '@elastic/elasticsearch';

@Injectable()
export class SearchService {
  private client = new Client({ node: 'http://localhost:9200' });

  async searchDocs(keyword: string) {
    const res = await this.client.search({
      index: 'documents',
      query: {
        multi_match: {
          query: keyword,
          fields: ['title', 'content'],
        },
      },
    });
    return res.hits.hits;
  }
}

对象存储(MinIO

当系统有大量文件(如 PDFWord、图片、音频)时,本地磁盘很快就会吃不消,这时可以用自建的 MinIO 集群来做对象存储。它负责长期保存大文件,后端只需要关心对象名和访问地址,不必再直接管理磁盘。

在 Nest 中通常会封装一个存储 Service,对上层暴露“上传文件”“生成下载地址”等方法;同时配合 imagekitsharpexiftool-vendoredpdf-parsemammoth 等,对文件做压缩、预览、元信息与文本提取等处理。

基本使用案例:

// storage.service.ts
import { Injectable } from '@nestjs/common';
import { Client } from 'minio';

@Injectable()
export class StorageService {
  private client = new Client({
    endPoint: 'localhost',
    port: 9000,
    useSSL: false,
    accessKey: 'minio',
    secretKey: 'minio123',
  });

  async upload(bucket: string, objectName: string, buffer: Buffer) {
    await this.client.putObject(bucket, objectName, buffer);
    return { bucket, objectName };
  }
}

@nestjs/swagger

@nestjs/swagger 负责从 Controller 和 DTO 的装饰器中生成 OpenAPI 文档,让接口定义和代码实现保持同步,不再需要单独维护一份容易过期的接口文档。

在前后端分离项目中,Swagger 文档可以同时服务前端、测试与产品:前端对齐请求和响应结构,测试做接口验证,产品了解后端已有能力。

基本使用案例:

// main.ts 片段
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('API 文档')
    .setDescription('服务接口说明')
    .setVersion('1.0')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('docs', app, document);

  await app.listen(3000);
}

class-validatorclass-transformer

class-validator 通过在 DTO 类属性上添加装饰器(如 IsStringIsIntIsOptional 等)定义字段的合法规则,class-transformer 负责把原始请求 JSON 转换成 DTO 实例。配合全局 ValidationPipe,可以保证进入 Controller 的数据已经过校验和转换。

这一体系大大减少了手写 if 校验的重复劳动,同时确保错误请求在统一入口被拦截并抛出合适的 HTTP 异常。

基本使用案例:

// create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  password: string;
}
// user.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() dto: CreateUserDto) {
    return dto;
  }
}

PrometheusTerminus

@willsoto/nestjs-prometheus 搭配 prom-client 可以方便地暴露各种指标端点,例如 HTTP 延迟、错误率、队列堆积情况等;@nestjs/terminus 则专注于健康检查,通过多种 HealthIndicator 检查数据库、RedisElasticsearchQdrant 等依赖服务是否可用。

在生产环境下,这两者为“可观测性”打基础,使运维和开发可以快速感知和定位问题。

基本使用案例:

// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import {
  HealthCheck,
  HealthCheckService,
  TypeOrmHealthIndicator,
} from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),
    ]);
  }
}

OpenAILangChain

openai 提供与大模型交互的基础接口,而 langchain@langchain/core@langchain/community@langchain/openai@langchain/textsplitters 则把模型调用、提示模板、工具调用、长文档切片等复杂逻辑抽象成可组合的模块。对于文档工作流类项目,这一层就是从“普通文档系统”升级为“智能文档系统”的关键。

在 Nest 中,通常会拆出一个 AI 模块,把“向量检索 + RAG + 模型调用”封装在 Service 里,再通过少量 HTTP 接口暴露出问答、总结、润色等能力。

基本使用案例:

// ai.service.ts
import { Injectable } from '@nestjs/common';
import OpenAI from 'openai';

@Injectable()
export class AiService {
  private client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

  async chat(prompt: string) {
    const res = await this.client.chat.completions.create({
      model: 'gpt-4.1-mini',
      messages: [{ role: 'user', content: prompt }],
    });
    return res.choices[0]?.message?.content ?? '';
  }
}

总结

如果只是想把接口“跑起来”,也许只要 NestJS 加上一两个库就够用;但一旦要扛起鉴权、实时协同、AI、文件与搜索这些完整能力,就很容易演变成这篇文章前面列出来的这一整套技术栈:NestJS + TypeScript 打基础,Fastify 提供高性能 HTTP 入口,Prisma + MySql 管数据,Redis + BullMQ 管缓存和队列,Elasticsearch 管搜索,MinIO 管文件,再加上 @nestjs/swaggerclass-validatorPrometheusTerminusOpenAILangChain 这些周边,让项目从“能跑”变成“好用、可维护、可扩展”。

对前端转全栈来说,这套组合有两个现实的好处:一是语法和思路都围绕 TypeScript 展开,上手成本可控;二是每一个环节都有成熟的 Nest 集成(模块、装饰器、示例代码),可以按需逐步引入,而不必一口气吃下全部。你可以先用 NestJS + Prisma + Redis 起一个简单项目,再慢慢把队列、搜索、对象存储和 AI 能力补上,最终搭出一套适合自己长期维护的后端脚手架。

让 AI 学会"问一嘴":assistant-ui 前端工具的人机交互实践

作者 红尘散仙
2026年1月20日 22:02

让 AI 学会"问一嘴":assistant-ui 前端工具的人机交互实践

🤖 当 AI 助手要帮用户执行敏感操作时,总不能闷头就干吧?得先问问人家确不确认啊!

背景:一个"莽撞"的 AI 助手

我在做一个工单管理系统,里面有个 AI 助手功能。用户可以说"帮我创建一个工单,数量 50",AI 就会调用工具创建记录。

听起来很美好,但问题来了:

AI 太"自信"了。

它收到指令就直接执行,万一用户说错了呢?万一 AI 理解错了呢?50 变成 500,那可就麻烦了。

所以我需要一个机制:AI 调用工具前,先让用户确认一下。

用户: 帮我创建一个工单,数量 50

AI: 好的,我来帮你创建记录
    ┌─────────────────────────┐
    │ 确认创建记录             │
    │ 名称: A 款               │
    │ 数量: 50                 │
    │ [确认]  [取消]           │
    └─────────────────────────┘

用户: *点击确认*

AI: ✅ 已创建记录:Ax 50

这就是所谓的 Human-in-the-Loop(人机协作)模式。

技术选型

  • 前端:React + @assistant-ui/react
  • 后端:Rust + Axum + Rig(AI 框架)
  • AI:OpenAI GPT-4

assistant-ui 是一个专门为 AI 聊天界面设计的 React 组件库,它有个很棒的特性:前端工具(Frontend Tools)

架构概览

flowchart TB
    subgraph Frontend["前端 (React)"]
        Runtime[useChatRuntime]
        Tool[CreateRecordTool<br/>前端工具]
        UI[确认 UI]
        Runtime --> Tool --> UI
        UI -->|resume| Tool
        Tool -->|sendAutomatically| Runtime
    end

    subgraph Backend["后端 (Rust)"]
        Handler[Chat Handler]
        FTool[FrontendTool<br/>工具定义转发]
        AI[AI Model]
        Handler --> FTool --> AI
    end

    Runtime <-->|HTTP| Handler

核心概念:前端工具 vs 后端工具

前端工具 后端工具
执行位置 浏览器 服务器
能否与用户交互 ✅ 可以 ❌ 不行
适用场景 需要确认的操作 查询、计算

前端工具的精髓在于:工具定义发给 AI,但执行在前端

AI 知道有这个工具可以用,当它决定调用时,前端拦截执行,可以弹个确认框、让用户填个表单,用户操作完再把结果告诉 AI。

实现步骤

1. 定义工具参数

// schema.ts
import { z } from "zod";

export const CreateRecordSchema = z.object({
  name: z.string().describe("名称"),
  amount: z.number().describe("数量"),
});

2. 创建前端工具

这是最关键的部分,使用 makeAssistantTool

import { makeAssistantTool } from "@assistant-ui/react";

export const CreateRecordTool = makeAssistantTool({
  toolName: "create-record",
  type: "frontend",  // 🔑 关键:标记为前端工具
  parameters: CreateRecordSchema,
  description: "创建记录",

  // execute 在前端执行
  execute: async (args, ctx) => {
    const { human } = ctx;

    // human() 会暂停执行,等待用户确认
    const response = await human("请确认创建记录");

    if (response === "confirmed") {
      // 调用实际 API
      await api.createRecord(args);
      return { success: true };
    }
    return { success: false };
  },

  // render 渲染确认 UI
  render: ({ args, status, result, resume }) => {
    // 等待确认状态
    if (status.type === "requires-action") {
      return (
        <div className="rounded-lg border p-4">
          <div>确认创建记录</div>
          <div>名称: {args.name} | 数量: {args.amount}</div>
          <button onClick={() => resume("confirmed")}>确认</button>
          <button onClick={() => resume("cancelled")}>取消</button>
        </div>
      );
    }

    // 完成状态
    return <div>{result?.success ? "✅" : "❌"} 已处理</div>;
  },
});

核心 API 解释:

  • human(message): 暂停执行,等待用户操作
  • resume(value): 用户操作后恢复执行,value 会作为 human() 的返回值

3. 注册工具

function ChatPage() {
  const processedToolCalls = useRef(new Set<string>());

  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({
      api: "/api/chat",
    }),
    // 工具完成后自动发送结果给后端
    sendAutomaticallyWhen: (options) => {
      if (!lastAssistantMessageIsCompleteWithToolCalls(options)) {
        return false;
      }
      const lastMsg = options.messages.at(-1);
      const toolPart = lastMsg?.parts.find(
        (p) => p.type === "tool-create-record" && p.state === "output-available"
      ) as { toolCallId: string } | undefined;

      if (toolPart && !processedToolCalls.current.has(toolPart.toolCallId)) {
        processedToolCalls.current.add(toolPart.toolCallId);
        return true;
      }
      return false;
    },
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Thread />
      <CreateRecordTool />
    </AssistantRuntimeProvider>
  );
}

4. 后端接收工具定义

前端会把工具定义发给后端,后端需要转发给 AI:

// Rust 后端
pub struct FrontendTool {
    pub name: String,
    pub description: String,
    pub parameters: Value,
}

impl ToolDyn for FrontendTool {
    fn name(&self) -> String { self.name.clone() }

    fn definition(&self, _: String) -> ToolDefinition {
        ToolDefinition {
            name: self.name.clone(),
            description: self.description.clone(),
            parameters: self.parameters.clone(),
        }
    }

    fn call(&self, _: String) -> Result<String, ToolError> {
        // 前端工具不在后端执行!
        Err(ToolError::ToolCallError("Frontend tool".into()))
    }
}

完整流程

sequenceDiagram
    participant 用户
    participant 前端
    participant 后端
    participant AI

    用户->>前端: "创建工单,数量 50"
    前端->>后端: 发送消息 + 工具定义
    后端->>AI: 转发
    AI->>后端: 调用 create-record
    后端-->>前端: 返回工具调用
    前端->>前端: execute() → human()
    前端->>用户: 显示确认框
    用户->>前端: 点击确认
    前端->>前端: resume("confirmed")
    前端->>后端: 发送工具结果
    后端->>AI: 转发结果
    AI->>后端: "好的,已记录"
    后端-->>前端: 返回回复
    前端->>用户: 显示完成

踩坑记录

坑 1:Tool call is not waiting for human input

原因:没在 execute 里调用 human(),或者在错误状态下调用了 resume()

解决:确保 execute 里有 await human(),且只在 status.type === "requires-action" 时调用 resume()

坑 2:工具完成后消息无限发送

原因sendAutomaticallyWhen 对同一个工具调用重复返回 true

解决:用 useRef 记录已处理的 toolCallId

const processedToolCalls = useRef(new Set<string>());

sendAutomaticallyWhen: (options) => {
  // ... 找到完成的工具调用
  if (!processedToolCalls.current.has(toolCallId)) {
    processedToolCalls.current.add(toolCallId);
    return true;
  }
  return false;
}

坑 3:用错了 makeAssistantToolUI

makeAssistantToolUI 只渲染 UI,不会把工具定义发给后端。如果需要 AI 能调用,必须用 makeAssistantTool

总结

通过 assistant-ui 的前端工具机制,我们实现了:

  1. AI 能力不打折:AI 仍然可以决定何时调用工具
  2. 用户有控制权:敏感操作必须经过用户确认
  3. 体验很自然:确认框嵌入在对话流中,不突兀

这套方案已经在生产环境稳定运行,用户再也不用担心 AI "手滑"了 😄


技术栈:React + Rust + Tauri + assistant-ui

如果你也在做 AI 应用,需要人机协作的场景,希望这篇文章对你有帮助!

欢迎评论区交流 👇

解绑宁王,天赐底气何在?

2026年1月20日 21:44

作者|Eastland

头图|视觉中国


在锂电产业链的博弈棋局中,没有永恒的绑定,只有动态的平衡。

 

一年半前,天赐材料(SZ:002709)通过全资子公司(宁德凯欣)与宁德时代签订《物料供货协议》。协议约定:在本协议有效期内(自生效至2025年12月31日止),凯欣向宁德时代供应固体六氟磷酸锂使用量为5.86万吨的对应数量电解液产品。


粗略推算,协议涉及的电解液可供生产410GWh~470GWh锂电池产品,约为宁德时代一年半产量的60%(假设2024年H2+2025年,宁德时代出货量750GWh)。


一个事实是,六氟磷酸锂电解液占动力电池成本的40%~50%,天赐材料无疑是宁德时代的核心供应商之一。


按理说,宁德时代、天赐材料早该签订长期供货协议,宁德锁定关键物料、天赐获得业绩保底。但双方2024年6月签的协议即将到期,新协议却迟迟没有动静,原因是天赐的选择变多了。

 

解绑“宁王”,底气何在?


1)与宁德时代紧密合作

 

2015年,天赐材料在宁德市建立全资子公司宁德凯欣,为宁德时代提供“贴身服务”:


2018年,来自宁德时代的收入突破10亿元、占天赐锂电池材料销售收入的35.7%;

……

……

2021年,来自宁德时代的收入达56亿元、占天赐锂电池材料销售收入的57.5%;

2022年,来自宁德时代的收入达122亿元、占天赐锂电池材料销售收入的58.4%;

2023年,来自宁德时代的收入回落至81亿元、占天赐锂电池材料销售收入的57.6%;

2024年,来自宁德时代的收入进一步降至50亿元、占天赐锂电池材料销售收入的45.6%;


  

两家的合作关系可概括为:天赐锂电池材料营收五成来自宁德时代,宁德时代电解液六成来自天赐。

 

2)降低对彼此的依赖

 

时至2025年末,天赐材料与宁德时代的“供货协议”即将到期,却迟迟不见续签消息。

 

另一方面,天赐材料陆续披露重大供货合同。


  • 第一轮:


2025年7月15日,与楚能新能源签订“合作协议”——2030年前供应不少于55万吨电解液产品;


签约时点耐人寻味——与宁德时代签订供货协议一周年之际。


  • 第二轮:


2025年9月22日,与瑞蒲兰钧签订“合作协议”——2030年前供给不小于80万吨电解液产品;


这是对宁德时代进一步施加压力——我的货不愁卖。


  • 第三轮:


2025年11月6日,与中航新能签订“保供框架协议”——2026~2028年供给72.5万吨电解液产品;

同日,与国轩高科签订“采购合同”——2026~2028年供给87万吨电解液产品;


这轮签订的客户,“卡位”大于前两轮,而且放在同一天。显示出天赐的决绝。


三轮签约(四份合同)涉及的总供货量达294.5万吨,2026年~2028年平均供货超过80万吨/年,而2024年,天赐材料电解液出货量仅50万吨。


预计2026年,天赐材料电解液产能达到100万吨(2027年120万吨)。


通常供应商不会把100%产能都拿去签长单。主要有两个原因:一是现货价有可能高于长协价,谁不想多赚点;二是为开发新客户、维护老客户,留些机动产能。


看到宁德时代“油盐不进”,天赐材料把80%产能“预售”出去,剩下20%不太可能与宁德签长单了(不排除现货交易)。


其实双方都在赌,赌的是“不依赖你,我过得更好”。


目前看来,天赐的“钱途”更明朗——未来三年,产量是2022年(33万吨)的三倍,销路不愁(80%产能已落实下家)、价格肯定比供宁德时代高。


宁德时代是行业巨头,老板是成熟的一流企业家,“不惯着”天赐材料,肯定另有打算。

 

最新消息:据韩国《ZDNET Korea》报道,12月24日Enchem与宁德时代签署合同。前者将在2026~2030年供给电解液35万吨,总价款约合72.3亿人民币。


年均7万吨的供应量超过Enchem产能(约5万吨/年),却与宁德时代需求(2026年约60~80万吨)相差甚远。


在推动供应电解液来源多元化的同时,宁德时代有可能自建产能降低对外部的依赖。


利润大起大落,基本面如何?

 

天赐“锂离子电池材料”主要产品包括:电解液、正极材料(磷酸铁锂)。除此之外,天赐材料还涉足资源/循环业务(锂辉石资源、磷酸铁锂电池回收等)。

 

天赐仅披露锂电池材料总出货量,没有完整明细。例如:


2023年出货61.9万吨,其中电解液约40万吨、磷酸铁锂约5万吨,其他不详;

2024年出货79.5万吨,其中电解液约50万吨、磷酸铁锂9.5万吨,其他不详;

 

锂电材料收入除以总出货量,得到综合平均价格,这个数字只能说明电池材料价格走势,无法算出具体产品(如电解液)单价:


2020年,每吨售价3万元;

2021年,每吨售价4.3万元;

2022年,每吨售价4.8万元;

2023年,每吨售价2.3万元;

2024年,每吨售价1.38万元;

 


天赐电池材料出货量稳步上升,但毛利润却冲高回落:

 

2022年,综合单价4.8万元、毛利润80亿、利润率38.6%,三个数据同时创历史新高;

 

2024年,出货量较2022年高84.5%,但出货价格仅为2022年的28.6%;毛利润率降至17.5%,毛利润金额19亿,为2022年的23.9%;



整体来看,天赐盈利能力经受住了考验:


  • 高峰


2022年,天赐毛利润达88亿、毛利润率39.6%;总费用仅15.5亿、总费用率不到7%;


  • 低谷

 

2024年,天赐毛利润仅24亿、毛利润率18.9%;总费用被压缩到13.9亿、总费用率增至11.1%;



  • 扣非净利润 


2022年,天赐扣非净利润达55亿,2024年仅为3.8亿,利润率3%。


2025年H1,天赐扣非净利润提高到4亿,利润率3.7%;



对多数制造业企业而言,20%的毛利润率不算低。2025年前三季度,特斯拉整车销售毛利润率仅为13.6%。


至于天赐的费用率,过往几年的数据证明刚性很强,几乎没有压缩余地。


回过头看,2020~2024年是一个比较完整的周期。锂矿(锂辉石)、碳酸锂、六氟磷酸锂(电解液)、磷酸铁锂(正极材料)……都经历了一轮冲高回落。


考察周期股,最重要的是看它在低谷的表现。好比基金经理,在股灾时能不陪或少赔,比牛市里赚得多更重要。正所谓“善战者不败,善败者终胜”。


在这方面,天赐材料比其他“周期股”优秀,如天齐锂业、TCL中环。


2025年是新周期的开始,动力电池、储能领域的需求叠加,产业链各环节吃紧。


如果把锂电产业链比喻成一条河,天赐材料“把守”的正是河道最窄的地方。


天赐成了“讨厌的”周期股?


有个理念至今仍有市场——周期性行业没有好生意,以茅台为代表的消费股价值更高。

 

天赐材料是该理念的拥趸,将主攻方向定为“个人护理材料”。2010年相关收入占营收的61.6%;锂电池材料仅占营收的21.9%。

 

但新能源毕竟是朝阳产业,尽管供需动态平衡过程充满间歇式的产能不足和产能过剩,行业规模却保持高速增长。锂电材料收入在天赐总营收中的占比“无可救药”地增长:


2016年,电池材料收入占比突进到60%以上;

……

……

……

2021年,电池材料收入占比达到90%一线:

2021年,收入97亿、占营收的88%;

2022年,收入208亿、占营收的93%;

2023年,收入141亿、占营收的92%;

2024年,收入110亿、占营收的88%;

2025年H1,收入63亿、占营收的90%;


 

从发展历程看,成为“周期股”并非天赐的初衷,但“抗争”了十几年,终究还是成了“自己讨厌的样子”。


回顾天赐材料的发展轨迹,其在行业调整期主动拓宽客户矩阵,降低对单一客户的依赖,体现出对行业趋势的清醒认知——在商业战场上,没有永远的朋友。

 

在逐步搭建起“锂辉石—碳酸锂—六氟磷酸锂—电解液”全产业链框架后,天赐材料的韧性将成为穿越周期的关键。


*以上分析仅供参考,不构成任何投资建议!

下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

晶核能源融资数千万元,专攻固态电池“固固界面”难题丨36氪首发

2026年1月20日 21:07

36氪获悉,诞生于“星空计划”的全固态电池企业晶核能源,近日完成数千万元天使轮融资,本轮融资由天空工场创投基金领投。

晶核能源是一家全固态电池电芯与系统解决方案供应商,其创始人兼CEO李延涛曾是中航锂电及吉利动力电池创始骨干成员。李延涛告诉36氪,这轮融资所获得资金,将用于全固态电池技术商业化落地、核心团队人才引进,以及全球商业化版图快速拓展。

在新能源汽车领域,全固态电池因其高能量密度与安全冗余,长期被视为行业的圣杯。但宥于工艺复杂、成本高企,且核心技术长期未取得突破,全固态电池至今未能实现大规模量产。

聚焦全固态电池行业痛点,晶核能源提出了自己的解决方案。全固态电池量产的最大掣肘,是固固界面阻抗问题。对此,李延涛表示,晶核能源研发了正负极包覆技术,“在制备正负极材料时,在外面做一层硫化物包覆,这样一来,相同材料间的致密性会更好,其电导率较目前的全固态电池能够提升一个量级”。晶核能源计划,“在2027年底,将全固态电池的充放电倍率提升至2C”。

固态电池

除此之外,晶核能源的全固态电池正极,所采用的材料是富锂锰基,而非高镍原材料,据李延涛介绍,富锂锰基作为正极材料,“能够将电芯能量密度提升至800Wh/kg”。

为了让旗下产品被更好地应用,晶核能源还同步开发了AI BMS电池管理系统、CTP4.0架构设计这一PACK技术。

36氪了解到,晶核能源研发AI BMS电池管理系统的目的,是为了实现更高精度的电芯状态监测,提升电池健康度识别能力。而CTP4.0架构的PACK设计,则能够“提升电池包空间利用率、从而降低生产成本”。

李延涛解释,在CTP4.0设计下,电池包内没有纵横梁,从而同样体积的电池包内,可以堆更多电芯。

同时,晶核能源设计了龙骨架构,“每个电芯之间也会保持两毫米的间隙,以应对充放电时的呼吸效应。电芯中间又加了六合一功能的材料,这种材料可呼吸、可压缩,从而能保障电池包的结构强度、起到隔热作用,并提升电池包内空间利用率”。

在技术攻坚之余,晶核能源也强调短期的商业化落地。目前,企业已和3家头部新能源商用车客户、2家能源厂商达成合作意向,首批工程样件预计于2026年第四季度完成装车测试。产品的应用场景,覆盖智能汽车、电力储能与eVTOL三大领域。

晶核能源创始人

按照李延涛的规划,晶核能源要在2027年,实现全固态电池的规模化产能突破。

本次产业投资方董事总经理俞佳彬认为:“双碳目标与能源转型驱动下,固态电池赛道已站在万亿级市场爆发前夜,技术壁垒与商业化能力是突围关键。”他强调,晶核能源团队深耕固态电池领域多年,核心技术实现颠覆性突破,且产业化潜力明确,天空工场创投基金将持续在资本、产业资源等方面赋能,助力其成长为全球全固态电池领军企业。

爱奇艺国际版2025年播放量同比增长114.5%

2026年1月20日 20:56
36氪获悉,1月20日,《爱奇艺国际版2025全球内容热播榜》正式发布。其中显示,2025年爱奇艺国际版内容播放量持续强劲增长,同比增长114.5%,华语内容成为驱动这一增长的核心动力。

海特生物:筹划发行H股股票并在香港联交所上市

2026年1月20日 20:50
36氪获悉,海特生物公告,公司拟发行境外上市外资股(H股)股票并申请在香港联合交易所有限公司主板挂牌上市,以进一步提高公司综合竞争力,提升公司国际品牌形象,同时更好地利用国际资本市场,多元化融资渠道。该事项尚需提交公司股东会审议,并需取得相关政府机构、监管机构备案、批准和/或核准。

热门中概股美股盘前普跌,拼多多跌超4%

2026年1月20日 20:48
36氪获悉,热门中概股美股盘前普跌,截至发稿,拼多多跌超4%,B站跌超3%,小鹏汽车跌超2%,阿里巴巴、蔚来、理想汽车跌超1%,京东跌0.76%,爱奇艺跌0.52%,网易跌0.22%,百度跌0.09%。

北大医药:股东北大医疗拟减持不超3%股份

2026年1月20日 20:41
36氪获悉,北大医药公告,持股5%以上股东北大医疗管理有限责任公司因自身资金需求,计划自公告披露之日起15个交易日后的3个月内(即2026年2月11日至2026年5月10日),通过集中竞价和大宗交易方式减持公司股份不超过178.80万股(占公司总股本比例为3%)。其中,集中竞价交易在任意连续90个自然日内不超过公司总股本的1%,大宗交易不超过2%。

锋龙股份:如未来公司股票价格进一步异常上涨,公司可能再次申请停牌核查

2026年1月20日 20:29
36氪获悉,锋龙股份公告,公司已就近期股票交易异常波动情况停牌核查,并公告核查结果复牌。目前,公司股票价格已严重脱离公司基本面情况,存在市场情绪过热、非理性炒作风险。公司主营业务未发生变化,未来36个月内,深圳市优必选科技股份有限公司不存在通过上市公司重组上市的计划或安排,未来12个月内不存在资产重组计划。此外,公司提示投资者关注市场竞争环境变化、国际贸易风险以及园林机械电动转型替代等风险。

必和必拓上调2026财年铜产量指引

2026年1月20日 20:19
1月20日,必和必拓发布截至2025年12月31日的2026上半财年运营公报。基于主要矿山运营的优异表现,集团上调了2026财年铜矿产量的指导目标。埃斯康迪达(Escondida)铜矿选矿厂吞吐量创下历史新高,带动全年产量指导目标上调。安塔米纳(Antamina)同样提高了产量指导目标,而斯宾塞(Spence)和南澳铜矿(Copper SA)的运营按计划推进,其中南澳铜矿的精炼黄金产量更突破历史纪录。(界面)

奕帆传动:拟购买北京和利时87.07%股权,预计构成重大资产重组

2026年1月20日 20:12
36氪获悉,奕帆传动公告,公司拟以支付现金方式购买北京和利时电机技术有限公司87.07%的股权,本次交易预计构成重大资产重组。交易完成后,标的公司将成为公司的控股子公司,并纳入公司合并财务报表范围。本次交易有利于公司综合实力的提升,对公司未来业务发展及经营业绩提升将产生积极影响,提升公司整体资产质量和核心竞争力。

爱奇艺首席财务官汪骏因个人原因辞任

2026年1月20日 19:58
36氪获悉,爱奇艺公告,首席财务官汪骏因个人原因辞任,即日生效。同时,公司任命财务高级副总裁曾颖担任公司临时首席财务官。汪骏将继续担任爱奇艺顾问至2026年5月31日,以确保平稳过渡。公司即将启动新任首席财务官的遴选工作。

凯捷计划在法国裁员至多2400人

2026年1月20日 19:29
法国凯捷集团1月20日发布声明称,计划在法国裁员至多2400人,约占其法国员工总数的6%。该公司表示:“凯捷正在法国实行转型,以应对技术加速变革(包括人工智能)以及客户需求变化带来的挑战和机遇。目前法国经济增长缓慢,某些行业面临重大挑战。”凯捷称,将为员工提供自愿内部再培训或离职方案。(财联社)

氪星晚报 |宜家计划在五年内将其在印度的投资增加一倍,至超过22亿美元;百度文心助手月活破2亿;王腾新公司“今日宜休”完成数千万种子轮融资

2026年1月20日 19:25

大公司:

燕东微:预计2025年净亏损3.4亿元-4.25亿元

36氪获悉,燕东微发布2025年度业绩预告。报告显示,预计归属于母公司所有者的净利润亏损3.4亿元至4.25亿元。2025年度公司利润有所下滑,主要原因为消费类产品受宏观环境影响,市场发生变化,部分产品竞争激烈,导致产品售价有所下降;同时本期研发投入、人员数量及股权激励摊销有所增加。

永辉超市:预计2025年归母净亏损21.4亿元

36氪获悉,永辉超市公告,预计2025年度归母净利润亏损21.4亿元,上年同期亏损14.7亿元。

同花顺:2025年净利同比预增50%-80%

36氪获悉,同花顺发布2025年度业绩预告。报告显示,预计归属于上市公司股东的净利润为27.35亿元–32.82亿元,比上年同期增长50%-80%。

豆包与浦东美术馆达成合作

36氪获悉,1月20日,字节跳动旗下豆包与上海浦东美术馆达成合作,成为该馆“图案的奇迹:卢浮宫印度、伊朗与奥斯曼的艺术杰作”与“非常毕加索:保罗•史密斯的新视角”两大展的官方AI讲解员。据了解,展览期间,观众可随时通过豆包App的“视频通话”功能或拍照提问方式,获取针对具体展品的专业讲解。

南侨食品:2025年净利润同比预降78.39%-81.99%

36氪获悉,南侨食品发布2025年度业绩预告。报告显示,预计2025年年度实现归属于上市公司股东的净利润3626.37万元到4351.64万元,同比减少78.39%到81.99%。报告期内,由于全年各主要原材料价格同比有不同程度上涨,带来生产成本的增加,从而导致公司整体毛利率以及净利率的下降。此外,公司高毛利产品烘焙应用油脂收入占比的下降,使得整体毛利率进一步承压。

国际复材:董事长江凌退休离任

36氪获悉,国际复材公告,公司董事长江凌因到龄退休申请辞去第三届董事会董事长等职务,该辞职报告自送达董事会时生效。江凌将继续履职至公司2026年第一次临时股东会补选新第三届董事会非独立董事之日。公司董事会提名莫秋实为新任董事候选人。

光明乳业:预计2025年净利润亏损1.2亿元-1.8亿元

36氪获悉,光明乳业发布2025年度业绩预告。报告显示,预计2025年度归属于母公司所有者的净利润为-1.8亿元到-1.2亿元。上年同期为盈利7.22亿元。本期业绩预亏主要因海外子公司新莱特生产基地出现生产问题,导致存货报废、生产成本上升,造成经营亏损,公司持有其65.25%股份,进而影响整体利润。

海康威视:2025年净利润141.88亿元,同比增长18.46%

36氪获悉,海康威视发布2025年度业绩快报。报告显示,期内实现营业总收入925.18亿元,同比增长0.02%。营业利润、利润总额和归属于上市公司股东的净利润分别为169.78亿元、170.29亿元和141.88亿元,同比分别增长18.63%、18.72%和18.46%。公司总资产和归属于上市公司股东的所有者权益分别达到1381.50亿元和833.38亿元,同比分别增长4.65%和3.31%。

宜家计划在五年内将其在印度的投资增加一倍,至超过22亿美元

据报道,瑞典宜家高层管理人员周一表示,未来五年,这家家具零售商将把在印度的投资增加一倍以上,超过2000亿卢比(约合22亿美元),计划开设更多门店并扩大本地采购规模。(新浪财经)

百度文心助手月活破2亿

36氪获悉,1月20日,百度旗下文心助手月活用户数已突破2亿,与豆包、千问形成国内三大亿级AI入口。此前国内报道,文心助手是百度App推出的AI智能助手,依托文心大模型和“百度猎户座”AI引擎,实现了搜索与AI的深度重构,是集深度思考、多模态交互与全场景服务于一体的全能搭子。

投融资:

王腾新公司“今日宜休”完成数千万种子轮融资

1月20日,“今日宜休”宣布完成总规模为数千万元的种子轮融资,投资方包括高瓴创投(GL Ventures)、云九资本等。据了解,公司将在今年下半年陆续发布系列软硬件产品,并计划出海。(界面)

新产品:

智能无人机品牌博坦Potensic推出AI智能ATOM系列无人机

36氪获悉,近日,智能无人机品牌博坦Potensic推出融合AI短片、AI夜景等智能功能、并符合全球法规的轻量化ATOM系列无人机。同时发布以“Atom 2 + PTD-1带屏遥控器”的产品体验方案,将AI融入飞行、操控与创作中,通过稳定的图传、智能辅助功能与更直观的操控界面。目前,博坦覆盖多个国家和地区的本地化运营,产品销往全球100多个国家和地区。

今日观点:

上期能源:调整国际铜期货相关合约交易保证金比例和涨跌停板幅度

36氪获悉,上海国际能源交易中心发布通知,经研究决定,自2026年1月22日(星期四)收盘结算时起,交易保证金比例和涨跌停板幅度调整如下:国际铜期货已上市合约的涨跌停板幅度调整为8%,套保持仓交易保证金比例调整为9%,一般持仓交易保证金比例调整为10%。如遇《上海国际能源交易中心风险控制管理细则》第十六条规定情况,则在上述交易保证金比例、涨跌停板幅度基础上调整。关于涨跌停板幅度和交易保证金比例的其他事项按《上海国际能源交易中心风险控制管理细则》及相关业务规则执行。

其他值得关注的新闻:

六部门:延续实施养老、托育、家政等社区家庭服务业税费优惠政策

36氪获悉,财政部等部门公告,延续实施养老、托育、家政等社区家庭服务业税费优惠政策。为社区提供养老、托育、家政等服务的机构,提供社区养老、托育、家政服务取得的收入,免征增值税。在计算应纳税所得额时,减按90%计入收入总额。承受房屋、土地用于提供社区养老、托育、家政服务的,免征契税。为社区提供养老、托育、家政等服务的机构自有或其通过承租、无偿使用等方式取得并用于提供社区养老、托育、家政服务的房产、土地,免征房产税、城镇土地使用税。本公告自2026年1月1日起执行至2027年12月31日。

现货黄金一度涨至4691.57美元/盎司

36氪获悉,现货黄金一度涨至4691.57美元/盎司,继续创纪录新高。

❌
❌