From 65bd3ec0ac9ea4a7d1abf555c2e43e283d1b3466 Mon Sep 17 00:00:00 2001 From: pooneyy <85266337+pooneyy@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:41:01 +0800 Subject: [PATCH] add Typesense --- apps/typesense/29.0/.env.sample | 9 + apps/typesense/29.0/data.yml | 19 ++ apps/typesense/29.0/docker-compose.yml | 17 + apps/typesense/README.md | 364 ++++++++++++++++++++++ apps/typesense/README_en.md | 410 +++++++++++++++++++++++++ apps/typesense/data.yml | 19 ++ apps/typesense/logo.png | Bin 0 -> 12321 bytes 7 files changed, 838 insertions(+) create mode 100644 apps/typesense/29.0/.env.sample create mode 100644 apps/typesense/29.0/data.yml create mode 100644 apps/typesense/29.0/docker-compose.yml create mode 100644 apps/typesense/README.md create mode 100644 apps/typesense/README_en.md create mode 100644 apps/typesense/data.yml create mode 100644 apps/typesense/logo.png diff --git a/apps/typesense/29.0/.env.sample b/apps/typesense/29.0/.env.sample new file mode 100644 index 000000000..dbf8a3a18 --- /dev/null +++ b/apps/typesense/29.0/.env.sample @@ -0,0 +1,9 @@ +CONTAINER_NAME="1panel-apps" +DATA_PATH="./data" +DATA_PATH_INTERNAL="/data" +ENV1="" +IMAGE="" +PANEL_APP_PORT_HTTP=40329 +PANEL_APP_PORT_HTTP_INTERNAL=40329 +RESTART_POLICY="always" +TIME_ZONE="Asia/Shanghai" diff --git a/apps/typesense/29.0/data.yml b/apps/typesense/29.0/data.yml new file mode 100644 index 000000000..14e3509f0 --- /dev/null +++ b/apps/typesense/29.0/data.yml @@ -0,0 +1,19 @@ +additionalProperties: + formFields: + - default: 8108 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + ja: ポート + ms: Port + pt-br: Porta + ru: Порт + ko: 포트 + zh: 端口 + zh-Hant: 埠 + required: true + rule: paramPort + type: number diff --git a/apps/typesense/29.0/docker-compose.yml b/apps/typesense/29.0/docker-compose.yml new file mode 100644 index 000000000..9bebd87bb --- /dev/null +++ b/apps/typesense/29.0/docker-compose.yml @@ -0,0 +1,17 @@ +services: + 2fauth: + image: typesense/typesense:29.0 + container_name: ${CONTAINER_NAME} + restart: always + ports: + - ${PANEL_APP_PORT_HTTP}:8108 + networks: + - 1panel-network + volumes: + - ./data:/data + command: '--data-dir /data --api-key=xyz --enable-cors' + labels: + createdBy: "Apps" +networks: + 1panel-network: + external: true diff --git a/apps/typesense/README.md b/apps/typesense/README.md new file mode 100644 index 000000000..6139adaf1 --- /dev/null +++ b/apps/typesense/README.md @@ -0,0 +1,364 @@ + [![Typesense](https://github.com/typesense/typesense/raw/v29/assets/typesense_logo.svg)](https://typesense.org) + +Typesense 是一个快速且容忍拼写错误的搜索引擎,适用于构建令人愉悦的搜索体验。 + +一个开源的 Algolia 替代方案 +一个使用更简单的 ElasticSearch 替代方案 + +[![](https://img.shields.io/docker/pulls/typesense/typesense)](https://hub.docker.com/r/typesense/typesense/tags) [![](https://img.shields.io/github/stars/typesense/typesense?label=github%20stars&style=flat)](https://github.com/typesense) +[![](https://img.shields.io/badge/searches_per_month_on_typesense_cloud-10%20Billion-blue)](https://cloud.typesense.org) + +[网站](https://typesense.org) | [文档](https://typesense.org/docs/) | [路线图](https://typesense.link/roadmap) | [Slack 社区](https://typesense.link/slack-community) | [社区主题](https://threads.typesense.org/kb) | [Twitter](https://twitter.com/typesense) + +![Typesense Demo](https://github.com/typesense/typesense/raw/v29/assets/typesense_books_demo.gif) + +✨ 这里有几个 **实时演示** ,展示了 Typesense 在大规模数据集中的应用: + +* 搜索 MusicBrainz 中的 3200 万首歌曲数据集:[songs-search.typesense.org](https://songs-search.typesense.org/) +* 搜索 OpenLibrary 中的 2800 万本书数据集:[books-search.typesense.org](https://books-search.typesense.org/) +* 搜索来自 RecipeNLG 的 200 万食谱数据集:[recipe-search.typesense.org](https://recipe-search.typesense.org/) +* 搜索来自 Linux 内核的 100 万 Git 提交信息:[linux-commits-search.typesense.org](https://linux-commits-search.typesense.org/) +* 带有自动补全的拼写检查器,包含 333,000 个英语单词:[spellcheck.typesense.org](https://spellcheck.typesense.org/) +* 电子商务浏览体验:[ecommerce-store.typesense.org](https://ecommerce-store.typesense.org/) +* GeoSearch / 搜索体验: [airbnb-geosearch.typesense.org](https://airbnb-geosearch.typesense.org/) +* Search / 浏览按主题分类的 xkcd 漫画: [xkcd-search.typesense.org](https://xkcd-search.typesense.org/) +* 语义 / 混合搜索 30 万条 HN 评论: [hn-comments-search.typesense.org](https://hn-comments-search.typesense.org) + +🗣️ 🎥 如果您更喜欢观看视频: + +* 这里有一个视频,我们介绍了 Typesense 并展示了操作流程:[https://youtu.be/F4mB0x_B1AE?t=144](https://youtu.be/F4mB0x_B1AE?t=144) +* 请查看 Typesense 在 Google I/O 开发者大会上的最新提及:[https://youtu.be/qBkyU1TJKDg?t=2399](https://youtu.be/qBkyU1TJKDg?t=2399) +* 这里有一个视频,我们的社区成员对 Typesense 进行了概述,并展示了完整的演示:[https://www.youtube.com/watch?v=kwtHOkf7Jdg](https://www.youtube.com/watch?v=kwtHOkf7Jdg) + +## 快速链接 + +* [功能](#功能) +* [基准测试](#基准测试) +* [开发路线图](#开发路线图) +* [谁在使用这个?](#谁在使用这个?) +* [安装](#安装) +* [快速开始](#快速开始) +* [逐步指南](#逐步指南) +* [API 文档](#API-文档) +* [API 客户端](#API-客户端) +* [搜索 UI 组件](#搜索-UI-组件) +* [常见问题](#常见问题) +* [支持](#支持) +* [贡献](#贡献) +* [获取最新更新](#获取最新更新) +* [从源代码构建](#从源代码构建) + +## 功能 + +* **拼写宽容度:** 开箱即用地处理拼写错误。 +* **简单易用:**易于设置、集成、操作和扩展。 +* **⚡ 极速高效:**用 C++ 编写。从底层精心设计,以实现低延迟(<50ms)的即时搜索。 +* **可调排名:**轻松调整搜索结果以达到最佳效果。 +* **排序:**根据查询时的特定字段动态排序(例如“按价格升序排序”功能)。 +* **分面搜索与筛选:** 深入细化并精炼结果。 +* **分组与唯一性:** 将相似结果分组以展示更多多样性。 +* **联合搜索:** 在单个 HTTP 请求中跨多个集合(索引)进行搜索。 +* **地理搜索:** 按纬度/经度周围的结果进行搜索和排序,或在边界框内进行搜索。 +* **向量搜索:** 在 Typesense 中索引来自你的机器学习模型的嵌入,并进行最近邻搜索。可以用于构建相似性搜索、语义搜索、视觉搜索、推荐等。 +* **语义/混合搜索:** 在 Typesense 中自动生成嵌入,使用内置模型如 S-BERT、E-5 等,或使用 OpenAI、PaLM API 等,对查询和索引数据进行处理。这允许你将 JSON 数据输入 Typesense,并构建开箱即用的语义搜索+关键词搜索体验。 +* **对话式搜索(内置检索生成):** 向 Typesense 发送问题,并基于你已索引在 Typesense 中的数据生成完整的句子作为响应。类似于 ChatGPT,但基于你自己的数据。 +* **自然语言搜索:** 基于 LLM 的意图检测与查询理解,将任何自由格式的自然语言短语转换为结构化的过滤器、排序和查询。 +* **图像搜索:** 使用图像内容的文字描述在图像中进行搜索,或使用 CLIP 模型进行相似性搜索。 +* **语音搜索:** 通过语音录音捕获并发送查询 - Typesense 将使用 Whisper 模型进行转录并提供搜索结果。 +* **范围受限的 API 密钥:** 生成仅允许访问某些记录的 API 密钥,适用于多租户应用程序。 +* **JOINs:** 通过共同的引用字段连接一个或多个集合,并在查询时将它们连接起来。这允许您优雅地建模 SQL-like 关系。 +* **同义词:** 定义词语之间的等同关系,因此搜索一个词语也将返回定义的同义词结果。 +* **内容策展与商品陈列:** 将特定记录在搜索结果中提升到固定位置,以突出展示。 +* **raft-based 聚类:** 设置一个高可用的分布式集群。 +* **无缝版本升级:** 随着新版本的 Typesense 发布,升级过程只需替换二进制文件并重启 Typesense 即可。 +* **无运行时依赖:**Typesense 是一个单二进制文件,你可以用一个命令在本地或生产环境中运行。 + +**这里没有看到某个功能?** 如果有人已经请求过该功能,请在相关问题中留言说明你的使用场景,或者如果没有相关问题,可以新开一个 issue。我们根据用户反馈来优先考虑功能开发,所以非常希望听到你的声音。 + +## 路线图 + +这是 Typesense 的公开路线图:[https://typesense.link/roadmap](https://typesense.link/roadmap)。 + +第一列还解释了我们如何优先考虑功能、如何影响优先级以及我们的发布节奏。 + +## 基准测试 + +* 一个包含 **220 万份食谱** (食谱名称和食材)的数据集: + * 在 Typesense 中索引时占用约 900MB 的内存 + * 全部 220 万条记录的索引耗时约 3.6 分钟 + * 在一台配备 4vCPU 的服务器上,Typesense 能够处理每秒高达**104 个并发搜索查询**,平均搜索处理时间为**11ms**。 +* 包含**2800万本书**(书名、作者和类别)的数据集: + * 在 Typesense 中索引时大约使用了 14GB 的 RAM + * 耗时78分钟索引全部2800万条记录 + * 在一台拥有 4 个 vCPU 的服务器上,Typesense 能够处理每秒高达 **46 个并发搜索查询** ,平均搜索处理时间为 **28ms**。 +* 在包含 **300 万产品** (亚马逊产品数据)的数据集中,Typesense 能够在 8 个 vCPU 的 3 节点高可用 Typesense 集群上处理每秒高达 **250 个并发搜索查询** 。 + +我们希望使用更大的数据集进行基准测试,如果能找到公开的大数据集的话。如果你有任何关于开放结构化数据集的建议,请通过创建问题告诉我们。我们也很乐意收到你自己的大数据集的基准测试结果,请提交一个拉取请求! + +## 谁在使用这个? + +Typesense 被不同领域和垂直行业的用户广泛使用。 + +在 Typesense Cloud 上,我们每月处理超过 **100 亿** 次搜索。Typesense 的 Docker 镜像已被下载超过 1200 万次。 + +我们最近开始在我们的 [展示区](SHOWCASE.md) 记录使用它的用户。如果您希望被包含在列表中,请随时编辑 [SHOWCASE.md](SHOWCASE.md) 并向我们提交 PR。 + +您还会在 [Typesense Cloud](https://cloud.typesense.org) 的首页上看到一列用户标志。 + +## 安装 + +**Option 1:** 您可以下载我们为 Linux(x86_64 和 arm64)和 Mac(x86_64)发布的二进制包。 + +**Option 2:** 您也可以从我们的官方 Docker 镜像运行 Typesense。 + +**Option 3:** 使用 Typesense Cloud 启动托管集群: + +[![Deploy with Typesense Cloud](https://github.com/typesense/typesense/raw/v29/assets/deploy_with_typesense_cloud.svg)](https://cloud.typesense.org) + +## 快速开始 + +以下是一个快速示例,展示如何在 Typesense 中创建集合、索引文档并对其进行搜索。 + +让我们先通过 Docker 启动 Typesense 服务器: + +``` +docker run -p 8108:8108 -v/tmp/data:/data typesense/typesense:29.0 --data-dir /data --api-key=Hu52dwsas2AdxdE +``` + +我们有几种语言的 [API 客户端](#api-clients) ,但这个例子中我们使用 Python 客户端。 + +安装 Typesense 的 Python 客户端: + +``` +pip install typesense +``` + +现在我们可以初始化客户端并创建一个 `companies` 集合: + +```python +import typesense + +client = typesense.Client({ + 'api_key': 'Hu52dwsas2AdxdE', + 'nodes': [{ + 'host': 'localhost', + 'port': '8108', + 'protocol': 'http' + }], + 'connection_timeout_seconds': 2 +}) + +create_response = client.collections.create({ + "name": "companies", + "fields": [ + {"name": "company_name", "type": "string" }, + {"name": "num_employees", "type": "int32" }, + {"name": "country", "type": "string", "facet": True } + ], + "default_sorting_field": "num_employees" +}) +``` + +现在,让我们向刚刚创建的集合中添加一个文档: + +```python +document = { + "id": "124", + "company_name": "Stark Industries", + "num_employees": 5215, + "country": "USA" +} + +client.collections['companies'].documents.create(document) +``` + +最后,让我们查找我们刚刚索引的文档: + +```python +search_parameters = { + 'q' : 'stork', + 'query_by' : 'company_name', + 'filter_by' : 'num_employees:>100', + 'sort_by' : 'num_employees:desc' +} + +client.collections['companies'].documents.search(search_parameters) +``` + +**你注意到查询文本中的拼写错误了吗?** 没有大碍。Typesense 默认就能处理这类拼写错误! + +## 逐步指南 + +在我们的网站上,您可以查看详细的逐步指南 [这里](https://typesense.org/guide) 。 + +这将引导您完成启动 Typesense 服务器、在其中索引数据以及查询数据集的过程。 + +## API 文档 + +这是我们在网站上提供的官方 API 文档:[https://typesense.org/api](https://typesense.org/api)。 + +如果发现文档或教程有任何问题,请告诉我们或在这里提交一个 PR:[https://github.com/typesense/typesense-website](https://github.com/typesense/typesense-website) + +## API 客户端 + +虽然你可以直接使用 CURL 与 Typesense 服务器交互,但我们提供了官方 API 客户端,以便从你选择的语言中简化使用 Typesense。这些 API 客户端内置了智能重试策略,确保通过它们进行的 API 调用在 HA 配置中具有高可靠性。 + +* [JavaScript](https://github.com/typesense/typesense-js) +* [PHP](https://github.com/typesense/typesense-php) +* [Python](https://github.com/typesense/typesense-python) +* [Ruby](https://github.com/typesense/typesense-ruby) + +如果暂时没有我们语言的 API 客户端,您仍然可以使用任何流行的 HTTP 客户端库直接访问 Typesense 的 API。 + +这里有一些社区贡献的客户端和集成: + +* [Go](https://github.com/typesense/typesense-go) +* [.Net](https://github.com/DAXGRID/typesense-dotnet) +* [Java](https://github.com/typesense/typesense-java) +* [Rust](https://github.com/typesense/typesense-rust) +* [Dart](https://github.com/typesense/typesense-dart) +* [Perl](https://github.com/Ovid/Search-Typesense) +* [Swift](https://github.com/typesense/typesense-swift) +* [Clojure](https://github.com/runeanielsen/typesense-clj) +* [python orm 客户端](https://github.com/RedSnail/typesense_orm) +* [PHP SEAL 适配器](https://github.com/schranz-search/seal-typesense-adapter) +* [Elixir](https://github.com/jaeyson/ex_typesense) + +我们欢迎社区贡献,增加更多的官方客户端库和集成。请通过 [contact@typsense.org](mailto:contact@typsense.org) 或在 GitHub 上打开问题与我们合作,共同构建架构。🙏 + +### 框架集成 + +我们还提供了以下框架集成: + +* [Laravel](https://github.com/typesense/laravel-scout-typesense-engine) +* [Firebase](https://github.com/typesense/firestore-typesense-search) +* [Gatsby](https://www.gatsbyjs.com/plugins/gatsby-plugin-typesense/) +* [WordPress](https://wordpress.org/plugins/search-with-typesense/?ref=typesense) +* [WooCommerce](https://www.codemanas.com/downloads/typesense-search-for-woocommerce/?ref=typesense) +* [Symfony](https://github.com/acseo/TypesenseBundle) +* [InstantSearch](https://github.com/typesense/typesense-instantsearch-adapter) +* [DocSearch](https://typesense.org/docs/guide/docsearch.html) +* [Docusaurus](https://github.com/typesense/docusaurus-theme-search-typesense) +* [ToolJet](https://tooljet.com/?ref=typesense) +* [Plone CMS](https://pypi.org/project/zopyx.typesense/) +* [Craft CMS](https://plugins.craftcms.com/typesense) +* [SEAL](https://github.com/schranz-search/schranz-search) 提供了 Typesense 在 Laravel、Symfony、Spiral、Yii 和 Laminas Mezzio PHP 框架中的集成 + +### Postman Collection + +我们维护了一个社区贡献的 Postman 集合:[https://github.com/typesense/postman](https://github.com/typesense/postman)。 + +[Postman](https://www.postman.com/downloads/) 是一个应用程序,允许您通过点击而不是在终端中手动输入来执行 HTTP 请求。上面的 Postman 集合提供了模板请求,您可以导入到 Postman 中,以快速调用 Typesense 的 API。 + +## 搜索 UI 组件 + +您可以使用我们的 [InstantSearch.js 适配器](https://github.com/typesense/typesense-instantsearch-adapter) 要快速构建强大的搜索体验,包含过滤、排序、分页等功能。 + +具体方法请参阅:[https://typesense.org/docs/guide/search-ui-components.html](https://typesense.org/docs/guide/search-ui-components.html) + +## 常见问题 + +### 与 Elasticsearch 有何不同? + +Elasticsearch 是一个大型软件,设置、管理、扩展和优化它都需要相当大的努力。它提供了数千个配置参数,以达到理想的配置。因此,它更适合拥有足够资源将其部署到生产环境、定期监控并扩展的大型团队,尤其是当他们需要存储数十亿文档和 PB 级数据(例如日志)时。 + +Typesense 是专门为缩短“上市时间”以提供出色的搜索体验而构建的。它是一个轻量级但强大且可扩展的替代方案,专注于开发人员的幸福感和体验,拥有简洁且文档完善的 API、清晰的语义和智能的默认设置,使其开箱即用,无需调整许多参数。 + +Elasticsearch 运行在 JVM 上,本身就可能需要大量的调优工作以达到最佳性能。相比之下,Typesense 是一个单一的轻量级自包含原生二进制文件,因此设置和操作都非常简单。 + +请查看功能对比 [这里](https://typesense.org/typesense-vs-algolia-vs-elasticsearch-vs-meilisearch/) 。 + +### 这与 Algolia 有何不同? + +Algolia 是一款专有的托管型搜索即服务产品,在成本不是问题的情况下表现良好。根据我们的经验,快速发展的网站和应用很快就会遇到搜索和索引限制,伴随着随着规模扩大而产生的昂贵计划升级。 + +相比之下,Typesense 是一款开源产品,你可以自行在其上运行,也可以使用我们的托管 SaaS 服务——Typesense Cloud。开源版本是免费使用的(当然,除了你自己的基础设施成本)。使用 Typesense Cloud 时,我们不按记录数或搜索操作收费。相反,你会获得一个专用集群,可以向其投入尽可能多的数据和流量。你只需根据所选配置支付固定的每小时费用和带宽费用,类似于大多数现代云平台。 + +从产品角度来看,Typesense 在精神上更接近于 Algolia,而不是 Elasticsearch。然而,我们已经解决了 Algolia 的一些重要限制。 + +Algolia 需要为每种排序方式单独创建索引,这会占用你的计划配额。大多数索引设置,如搜索字段、分面字段、分组字段、排名设置等,都是在创建索引时定义的,而不是在查询时动态设置的。 + +而 Typesense 允许在查询时通过查询参数来配置这些设置,这使其非常灵活,并解锁了新的应用场景。Typesense 还能够在单个索引中提供排序结果,而不需要创建多个索引,这有助于减少内存消耗。 + +Algolia 提供了一些 Typesense 目前尚未具备的功能,如个性化搜索和基于服务器的搜索分析。对于分析,你仍然可以在客户端进行搜索指标的跟踪,并将搜索数据发送到你选择的网站分析工具。 + +我们计划在 Typesense 中弥补这一差距,但在那之前,请通过我们的问题跟踪器创建功能请求,告诉我们这些功能是否是你的应用场景的拦路虎。 + +参见并排功能对比 [这里](https://typesense.org/typesense-vs-algolia-vs-elasticsearch-vs-meilisearch/) 。 + +### 速度很棒,但内存占用如何? + +一个全新的 Typesense 服务器大约会消耗 30 MB 的内存。当你开始索引文档时,内存使用量会相应增加。具体增加多少取决于你索引的字段数量和类型。 + +我们力求使内存中的数据结构保持精简。举个大概的例子:当索引 100 万条 Hacker News 标题及其分数时,Typesense 消耗的内存约为 165 MB。同样大小的数据以 JSON 格式存储在磁盘上的大小为 88 MB。如果你有任何来自你自己的数据集的数字,可以添加到这里,请发送一个 PR 给我们! + +### 为什么选择 GPL 许可证? + +从我们的经验来看,公司通常会对他们使用的带有 GPL 许可证的**库**感到担忧,因为库代码会被直接集成到他们的代码中,从而形成衍生作品并触发 GPL 合规性要求。然而,Typesense Server 是**服务器软件** ,我们期望用户通常会将其作为独立的守护进程运行,而不是将其集成到自己的代码中。GPL 对此类用例给予了宽泛的覆盖和允许(例如:Linux 就是 GPL 许可的)。现在,AGPL 会使通过网络访问的服务器软件被视为衍生作品,而不是 GPL。因此,我们没有选择使用 AGPL 许可证。 + +现在,如果有人对 Typesense 服务器进行了修改,GPL 实际上允许你保留这些修改,只要你不分发修改后的代码。因此,一家公司可以修改 Typesense 服务器并在内部运行修改后的代码,而无需公开其修改内容,只要他们将修改后的代码提供给所有有权访问该修改软件的人。 + +现在,如果有人对 Typesense 服务器进行修改并分发这些修改,那么 GPL 就会生效。鉴于我们已经将工作成果发布给了社区,我们也希望其他人的修改也能以开源的形式回馈给社区。 **我们使用 GPL 来实现这一目的。** 其他许可证可能会允许我们的开源工作被修改、闭源并分发,这是我们希望避免的情况,以确保 Typesense 项目的长期可持续性。 + +这里是我们选择 GPL 的更多背景信息,如 Discourse 所述:[https://meta.discourse.org/t/why-gnu-license/2531](https://meta.discourse.org/t/why-gnu-license/2531)。其中提到的许多观点与我们相符。 + +以上仅适用于 Typesense 服务器。我们的客户端库确实是为了集成到用户代码中而设计的,因此它们使用的是 Apache 许可证。 + +总之,AGPL 通常对服务器软件来说是个问题,我们选择不使用它。我们认为 GPL 对 Typesense 服务器来说能够捕捉到我们希望为这个开源项目实现的核心理念。GPL 有着成功应用于许多流行开源项目的悠久历史。我们的库仍然是使用 Apache 许可证。 + +如果你因为许可证问题而无法使用 Typesense,并且希望进一步探讨这个问题,请随时联系我们。 + +## 支持 + +👋 🌐 如果你有关于 Typesense 的一般性问题,想要打招呼或者只是想关注一下,我们诚邀你加入我们的公共 [Slack 社区](https://typesense.link/slack-community) 。 + +如果你遇到任何问题或 bug,请在 GitHub 上创建一个 issue,我们会尽力帮助你。 + +我们通过 GitHub 上的问题跟踪器提供良好的支持。然而,如果您希望获得私密且优先级较高的支持: + +* 保证的服务级别协议 +* 电话/视频通话以讨论您的具体用例,并获得最佳实践建议 +* Slack 上的私聊 +* 扩展最佳实践指南 +* 优先级高的功能请求 + +我们提供付费支持选项,详情请参阅 [这里](https://typesense.org/support/) 。 + +## 贡献 + +我们是一个精简团队,致力于普及搜索技术,我们非常需要各方面的帮助!如果您愿意参与进来,请查看 [Contributing.md](https://github.com/typesense/typesense/blob/master/CONTRIBUTING.md) 了解我们如何需要您的帮助。 + +## 获取最新更新 + +如果你想在我们发布新版本时收到更新,请点击页面顶部的“Watch”按钮,然后选择“仅发布内容”。GitHub 将会在每次新发布时向你发送通知和变更日志。 + +我们还会在 Twitter 账号上发布关于 Typesense 的发布信息及其他相关主题的更新。关注我们:[@typesense](@typesense) + +👋 🌐 我们还会在 Slack 社区发布更新。[加入我们的 Slack 社区](加入我们的 Slack 社区)。 + +## 从源代码构建 + +我们使用 [Bazel](https://bazel.build) 进行构建。 + +Typesense 需要以下依赖: + +* 兼容 C++11 的编译器(GCC >= 4.9.0,Apple Clang >= 8.0,Clang >= 3.9.0) +* Snappy +* zlib +* OpenSSL (>=1.0.2) +* curl +* ICU + +请参阅 [CI 构建步骤](.github/workflows/tests.yml) 以获取最新的依赖项。 + +安装完成后,在仓库根目录下运行以下命令: + +```shell +bazel build //:typesense-server +``` + +首次构建会花费一些时间,因为构建过程中会拉取并构建其他第三方库。 + +* * * + +© 2016-至今 Typesense Inc. \ No newline at end of file diff --git a/apps/typesense/README_en.md b/apps/typesense/README_en.md new file mode 100644 index 000000000..a1defb7e4 --- /dev/null +++ b/apps/typesense/README_en.md @@ -0,0 +1,410 @@ +

+ + + + Typesense + + +

+

+ Typesense is a fast, typo-tolerant search engine for building delightful search experiences. +

+ +

+ An Open Source Algolia Alternative &
+ An Easier-to-Use ElasticSearch Alternative +

+ +

+ + +
+ +

+

+ Website | + Documentation | + Roadmap | + Slack Community | + Community Threads | + Twitter +

+
+

+ Typesense Demo +

+ +✨ Here are a couple of **live demos** that show Typesense in action on large datasets: + +- Search a 32M songs dataset from MusicBrainz: [songs-search.typesense.org](https://songs-search.typesense.org/) +- Search a 28M books dataset from OpenLibrary: [books-search.typesense.org](https://books-search.typesense.org/) +- Search a 2M recipe dataset from RecipeNLG: [recipe-search.typesense.org](https://recipe-search.typesense.org/) +- Search 1M Git commit messages from the Linux Kernel: [linux-commits-search.typesense.org](https://linux-commits-search.typesense.org/) +- Spellchecker with type-ahead, with 333K English words: [spellcheck.typesense.org](https://spellcheck.typesense.org/) +- An E-Commerce Store Browsing experience: [ecommerce-store.typesense.org](https://ecommerce-store.typesense.org/) +- GeoSearch / Browsing experience: [airbnb-geosearch.typesense.org](https://airbnb-geosearch.typesense.org/) +- Search / Browse xkcd comics by topic: [xkcd-search.typesense.org](https://xkcd-search.typesense.org/) +- Semantic / Hybrid search on 300K HN comments: [hn-comments-search.typesense.org](https://hn-comments-search.typesense.org) + +🗣️ 🎥 If you prefer watching videos: + +- Here's one where we introduce Typesense and show a walk-through: https://youtu.be/F4mB0x_B1AE?t=144 +- Check out Typesense's recent mention during Google I/O Developer Keynote: https://youtu.be/qBkyU1TJKDg?t=2399 +- Here's one where one of our community members gives an overview of Typesense and shows you an end-to-end demo: https://www.youtube.com/watch?v=kwtHOkf7Jdg + +## Quick Links + +- [Features](#features) +- [Benchmarks](#benchmarks) +- [Roadmap](#roadmap) +- [Who's using this](#whos-using-this) +- [Install](#install) +- [Quick Start](#quick-start) +- [Step-by-step Walk-through](#step-by-step-walk-through) +- [API Documentation](#api-documentation) +- [API Clients](#api-clients) +- [Search UI Components](#search-ui-components) +- [FAQ](#faq) +- [Support](#support) +- [Contributing](#contributing) +- [Getting Latest Updates](#getting-latest-updates) +- [Build from Source](#build-from-source) + +## Features + +- **Typo Tolerance:** Handles typographical errors elegantly, out-of-the-box. +- **Simple and Delightful:** Simple to set-up, integrate with, operate and scale. +- **⚡ Blazing Fast:** Built in C++. Meticulously architected from the ground-up for low-latency (<50ms) instant searches. +- **Tunable Ranking:** Easy to tailor your search results to perfection. +- **Sorting:** Dynamically sort results based on a particular field at query time (helpful for features like "Sort by Price (asc)"). +- **Faceting & Filtering:** Drill down and refine results. +- **Grouping & Distinct:** Group similar results together to show more variety. +- **Federated Search:** Search across multiple collections (indices) in a single HTTP request. +- **Geo Search:** Search and sort by results around a latitude/longitude or within a bounding box. +- **Vector Search:** Index embeddings from your machine learning models in Typesense and do a nearest-neighbor search. Can be used to build similarity search, semantic search, visual search, recommendations, etc. +- **Semantic / Hybrid Search:** Automatically generate embeddings from within Typesense using built-in models like S-BERT, E-5, etc or use OpenAI, PaLM API, etc, for both queries and indexed data. This allows you to send JSON data into Typesense and build an out-of-the-box semantic search + keyword search experience. +- **Conversational Search (Built-in RAG):** Send questions to Typesense and have the response be a fully-formed sentence, based on the data you've indexed in Typesense. Think ChatGPT, but over your own data. +- **Natural Language Search:** LLM-powered intent detection & query understanding, that converts any free-form natural language phrases into structured filters, sorts and queries. +- **Image Search:** Search through images using text descriptions of their contents, or perform similarity searches, using the CLIP model. +- **Voice Search:** Capture and send query via voice recordings - Typesense will transcribe (via Whisper model) and provide search results. +- **Scoped API Keys:** Generate API keys that only allow access to certain records, for multi-tenant applications. +- **JOINs:** Connect one or more collections via common reference fields and join them during query time. This allows you to model SQL-like relationships elegantly. +- **Synonyms:** Define words as equivalents of each other, so searching for a word will also return results for the synonyms defined. +- **Curation & Merchandizing:** Boost particular records to a fixed position in the search results, to feature them. +- **Raft-based Clustering:** Setup a distributed cluster that is highly available. +- **Seamless Version Upgrades:** As new versions of Typesense come out, upgrading is as simple as swapping out the binary and restarting Typesense. +- **No Runtime Dependencies:** Typesense is a single binary that you can run locally or in production with a single command. + +**Don't see a feature on this list?** Search our issue tracker if someone has already requested it and add a comment to it explaining your use-case, or open a new issue if not. We prioritize our roadmap based on user feedback, so we'd love to hear from you. + +## Roadmap + +Here's Typesense's public roadmap: [https://typesense.link/roadmap](https://typesense.link/roadmap). + +The first column also explains how we prioritize features, how you can influence prioritization and our release cadence. + +## Benchmarks + +- A dataset containing **2.2 Million recipes** (recipe names and ingredients): + - Took up about 900MB of RAM when indexed in Typesense + - Took 3.6mins to index all 2.2M records + - On a server with 4vCPUs, Typesense was able to handle a concurrency of **104 concurrent search queries per second**, with an average search processing time of **11ms**. +- A dataset containing **28 Million books** (book titles, authors and categories): + - Took up about 14GB of RAM when indexed in Typesense + - Took 78mins to index all 28M records + - On a server with 4vCPUs, Typesense was able to handle a concurrency of **46 concurrent search queries per second**, with an average search processing time of **28ms**. +- With a dataset containing **3 Million products** (Amazon product data), Typesense was able to handle a throughput of **250 concurrent search queries per second** on an 8-vCPU 3-node Highly Available Typesense cluster. + +We'd love to benchmark with larger datasets, if we can find large ones in the public domain. If you have any suggestions for structured datasets that are open, please let us know by opening an issue. We'd also be delighted if you're able to share benchmarks from your own large datasets. Please send us a PR! + +## Who's using this? + +Typesense is used by a range of users across different domains and verticals. + +On Typesense Cloud we serve more than **10 BILLION** searches per month. Typesense's Docker images have been downloaded over 12M times. + +We've recently started documenting who's using it in our [Showcase](SHOWCASE.md). +If you'd like to be included in the list, please feel free to edit [SHOWCASE.md](SHOWCASE.md) and send us a PR. + +You'll also see a list of user logos on the [Typesense Cloud](https://cloud.typesense.org) home page. + +## Install + +**Option 1:** You can download the [binary packages](https://typesense.org/downloads) that we publish for +Linux (x86_64 & arm64) and Mac (x86_64). + +**Option 2:** You can also run Typesense from our [official Docker image](https://hub.docker.com/r/typesense/typesense). + +**Option 3:** Spin up a managed cluster with [Typesense Cloud](https://cloud.typesense.org): + +Deploy with Typesense Cloud + +## Quick Start + +Here's a quick example showcasing how you can create a collection, index a document and search it on Typesense. + +Let's begin by starting the Typesense server via Docker: + +``` +docker run -p 8108:8108 -v/tmp/data:/data typesense/typesense:29.0 --data-dir /data --api-key=Hu52dwsas2AdxdE +``` + +We have [API Clients](#api-clients) in a couple of languages, but let's use the Python client for this example. + +Install the Python client for Typesense: + +``` +pip install typesense +``` + +We can now initialize the client and create a `companies` collection: + +```python +import typesense + +client = typesense.Client({ + 'api_key': 'Hu52dwsas2AdxdE', + 'nodes': [{ + 'host': 'localhost', + 'port': '8108', + 'protocol': 'http' + }], + 'connection_timeout_seconds': 2 +}) + +create_response = client.collections.create({ + "name": "companies", + "fields": [ + {"name": "company_name", "type": "string" }, + {"name": "num_employees", "type": "int32" }, + {"name": "country", "type": "string", "facet": True } + ], + "default_sorting_field": "num_employees" +}) +``` + +Now, let's add a document to the collection we just created: + +```python +document = { + "id": "124", + "company_name": "Stark Industries", + "num_employees": 5215, + "country": "USA" +} + +client.collections['companies'].documents.create(document) +``` + +Finally, let's search for the document we just indexed: + +```python +search_parameters = { + 'q' : 'stork', + 'query_by' : 'company_name', + 'filter_by' : 'num_employees:>100', + 'sort_by' : 'num_employees:desc' +} + +client.collections['companies'].documents.search(search_parameters) +``` + +**Did you notice the typo in the query text?** No big deal. Typesense handles typographic errors out-of-the-box! + +## Step-by-step Walk-through + +A step-by-step walk-through is available on our website [here](https://typesense.org/guide). + +This will guide you through the process of starting up a Typesense server, indexing data in it and querying the data set. + +## API Documentation + +Here's our official API documentation, available on our website: [https://typesense.org/api](https://typesense.org/api). + +If you notice any issues with the documentation or walk-through, please let us know or send us a PR here: [https://github.com/typesense/typesense-website](https://github.com/typesense/typesense-website). + +## API Clients + +While you can definitely use CURL to interact with Typesense Server directly, we offer official API clients to simplify using Typesense from your language of choice. The API Clients come built-in with a smart retry strategy to ensure that API calls made via them are resilient, especially in an HA setup. + +- [JavaScript](https://github.com/typesense/typesense-js) +- [PHP](https://github.com/typesense/typesense-php) +- [Python](https://github.com/typesense/typesense-python) +- [Ruby](https://github.com/typesense/typesense-ruby) + +If we don't offer an API client in your language, you can still use any popular HTTP client library to access Typesense's APIs directly. + +Here are some community-contributed clients and integrations: + +- [Go](https://github.com/typesense/typesense-go) +- [.Net](https://github.com/DAXGRID/typesense-dotnet) +- [Java](https://github.com/typesense/typesense-java) +- [Rust](https://github.com/typesense/typesense-rust) +- [Dart](https://github.com/typesense/typesense-dart) +- [Perl](https://github.com/Ovid/Search-Typesense) +- [Swift](https://github.com/typesense/typesense-swift) +- [Clojure](https://github.com/runeanielsen/typesense-clj) +- [python orm client](https://github.com/RedSnail/typesense_orm) +- [PHP SEAL Adapter](https://github.com/schranz-search/seal-typesense-adapter) +- [Elixir](https://github.com/jaeyson/ex_typesense) + +We welcome community contributions to add more official client libraries and integrations. Please reach out to us at contact@typsense.org or open an issue on GitHub to collaborate with us on the architecture. 🙏 + +### Framework Integrations + +We also have the following framework integrations: + +- [Laravel](https://github.com/typesense/laravel-scout-typesense-engine) +- [Firebase](https://github.com/typesense/firestore-typesense-search) +- [Gatsby](https://www.gatsbyjs.com/plugins/gatsby-plugin-typesense/) +- [WordPress](https://wordpress.org/plugins/search-with-typesense/?ref=typesense) +- [WooCommerce](https://www.codemanas.com/downloads/typesense-search-for-woocommerce/?ref=typesense) +- [Symfony](https://github.com/acseo/TypesenseBundle) +- [InstantSearch](https://github.com/typesense/typesense-instantsearch-adapter) +- [DocSearch](https://typesense.org/docs/guide/docsearch.html) +- [Docusaurus](https://github.com/typesense/docusaurus-theme-search-typesense) +- [ToolJet](https://tooljet.com/?ref=typesense) +- [Plone CMS](https://pypi.org/project/zopyx.typesense/) +- [Craft CMS](https://plugins.craftcms.com/typesense) +- [SEAL](https://github.com/schranz-search/schranz-search) provides integrations of Typesense in Laravel, Symfony, Spiral, Yii and Laminas Mezzio PHP Framework + +### Postman Collection + +We have a community-maintained Postman Collection here: [https://github.com/typesense/postman](https://github.com/typesense/postman). + +[Postman](https://www.postman.com/downloads/) is an app that let's you perform HTTP requests by pointing and clicking, instead of having to type them out in the terminal. +The Postman Collection above gives you template requests that you can import into Postman, to quickly make API calls to Typesense. + +## Search UI Components + +You can use our [InstantSearch.js adapter](https://github.com/typesense/typesense-instantsearch-adapter) +to quickly build powerful search experiences, complete with filtering, sorting, pagination and more. + +Here's how: [https://typesense.org/docs/guide/search-ui-components.html](https://typesense.org/docs/guide/search-ui-components.html) + +## FAQ + +### How does this differ from Elasticsearch? + +Elasticsearch is a large piece of software, that takes non-trivial amount of effort to setup, administer, scale and fine-tune. +It offers you a few thousand configuration parameters to get to your ideal configuration. So it's better suited for large teams +who have the bandwidth to get it production-ready, regularly monitor it and scale it, especially when they have a need to store +billions of documents and petabytes of data (eg: logs). + +Typesense is built specifically for decreasing the "time to market" for a delightful search experience. It's a light-weight +yet powerful & scaleable alternative that focuses on Developer Happiness and Experience with a clean well-documented API, clear semantics +and smart defaults so it just works well out-of-the-box, without you having to turn many knobs. + +Elasticsearch also runs on the JVM, which by itself can be quite an effort to tune to run optimally. Typesense, on the other hand, +is a single light-weight self-contained native binary, so it's simple to setup and operate. + +See a side-by-side feature comparison [here](https://typesense.org/typesense-vs-algolia-vs-elasticsearch-vs-meilisearch/). + +### How does this differ from Algolia? + +Algolia is a proprietary, hosted, search-as-a-service product that works well, when cost is not an issue. From our experience, +fast growing sites and apps quickly run into search & indexing limits, accompanied by expensive plan upgrades as they scale. + +Typesense on the other hand is an open-source product that you can run on your own infrastructure or +use our managed SaaS offering - [Typesense Cloud](https://cloud.typesense.org). +The open source version is free to use (besides of course your own infra costs). +With Typesense Cloud we don't charge by records or search operations. Instead, you get a dedicated cluster +and you can throw as much data and traffic at it as it can handle. You only pay a fixed hourly cost & bandwidth charges +for it, depending on the configuration your choose, similar to most modern cloud platforms. + +From a product perspective, Typesense is closer in spirit to Algolia than Elasticsearch. +However, we've addressed some important limitations with Algolia: + +Algolia requires separate indices for each sort order, which counts towards your plan limits. Most of the index settings like +fields to search, fields to facet, fields to group by, ranking settings, etc +are defined upfront when the index is created vs being able to set them on the fly at query time. + +With Typesense, these settings can be configured at search time via query parameters which makes it very flexible +and unlocks new use cases. Typesense is also able to give you sorted results with a single index, vs having to create multiple. +This helps reduce memory consumption. + +Algolia offers the following features that Typesense does not have currently: personalization & server-based search analytics. For analytics, you can still instrument your search on the client-side and send search metrics to your web analytics tool of choice. + +We intend to bridge this gap in Typesense, but in the meantime, please let us know +if any of these are a show stopper for your use case by creating a feature request in our issue tracker. + +See a side-by-side feature comparison [here](https://typesense.org/typesense-vs-algolia-vs-elasticsearch-vs-meilisearch/). + +### Speed is great, but what about the memory footprint? + +A fresh Typesense server will consume about 30 MB of memory. As you start indexing documents, the memory use will +increase correspondingly. How much it increases depends on the number and type of fields you index. + +We've strived to keep the in-memory data structures lean. To give you a rough idea: when 1 million +Hacker News titles are indexed along with their points, Typesense consumes 165 MB of memory. The same size of that data +on disk in JSON format is 88 MB. If you have any numbers from your own datasets that we can add to this section, please send us a PR! + +### Why the GPL license? + +From our experience companies are generally concerned when **libraries** they use are GPL licensed, since library code is directly integrated into their code and will lead to derivative work and trigger GPL compliance. However, Typesense Server is **server software** and we expect users to typically run it as a separate daemon, and not integrate it with their own code. GPL covers and allows for this use case generously **(eg: Linux is GPL licensed)**. Now, AGPL is what makes server software accessed over a network result in derivative work and not GPL. And for that reason we’ve opted to not use AGPL for Typesense. + +Now, if someone makes modifications to Typesense server, GPL actually allows you to still keep the modifications to yourself as long as you don't distribute the modified code. So a company can for example modify Typesense server and run the modified code internally and still not have to open source their modifications, as long as they make the modified code available to everyone who has access to the modified software. + +Now, if someone makes modifications to Typesense server and distributes the modifications, that's where GPL kicks in. Given that we’ve published our work to the community, we'd like for others' modifications to also be made open to the community in the spirit of open source. **We use GPL for this purpose.** Other licenses would allow our open source work to be modified, made closed source and distributed, which we want to avoid with Typesense for the project’s long term sustainability. + +Here's more background on why GPL, as described by Discourse: https://meta.discourse.org/t/why-gnu-license/2531. Many of the points mentioned there resonate with us. + +Now, all of the above only apply to Typesense Server. Our client libraries are indeed meant to be integrated into our users’ code and so they use Apache license. + +So in summary, AGPL is what is usually problematic for server software and we’ve opted not to use it. We believe GPL for Typesense Server captures the essence of what we want for this open source project. GPL has a long history of successfully being used by popular open source projects. Our libraries are still Apache licensed. + +If you have specifics that prevent you from using Typesense due to a licensing issue, we're happy to explore this topic further with you. Please reach out to us. + +## Support + +👋 🌐 If you have general questions about Typesense, want to say hello or just follow along, we'd like to invite you to join our public [Slack Community](https://typesense.link/slack-community). + +If you run into any problems or issues, please create a GitHub issue and we'll try our best to help. + +We strive to provide good support through our issue trackers on GitHub. However, if you'd like to receive private & prioritized support with: + +- Guaranteed SLAs +- Phone / video calls to discuss your specific use case and get recommendations on best practices +- Private discussions over Slack +- Guidance around scaling best practices +- Prioritized feature requests + +We offer Paid Support options described [here](https://typesense.org/support/). + +## Contributing + +We are a lean team on a mission to democratize search and we'll take all the help we can get! If you'd like to get involved, here's information on where we could use your help: [Contributing.md](https://github.com/typesense/typesense/blob/master/CONTRIBUTING.md) + +## Getting Latest Updates + +If you'd like to get updates when we release new versions, click on the "Watch" button on the top and select "Releases only". GitHub will then send you notifications along with a changelog with each new release. + +We also post updates to our Twitter account about releases and additional topics related to Typesense. Follow us here: [@typesense](https://twitter.com/typesense). + +👋 🌐 We'll also post updates on our [Slack Community](https://typesense.link/slack-community). + +## Build from source + +We use [Bazel](https://bazel.build) to build Typesense. + +Typesense requires the following dependencies: + +* C++11 compatible compiler (GCC >= 4.9.0, Apple Clang >= 8.0, Clang >= 3.9.0) +* Snappy +* zlib +* OpenSSL (>=1.0.2) +* curl +* ICU + +Please refer to the [CI build steps](.github/workflows/tests.yml) for the latest set of dependencies. + +Once you've installed them, run the following from the root of the repo: + +```shell +bazel build //:typesense-server +``` + +The first build will take some time since other third-party libraries are pulled and built as part of the build process. + +--- +© 2016-present Typesense Inc. diff --git a/apps/typesense/data.yml b/apps/typesense/data.yml new file mode 100644 index 000000000..d4d2d12c5 --- /dev/null +++ b/apps/typesense/data.yml @@ -0,0 +1,19 @@ +name: Typesense +tags: + - 建站 +title: 快速、容错率高的搜索引擎,用于构建令人愉悦的搜索体验 +description: 快速、容错率高的搜索引擎,用于构建令人愉悦的搜索体验 +additionalProperties: + key: typesense + name: Typesense + tags: + - Website + shortDescZh: 快速、容错率高的搜索引擎,用于构建令人愉悦的搜索体验 + shortDescEn: Fast, typo tolerant, fuzzy search engine for building delightful search experiences + type: website + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://typesense.org/ + github: https://github.com/typesense/typesense + document: https://typesense.org/docs/ diff --git a/apps/typesense/logo.png b/apps/typesense/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..64e41565f310a40c1e472cbea0b05b910953446c GIT binary patch literal 12321 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