10分钟打造基于ChatGPT的Markdown智能文档

news2025/4/21 19:01:22

ChatGPT可以帮助我们实现很多原本很难实现功能,为传统系统加入AI支持,从而提升用户体验。本文介绍了如何给在线Markdown文档系统添加ChatGPT问答支持,将静态文档改造为智能文档。原文: Build a ChatGPT Powered Markdown Documentation in No Time

alt

今天,我们将学习如何构建一个通过ChatGPT来回答关于文档相关问题的系统,该系统将基于OpenAI和Embedbase构建。项目发布在 https://differentai.gumroad.com/l/chatgpt-documentation,也可以在https://docs.embedbase.xyz上尝试互动版。

概述
alt

我们在这里主要讨论:

  1. 需要将内容存储在数据库中。
  2. 需要让用户输入查询。
  3. 在数据库中搜索与用户查询最相似的结果(稍后详细介绍)。
  4. 基于匹配查询的前5个最相似结果创建"上下文"并询问ChatGPT:

根据下面的上下文回答问题,如果不能根据上下文回答问题,就说"我不知道"

上下文:
[上下文内容]
---
问题:
[问题内容]
回答:

实现细节

好,我们开始。

以下是实现本系统需要的前提条件。

  • Embedbase API key: 一个可以找到"最相似结果"的数据库。并不是所有数据库都适合这种工作,我们将使用Embedbase,它可以做到这一点。Embedbase允许我们找到搜索查询和存储内容之间的"语义相似性"。
  • OpenAI API key: 这是ChatGPT部分。
  • Nextra: 并且安装好 Node.js

.env中填好Embedbase和OpenAI API key。

OPENAI_API_KEY="<YOUR KEY>"
EMBEDBASE_API_KEY="<YOUR KEY>"

提醒一下,我们将基于了不起的文档框架Nextra创建由ChatGPT提供支持的QA文档,该框架允许我们使用NextJS、tailwindcss和MDX(Markdown + React)编写文档。我们还将使用Embedbase作为数据库,并调用OpenAI的ChatGPT。

创建Nextra文档

可以在Github[1]上找到官方Nextra文档模板,用模板创建文档之后,可以用任何你喜欢的编辑器打开。

# we won't use "pnpm" here, rather the traditional "npm"
rm pnpm-lock.yaml
npm i
npm run dev

现在请访问https://localhost:3000。

尝试编辑.mdx文档,看看内容有何变化。

准备并存储文件

第一步需要将文档存储在Embedbase中。不过有一点需要注意,如果我们在DB中存储相关联的较小的块,效果会更好,因此我们将把文档按句子分组。让我们从在文件夹scripts中编写一个名为sync.js的脚本开始。

你需要glob库来列出文件,用命令npm i glob@8.1.0(我们将使用8.1.0版本)安装glob库。

const glob = require("glob");
const fs = require("fs");
const sync = async () => {
 // 1. read all files under pages/* with .mdx extension
 // for each file, read the content
 const documents = glob.sync("pages/**/*.mdx").map((path) => ({
  // we use as id /{pagename} which could be useful to
  // provide links in the UI
  id: path.replace("pages/""/").replace("index.mdx""").replace(".mdx"""),
  // content of the file
  data: fs.readFileSync(path, "utf-8")
 }));
 
 // 2. here we split the documents in chunks, you can do it in many different ways, pick the one you prefer
 // split documents into chunks of 100 lines
 const chunks = [];
 documents.forEach((document) => {
  const lines = document.data.split("\n");
  const chunkSize = 100;
  for (let i = 0; i < lines.length; i += chunkSize) {
   const chunk = lines.slice(i, i + chunkSize).join("\n");
    chunks.push({
      data: chunk
   });
  }
 });
}
sync();

现在我们构建好了存储在DB中的块,接下来扩展脚本,以便将块添加到Embedbase。

要查询Embedbase,需要执行npm i node-fetch@2.6.9安装2.6.9版本的node-fetch。

const fetch = require("node-fetch");
// your Embedbase api key
const apiKey = process.env.EMBEDBASE_API_KEY;
const sync = async () => {
 // ...
 // 3. we then insert the data in Embedbase
 const response = await fetch("https://embedbase-hosted-usx5gpslaq-uc.a.run.app/v1/documentation", { // "documentation" is your dataset ID
  method"POST",
  headers: {
   "Authorization""Bearer " + apiKey,
   "Content-Type""application/json"
  },
  bodyJSON.stringify({
   documents: chunks
  })
 });
 const data = await response.json();
 console.log(data);
}
sync();

很好,现在可以运行了:

EMBEDBASE_API_KEY="<YOUR API KEY>" node scripts/sync.js

如果运行良好,应该看到:

alt
获取用户查询

接下来修改Nextra文档主题,将内置搜索栏替换为支持ChatGPT的搜索栏。

theme.config.tsx中添加一个Modal组件,内容如下:

// update the imports
import { DocsThemeConfig, useTheme } from 'nextra-theme-docs'
const Modal = ({ children, open, onClose }) => {
 const theme = useTheme();
 if (!open) return null;
 return (
  <div
    style={{
      position: 'fixed',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: 'rgba(0,0,0,0.5)',
      zIndex: 100,
     }}
    onClick={onClose}>
   
    <div
      style={{
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        backgroundColor: theme.resolvedTheme === 'dark' ? '#1a1a1a: 'white',
        padding: 20,
        borderRadius: 5,
        width: '80%',
        maxWidth: 700,
        maxHeight: '80%',
        overflow: 'auto',
      }}
      onClick={(e) =>
 e.stopPropagation()}>   
        {children}
    </div>
       </div>

 );
};

现在创建搜索栏:

// update the imports
import React, { useState } from 'react'
// we create a Search component
const Search = () => {
  const [open, setOpen] = useState(false);
  const [question, setQuestion] = useState("");
  // ...
  // All the logic that we will see later
  const answerQuestion = () => {  }
  // ...
  return (
    <>
      <input
        placeholder="Ask a question"
 // We open the modal here
 // to let the user ask a question
 onClick={() =>
 setOpen(true)}
 type="text"
      />
      <Modal open={open} onClose={() => setOpen(false)}>
        <form onSubmit={answerQuestion} className="nx-flex nx-gap-3">
   <input
     placeholder="Ask a question"
     type="text"
     value={question}
            onChange={(e) =>
 setQuestion(e.target.value)}
          />
   <button type="submit">     
     Ask
   </button>
        </form>
      </Modal>
    </>

  );
}

最后,更新配置以设置新创建的搜索栏:

const config: DocsThemeConfig = {
 logo<span>My Project</span>,
 project: {
  link'https://github.com/shuding/nextra-docs-template',
 },
 chat: {
  link'https://discord.com',
 },
 docsRepositoryBase'https://github.com/shuding/nextra-docs-template',
 footer: {
  text'Nextra Docs Template',
 },
 // add this to use our Search component
 search: {
  component<Search />
 }
}
构建上下文

这里需要OpenAI token计数库tiktoken,执行npm i @dqbd/tiktoken安装。

接下来创建带上下文的ChatGPT提示词。创建文件pages/api/buildPrompt.ts,代码如下:

// pages/api/buildPrompt.ts
import { get_encoding } from "@dqbd/tiktoken";
// Load the tokenizer which is designed to work with the embedding model
const enc = get_encoding('cl100k_base');
const apiKey = process.env.EMBEDBASE_API_KEY;
// this is how you search Embedbase with a string query
const search = async (query: string) => {
 return fetch("https://embedbase-hosted-usx5gpslaq-uc.a.run.app/v1/documentation/search", {
  method"POST",
  headers: {
   Authorization"Bearer " + apiKey,
   "Content-Type""application/json"
  },
  bodyJSON.stringify({
   query: query
  })
 }).then(response => response.json());
};
const createContext = async (question: string, maxLen = 1800) => {
 // get the similar data to our query from the database
 const searchResponse = await search(question);
 let curLen = 0;
 const returns = [];
 // We want to add context to some limit of length (tokens)
 // because usually LLM have limited input size
 for (const similarity of searchResponse["similarities"]) {
  const sentence = similarity["data"];
  // count the tokens
  const nTokens = enc.encode(sentence).length;
  // a token is roughly 4 characters, to learn more
  // https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
  curLen += nTokens + 4;
  if (curLen > maxLen) {
   break;
  }
  returns.push(sentence);
 }
 // we join the entries we found with a separator to show it's different
 return returns.join("\n\n###\n\n");
}
// this is the endpoint that returns an answer to the client
export default async function buildPrompt(req, res{
 const prompt = req.body.prompt;
 const context = await createContext(prompt);
 const newPrompt = `Answer the question based on the context below, and if the question can't be answered based on the context, say "I don't know"\n\nContext: ${context}\n\n---\n\nQuestion: ${prompt}\nAnswer:`;
 res.status(200).json({ prompt: newPrompt });
}
调用ChatGPT

首先,在文件utils/OpenAIStream.ts中添加一些用于对OpenAI进行流调用的函数,执行npm i eventsource-parser安装eventsource-parser。

import {
  createParser,
  ParsedEvent,
  ReconnectInterval,
from "eventsource-parser";
export interface OpenAIStreamPayload {
 model: string;
 // this is a list of messages to give ChatGPT
 messages: { role"user"; content: string }[];
 stream: boolean;
}
  
export async function OpenAIStream(payload: OpenAIStreamPayload{
 const encoder = new TextEncoder();
 const decoder = new TextDecoder();
 
 let counter = 0;
 const res = await fetch("https://api.openai.com/v1/chat/completions", {
  headers: {
   "Content-Type""application/json",
   "Authorization"`Bearer ${process.env.OPENAI_API_KEY ?? ""}`,
  },
  method"POST",
  bodyJSON.stringify(payload),
 });
 
 const stream = new ReadableStream({
  async start(controller) {
   // callback
   function onParse(event: ParsedEvent | ReconnectInterval{
    if (event.type === "event") {
     const data = event.data;
     // https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
     if (data === "[DONE]") {
      controller.close();
      return;
     }
     try {
      const json = JSON.parse(data);
      // get the text response from ChatGPT
      const text = json.choices[0]?.delta?.content;
      if (!text) return;
      if (counter < 2 && (text.match(/\n/) || []).length) {
       // this is a prefix character (i.e., "\n\n"), do nothing
       return;
      }
      const queue = encoder.encode(text);
      controller.enqueue(queue);
      counter++;
     } catch (e) {
      // maybe parse error
      controller.error(e);
     }
    }
   }
  
   // stream response (SSE) from OpenAI may be fragmented into multiple chunks
   // this ensures we properly read chunks and invoke an event for each SSE event stream
   const parser = createParser(onParse);
   // https://web.dev/streams/#asynchronous-iteration
   for await (const chunk of res.body as any) {
    parser.feed(decoder.decode(chunk));
   }
  },
 });
 
   
 return stream;
}

然后创建文件pages/api/qa.ts,作为对ChatGPT进行流调用的端点。

// pages/api/qa.ts
import { OpenAIStream, OpenAIStreamPayload } from "../../utils/OpenAIStream";
export const config = {
  // We are using Vercel edge function for this endpoint
  runtime"edge",
};
interface RequestPayload {
 prompt: string;
}
const handler = async (req: Request, res: Response): Promise<Response> => {
 const { prompt } = (await req.json()) as RequestPayload;
 if (!prompt) {
  return new Response("No prompt in the request", { status400 });
 }
 const payload: OpenAIStreamPayload = {
  model"gpt-3.5-turbo",
  messages: [{ role"user"content: prompt }],
  streamtrue,
 };
 const stream = await OpenAIStream(payload);
 return new Response(stream);
};
export default handler;
连接一切并提问

现在是时候通过API调用提问。编辑theme.config.tsx,将该函数添加到Search组件中:

// theme.config.tsx
const Search = () => {
 const [open, setOpen] = useState(false);
 const [question, setQuestion] = useState("");
 const [answer, setAnswer] = useState("");
 const answerQuestion = async (e: any) => {
  e.preventDefault();
  setAnswer("");
  // build the contextualized prompt
  const promptResponse = await fetch("/api/buildPrompt", {
   method"POST",
   headers: {
    "Content-Type""application/json",
   },
   bodyJSON.stringify({
    prompt: question,
   }),
  });
  const promptData = await promptResponse.json();
  // send it to ChatGPT
  const response = await fetch("/api/qa", {
   method"POST",
   headers: {
    "Content-Type""application/json",
   },
   bodyJSON.stringify({
    prompt: promptData.prompt,
   }),
  });
  if (!response.ok) {
   throw new Error(response.statusText);
  }
  const data = response.body;
  if (!data) {
   return;
  }
  
  const reader = data.getReader();
  const decoder = new TextDecoder();
  let done = false;
  // read the streaming ChatGPT answer
  while (!done) {
   const { value, done: doneReading } = await reader.read();
   done = doneReading;
   const chunkValue = decoder.decode(value);
   // update our interface with the answer
   setAnswer((prev) => prev + chunkValue);
  }
 };
 return (
  <>
   <input
    placeholder="Ask a question"
    onClick={() =>
 setOpen(true)}
    type="text"
   />
   <Modal open={open} onClose={() => setOpen(false)}>
    <form onSubmit={answerQuestion} className="nx-flex nx-gap-3">
     <input
      placeholder="Ask a question"
      type="text"
      value={question}
      onChange={(e) =>
 setQuestion(e.target.value)}
     />
     <button type="submit">
      Ask
     </button>
    </form>
    <p>
     {answer}
    </p>
   </Modal>
  </>

 );
}

你现在应该能看到:

alt

当然,可以随意改进样式。

结论

总结一下,我们做了:

  • 创建了Nextra文档
  • 在Embedbase中准备和存储文档
  • 构建了获取用户查询的接口
  • 在数据库中搜索需要查询ChatGPT的问题上下文
  • 使用此上下文构建提示并调用ChatGPT
  • 通过将所有内容联系起来,让用户提问

感谢阅读本文,Github[2]上有一个创建此类文档的开源模板。

延伸阅读

嵌入(Embedding)是一种机器学习概念,允许我们将数据的语义数字化,从而创建以下功能:

  • 语义搜索(例如,"牛吃草"和"猴子吃香蕉"之间有什么相似之处,也适用于比较图像等)
  • 推荐系统(如果你喜欢电影《阿凡达》,可能也会喜欢《星球大战》)
  • 分类("这部电影太棒了"是肯定句,"这部电影烂透了"是否定句)
  • 生成式搜索(可以回答有关PDF、网站、YouTube视频等问题的聊天机器人)

Embedding并不是一项新技术,但由于OpenAI Embedding端点的快速和廉价,最近变得更受欢迎、更通用、更容易使用。在网上有很多关于Embedding的信息,因此我们不会深入研究Embedding的技术主题。

AI embedding可以被认为是哈利波特的分院帽。就像分院帽根据学生特质来分配学院一样,AI embedding也是根据特征来分类相似内容。当我们想找到类似内容时,可以要求AI为我们提供内容的embedding,计算它们之间的距离。embedding之间的距离越近,内容就越相似。这个过程类似于分院帽如何利用每个学生的特征来确定最适合的学院。通过使用AI embedding,我们可以根据内容特征快速、轻松的进行比较,从而做出更明智的决定和更有效的搜索结果。

alt

上面描述的方法只是简单的嵌入单词,但如今已经可以嵌入句子、图像、句子+图像以及许多其他东西。

如果想在生产环境中使用embedding,有一些陷阱需要小心:

  • 大规模存储embedding的基础设施
  • 成本优化(例如避免计算两次数据)
  • 用户embedding的隔离(不希望搜索功能显示其他用户的数据)
  • 处理模型输入的大小限制
  • 与流行的应用基础设施(supabase, firebase,谷歌云等)集成
在GitHub Action中持续准备数据

embedding的意义在于能够索引任何类型的非结构化数据,我们希望每次修改文档时都能被索引,对吧?下面展示的是一个GitHub Action,当主分支完成git push时,将索引每个markdown文件:

# .github/workflows/index.yaml
name: Index documentation
on:
  push:
    branches:
      - main
jobs:
  index:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
   node-version: 14
      - run: npm install
      - run: node scripts/sync.js
 env:
   EMBEDBASE_API_KEY: ${{ secrets.EMBEDBASE_API_KEY }}

别忘了把EMBEDBASE_API_KEY添加到你的GitHub密钥里。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind

参考资料

[1]

Nextra Docs Template: https://github.com/shuding/nextra-docs-template

[2]

Embedbase Nextra Docs Template: https://github.com/another-ai/embedbase-nextra-docs-template

- END -

本文由 mdnice 多平台发布

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

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

相关文章

第五十九章 Unity 发布Android平台

本章节我们讲解如何打包发布到安卓手机平台。要为 Android 构建和运行应用程序&#xff0c;必须安装 Unity Android Build Support 平台模块。还需要安装 Android 软件开发工具包&#xff08;SDK&#xff09;和原生开发工具包&#xff08;NDK&#xff09;才能在 Android 设备上…

【UDS】ISO15765-2之诊断时间参数

文章目录 简介分类1. P2client2. P2server3. P2*client4. P2*server5. S3client5. S3server 总结 ->返回总目录<- 简介 诊断层包含六种定时器时间参数&#xff1a;P2client, P2client, P2server, P2server, S3client, S3server。 相对来说&#xff0c;比较好理解。就是…

Unity的UGUI避免行的开头出现符号

一、遇到问题 大家好&#xff0c;我是阿赵。最近在游戏过版署的时候&#xff0c;修改意见里面有一条&#xff0c;游戏内部分文本内容中有标点符号出现在行首的问题。 一般来说&#xff0c;我们编辑文本的时候&#xff0c;是会注意不要把标点符号在换行的时候刚好出现的在行首的…

从学校到职场:在阿里的这7年

简介&#xff1a; 本文不仅希望能给一些初入职场的同学一些思考&#xff0c;也是通过自己阿里7年的总结&#xff0c;对自己未来的一种期望&#xff0c;也希望自己未来再遇到槽糕的处境迷茫的时候&#xff0c;可以回看下自己的心路历程&#xff0c;并做出更理性的选择。 引言&a…

软件测试基础知识整理(二) - 常用dos命令、服务器和域名

目录 一、常用dos命令 二、服务器和域名 2.1 网站上线的基本条件 2.1.1 域名 2.1.2 空间、服务器、云存储 一、常用dos命令 Win R 打开运行窗口&#xff0c;输入cmd即可进入命令行窗口 常用命令作用举例ipconfig/all查看IP的主机信息&#xff0c;DNS信息&#xff0c;物理…

高效理解机器学习

对于初学者来说&#xff0c;机器学习相当复杂&#xff0c;可能很容易迷失在细节的海洋里。本文通过将机器学习算法分为三个类别&#xff0c;梳理出一条相对清晰的路线&#xff0c;帮助初学者理解机器学习算法的基本原理&#xff0c;从而更高效的学习机器学习。原文: Machine Le…

《设计模式》访问者模式

《设计模式》访问者模式 定义&#xff1a; 访问者模式用于封装一些作用于某种数据结构中的各元素的操作&#xff0c;将数据结构和数据操作分离&#xff0c;它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。属于行为型模式。 访问模式的角色组成&#xff1a; …

基本数据类型不一定存储在栈中,是不是颠覆了你的认知

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;有很多小伙伴问我&#xff0c;基本数据类型一定在栈内存中吗&#xff1f;网上答案也是五花八门&#xff0c;部分读者都有被误导过&#xff0c;基本数据类型不一定在栈内存中&#xff01; 虽然基本…

使用Lychee荔枝图床+cpolar内网穿透快速搭建稳定的私人图床【无需公网IP】

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

【Linux内核解析-linux-5.14.10-内核源码注释】自旋锁spinlock机制

自旋锁 Note: 在使用自旋锁时应该避免长时间持有锁&#xff0c;否则可能会导致其他线程或进程无法访问共享资源。因此&#xff0c;建议将锁的持有时间尽量缩短&#xff0c;以提高系统的并发性能。 Linux中的自旋锁机制是一种用于同步多个线程或进程访问共享资源的技术。当一个…

一分钟图情论文:《公共图书馆法》视域下的馆员知识与能力体系探究

一分钟图情论文&#xff1a;《公共图书馆法》视域下的馆员知识与能力体系探究 在公共服务体系建设过程中&#xff0c;图书馆建设是十分关键地一环&#xff0c;在图书馆建设过程中又以图书馆员队伍的建设首当其冲。在当今复杂的信息环境下&#xff0c;我们该如何培养图书馆员&a…

语言与专业的奇迹:如何利用ChatGPT优化跨国贸易

贸易公司&#xff0c;在进行跨国贸易时&#xff0c;往往需要面对不同国家的甲方或者乙方&#xff0c;在与之沟通的过程中&#xff0c;语言和专业是必须要过的一关&#xff0c;顺畅的交流&#xff0c;往往会带来更好的收益。 今天以“茶”为例&#xff0c;给大家介绍一“知否AI…

Nacos 服务网格2

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

cam_lidar_calibration标定速腾激光雷达和单目相机外参

目录 一、资源链接二、代码测试2.1安装依赖2.2代码下载和修改2.2.1 optimiser.h文件2.2.2 feature_extractor.h文件 2.3编译代码2.4测试数据集2.4.1迭代计算2.4.2查看校准结果 三、标定自己激光雷达和相机3.1修改代码3.1.1camera_info.yaml配置文件3.1.2params.yaml配置文件3.1…

跳槽,如果没有更好的选择,可以去美团试试···

在美团干了半年&#xff0c;说一下自己的感受&#xff0c;美团是一家福利中等&#xff0c;工资待遇中上&#xff0c;高层管理团队强大&#xff0c;加班强度一般&#xff0c;技术不错&#xff0c;办公环境一般&#xff0c;工作氛围中上&#xff0c;部门差距之间工作体验差距巨大…

阿里巴巴官方上线!号称国内Java八股文天花板(终极版)首次开源

铜三铁四已经结束了&#xff0c;但还是有很多Java程序员没有找到工作或者成功跳槽&#xff0c;跳槽成功的也只是从一个坑中&#xff0c;跳入另一个坑中…… 在LZ看来&#xff0c;真正有意义的就业与跳槽&#xff0c;是要进入到一个有绝对潜力的行业或者薪资能实现爆炸式增长的。…

Science Advances:宋艳课题组发现经颅近红外激光刺激可提升人类工作记忆

图1. 新闻稿封面 工作记忆——在几秒钟内主动“记住”有用信息的能力——在许多高级认知活动中起着至关重要的作用。由于工作记忆能力的个体差异可以预测流体智力和广泛的认知功能&#xff0c;这使得提高工作记忆能力成为干预和增强的有吸引力的目标。 美国食品及药品管理局声…

SpringSecurity 一文彻底掌握

文章目录 前言一、SpringSecurity Web方案&#x1f353;Test Controller 测试请求控制器&#x1f923;SpringSecurity 基本原理&#x1f30d;代码底层流程&#xff1a;重点看三个过滤器FilterSecurityInterceptor 方法级的权限过滤器ExceptionTranslationFilter 异常过滤器User…

智能玩具机器人语音识别方案——NRK3301离线语音IC

机器人玩具已经成为儿童玩具和教育用品的主流&#xff0c;它不仅能充分激发和满足了儿童消费群体的好奇心&#xff0c;同时还能强化了消费群体和玩具的互动体验。 机器人玩具主要是通过语音识别技术&#xff0c;让我们可以与玩具对话&#xff0c;可以用语音对玩具发出命令&…

ENVI实现基于像元方法的栅格图像镶嵌拼接(所有图像无需地理信息)

本文介绍基于ENVI软件&#xff0c;利用“Pixel Based Mosaicking”工具实现栅格遥感影像镶嵌拼接的方法。 首先需要说明的是&#xff0c;本文需要镶嵌的遥感影像并不含地理参考信息&#xff0c;因此仅可以使用ENVI中的“Pixel Based Mosaicking”工具&#xff08;该工具可以对…