Compare commits

..

48 Commits

Author SHA1 Message Date
Vitor Alcantara Batista
154301b3ad
Brazilian Portuguese translation (#6809)
Co-authored-by: Vitor Alcantara Batista <vitor.alcantara@petrobras.com.br>
2025-10-29 23:36:35 +05:30
Nghia Doan
e7b7ac9e94
Merge pull request #6831 from janhq/feat/proactive_mode
feat: Proactive mode
2025-10-29 21:02:05 +07:00
Nguyen Ngoc Minh
e531eaa4ad
Merge pull request #6836 from janhq/chore/deprecate-webhook-discord
chore: deprecate webhook discord
2025-10-29 12:15:07 +07:00
Minh141120
23b03da714 chore: deprecate webhook discord 2025-10-29 11:48:32 +07:00
Vanalite
22be93807d Merge remote-tracking branch 'origin/dev' into feat/proactive_mode 2025-10-28 17:56:47 +07:00
Nguyen Ngoc Minh
653ecdb494
Merge pull request #6834 from janhq/chore/update-org-name
chore: update org name
2025-10-28 17:56:07 +07:00
Minh141120
15c426aefc chore: update org name 2025-10-28 17:26:27 +07:00
Vanalite
2fa153ac34 fix: Remove unused Proactive icon on chatInput
This icon doesn't do anything on chatInput but just an indicator when the proactive capability is activated. Safely remove since this can be indicated from the model dropdown
2025-10-28 17:04:31 +07:00
Dinh Long Nguyen
62bd91a1e1
fix: model should not include file attachment tools if not supported (#6833) 2025-10-28 16:58:18 +07:00
Vanalite
f7e0e790b6 feat: remove unnecessary TODO 2025-10-28 15:49:17 +07:00
hiento09
c854c54c0c
chore: update api domain to jan.ai (#6832) 2025-10-28 15:45:42 +07:00
Vanalite
a14872666a feat: Add tests for proactive mode 2025-10-28 12:19:00 +07:00
Vanalite
e9f469b623 feat: Proactively take screenshot and snapshot for every browser tool call 2025-10-28 11:48:55 +07:00
utenadev
5a016860aa
feat: Add Japanese translation (#6806)
This commit introduces Japanese as a supported language in the web application.

Key changes include:
- Addition of a new `ja` locale with 15 translated JSON resource files, making the application accessible to Japanese-speaking users.
- Update of the `LanguageSwitcher.tsx` component to include '日本語' in the language selection dropdown menu, allowing users to switch to the new language.
- The localization files were added by creating a new `ja` directory under `web-app/src/locales` and translating the content from the `en` directory.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-10-27 19:53:36 +05:30
Vanalite
c773abb688 feat: Adding proactive button as experimental feature 2025-10-27 18:18:23 +07:00
Akarshan Biswas
2561fcd78a
feat: support multimodal tool results and improve tool message handling (#6816)
* feat: support multimodal tool results and improve tool message handling

- Added a temporary `ToolResult` type that mirrors the structure returned by tools (text, image data, URLs, errors).
- Implemented `convertToolPartToApiContentPart` to translate each tool output part into the format expected by the OpenAI chat completion API.
- Updated `CompletionMessagesBuilder.addToolMessage` to accept a full `ToolResult` instead of a plain string and to:
  - Detect multimodal content (base64 images, image URLs) and build a structured `content` array.
  - Properly handle plain‑text results, tool execution errors, and unexpected formats with sensible fallbacks.
  - Cast the final content to `any` for the `tool` role as required by the API.
- Modified `postMessageProcessing` to pass the raw tool result (`result as any`) to `addToolMessage`, avoiding premature extraction of only the first text part.
- Refactored several formatting and type‑annotation sections:
  - Added multiline guard for empty user messages to insert a placeholder.
  - Split the image URL construction into a clearer multiline object.
  - Adjusted method signatures and added minor line‑breaks for readability.
- Included extensive comments explaining the new logic and edge‑case handling.

These changes enable the chat system to handle richer tool outputs (e.g., images, mixed content) and provide more robust error handling.

* Satisfy ts linter

* Make ts linter happy x2

* chore: update test message creation

---------

Co-authored-by: Faisal Amir <urmauur@gmail.com>
2025-10-24 20:15:15 +05:30
locnguyen1986
28ed5e2af2
Merge pull request #6817 from menloresearch/fix/conversation-saving
we use POST to update now
2025-10-24 14:51:57 +07:00
nguyen.ngo
4c5c8e6aed we use POST to update now 2025-10-24 13:09:35 +07:00
Dinh Long Nguyen
f07e43cfe0
fix: conversation items (#6815) 2025-10-24 09:01:31 +07:00
Dinh Long Nguyen
e46200868e
web: update model capabilites (#6814)
* update model capabilites

* refactor + remove projects
2025-10-24 01:31:21 +07:00
Akarshan Biswas
147cab94a8
fix: Escape dollar signs followed by numbers in Markdown (#6797)
This commit introduces a change to prevent **Markdown** rendering issues where a dollar sign followed by a number (like **`$1`**) is incorrectly interpreted as **LaTeX** by the rendering engine.

---

The `normalizeLatex` function in `RenderMarkdown.tsx` now explicitly escapes these sequences (e.g., **`$1`** becomes **`\$1`**), ensuring they are displayed literally instead of being processed as mathematical expressions. This improves the fidelity of text that might contain currency or similar numerical notations.
2025-10-16 12:15:24 +05:30
Nguyen Ngoc Minh
2fb956ccaf
Merge pull request #6798 from menloresearch/docs/changelog-v0.7.2
docs: update changelog for Jan v0.7.2
2025-10-16 13:26:36 +07:00
Minh141120
4dee0a4ba1 docs: update changelog for Jan v0.7.2 2025-10-16 13:18:20 +07:00
Nguyen Ngoc Minh
418a48ab39
Merge pull request #6790 from menloresearch/chore/happy-dom-update
chore: update happy dom deps version
2025-10-15 02:53:24 -07:00
Minh141120
9bc56f6e30 chore: remove redudant deps in yarn lock file 2025-10-15 15:15:38 +07:00
Minh141120
f0ca9cce35 chore: update happy-dom version 2025-10-15 14:43:58 +07:00
Faisal Amir
746dbc632b
Merge pull request #6766 from menloresearch/feat/file-attachment
feat: file attachment
2025-10-15 11:01:40 +07:00
Faisal Amir
462b05e612 chore: fix conflict revert analytic 2025-10-15 10:35:36 +07:00
dinhlongviolin1
946b347f44 fix: lint 2025-10-15 00:21:10 +07:00
Dinh Long Nguyen
b23e88f078
Merge branch 'dev' into feat/file-attachment 2025-10-14 14:06:17 +07:00
Trang Le
476fdd6040
feat: Enable new prompt input while waiting for an answer (#6676)
* enable new prompt input while waiting for an answer

* correct spelling of handleSendMessage function

* remove test for disabling input while streaming content
2025-10-14 14:04:52 +07:00
Dinh Long Nguyen
fa8b3664cb
Merge branch 'dev' into feat/file-attachment 2025-10-14 14:00:10 +07:00
Nguyen Ngoc Minh
8b687619b2
Merge pull request #6783 from menloresearch/docs/update-jan-web-url
docs: update jan server url
2025-10-13 23:58:49 -07:00
Minh141120
176ad07f1d docs: update jan server url 2025-10-14 13:54:43 +07:00
Faisal Amir
7b5060c9be
Merge pull request #6774 from menloresearch/chore/disable-posthog-event
chore: revert track event posthog
2025-10-13 10:13:45 +07:00
Faisal Amir
584daa9682 chore: revert track event posthog 2025-10-11 21:46:15 +07:00
Akarshan
31f9501d8e
feat: Optimize state updates in server and model checks
- Added shallow equality guard for `connectedServers` state to prevent redundant updates when the fetched server list hasn't changed.
- Updated error handling for server fetch to only clear the state when it actually contains data.
- Introduced `newHasActiveModels` variable and conditional updater for `hasActiveModels` to avoid unnecessary state changes.
- Adjusted error handling for active model fetch to only set `hasActiveModels` to `false` when the current state differs.

These changes reduce needless re‑renders and improve component performance.
2025-10-10 20:25:17 +05:30
Roushan Kumar Singh
c096929d8b
fix(amd/linux): show dedicated VRAM on device list (override Vulkan UMA) (#6533) 2025-10-09 23:33:07 +07:00
Dinh Long Nguyen
45d57dd34d
Update web-app/src/services/uploads/default.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-09 04:53:19 +07:00
Dinh Long Nguyen
f4066e6e5a
Update web-app/src/lib/fileMetadata.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-09 04:50:31 +07:00
Dinh Long Nguyen
a2fbce698f fix thread scrolling 2025-10-09 04:41:18 +07:00
Dinh Long Nguyen
fc784620e0 fix tests 2025-10-09 04:28:08 +07:00
Dinh Long Nguyen
340042682a ui ux enhancement 2025-10-09 03:48:51 +07:00
Dinh Long Nguyen
6dd2d2d6c1
Merge branch 'dev' into feat/file-attachment 2025-10-09 02:21:22 +07:00
Akarshan
7762cea10a
feat: Distinguish and preserve embedding model sessions
This commit introduces a new field, `is_embedding`, to the `SessionInfo` structure to clearly mark sessions running dedicated embedding models.

Key changes:
- Adds `is_embedding` to the `SessionInfo` interface in `AIEngine.ts` and the Rust backend.
- Updates the `loadLlamaModel` command signatures to pass this new flag.
- Modifies the llama.cpp extension's **auto-unload logic** to explicitly **filter out** and **not unload** any currently loaded embedding models when a new text generation model is loaded. This is a critical performance fix to prevent the embedding model (e.g., used for RAG) from being repeatedly reloaded.

Also includes minor code style cleanup/reformatting in `jan-provider-web/provider.ts` for improved readability.
2025-10-08 20:03:35 +05:30
Dinh Long Nguyen
ff93dc3c5c Merge branch 'dev' into feat/file-attachment 2025-10-08 16:34:45 +07:00
Dinh Long Nguyen
510c4a5188 working attachments 2025-10-08 16:08:40 +07:00
Dinh Long Nguyen
a72c74dbf9 initial layout 2025-10-07 10:36:45 +07:00
307 changed files with 10546 additions and 493956 deletions

View File

@ -1,5 +1,5 @@
blank_issues_enabled: true blank_issues_enabled: true
contact_links: contact_links:
- name: Jan Discussions - name: Jan Discussions
url: https://github.com/orgs/menloresearch/discussions/categories/q-a url: https://github.com/orgs/janhq/discussions/categories/q-a
about: Get help, discuss features & roadmap, and share your projects about: Get help, discuss features & roadmap, and share your projects

View File

@ -12,7 +12,7 @@ jobs:
build-and-preview: build-and-preview:
runs-on: [ubuntu-24-04-docker] runs-on: [ubuntu-24-04-docker]
env: env:
JAN_API_BASE: "https://api-dev.menlo.ai/v1" MENLO_PLATFORM_BASE_URL: "https://api-dev.jan.ai/v1"
permissions: permissions:
pull-requests: write pull-requests: write
contents: write contents: write
@ -52,7 +52,7 @@ jobs:
- name: Build docker image - name: Build docker image
run: | run: |
docker build --build-arg JAN_API_BASE=${{ env.JAN_API_BASE }} -t ${{ steps.vars.outputs.FULL_IMAGE }} . docker build --build-arg MENLO_PLATFORM_BASE_URL=${{ env.MENLO_PLATFORM_BASE_URL }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
- name: Push docker image - name: Push docker image
if: github.event_name == 'push' if: github.event_name == 'push'

View File

@ -13,7 +13,7 @@ jobs:
deployments: write deployments: write
pull-requests: write pull-requests: write
env: env:
JAN_API_BASE: "https://api.menlo.ai/v1" MENLO_PLATFORM_BASE_URL: "https://api.jan.ai/v1"
GA_MEASUREMENT_ID: "G-YK53MX8M8M" GA_MEASUREMENT_ID: "G-YK53MX8M8M"
CLOUDFLARE_PROJECT_NAME: "jan-server-web" CLOUDFLARE_PROJECT_NAME: "jan-server-web"
steps: steps:
@ -43,7 +43,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: make config-yarn && yarn install && yarn build:core && make build-web-app run: make config-yarn && yarn install && yarn build:core && make build-web-app
env: env:
JAN_API_BASE: ${{ env.JAN_API_BASE }} MENLO_PLATFORM_BASE_URL: ${{ env.MENLO_PLATFORM_BASE_URL }}
GA_MEASUREMENT_ID: ${{ env.GA_MEASUREMENT_ID }} GA_MEASUREMENT_ID: ${{ env.GA_MEASUREMENT_ID }}
- name: Publish to Cloudflare Pages Production - name: Publish to Cloudflare Pages Production

View File

@ -12,7 +12,7 @@ jobs:
build-and-preview: build-and-preview:
runs-on: [ubuntu-24-04-docker] runs-on: [ubuntu-24-04-docker]
env: env:
JAN_API_BASE: "https://api-stag.menlo.ai/v1" MENLO_PLATFORM_BASE_URL: "https://api-stag.jan.ai/v1"
permissions: permissions:
pull-requests: write pull-requests: write
contents: write contents: write
@ -52,7 +52,7 @@ jobs:
- name: Build docker image - name: Build docker image
run: | run: |
docker build --build-arg JAN_API_BASE=${{ env.JAN_API_BASE }} -t ${{ steps.vars.outputs.FULL_IMAGE }} . docker build --build-arg MENLO_PLATFORM_BASE_URL=${{ env.MENLO_PLATFORM_BASE_URL }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
- name: Push docker image - name: Push docker image
if: github.event_name == 'push' if: github.event_name == 'push'

View File

@ -168,62 +168,62 @@ jobs:
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
AWS_EC2_METADATA_DISABLED: 'true' AWS_EC2_METADATA_DISABLED: 'true'
noti-discord-nightly-and-update-url-readme: # noti-discord-nightly-and-update-url-readme:
needs: # needs:
[ # [
build-macos, # build-macos,
build-windows-x64, # build-windows-x64,
build-linux-x64, # build-linux-x64,
get-update-version, # get-update-version,
set-public-provider, # set-public-provider,
sync-temp-to-latest, # sync-temp-to-latest,
] # ]
secrets: inherit # secrets: inherit
if: github.event_name == 'schedule' # if: github.event_name == 'schedule'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml # uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with: # with:
ref: refs/heads/dev # ref: refs/heads/dev
build_reason: Nightly # build_reason: Nightly
push_to_branch: dev # push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }} # new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-pre-release-and-update-url-readme: # noti-discord-pre-release-and-update-url-readme:
needs: # needs:
[ # [
build-macos, # build-macos,
build-windows-x64, # build-windows-x64,
build-linux-x64, # build-linux-x64,
get-update-version, # get-update-version,
set-public-provider, # set-public-provider,
sync-temp-to-latest, # sync-temp-to-latest,
] # ]
secrets: inherit # secrets: inherit
if: github.event_name == 'push' # if: github.event_name == 'push'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml # uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with: # with:
ref: refs/heads/dev # ref: refs/heads/dev
build_reason: Pre-release # build_reason: Pre-release
push_to_branch: dev # push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }} # new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-manual-and-update-url-readme: # noti-discord-manual-and-update-url-readme:
needs: # needs:
[ # [
build-macos, # build-macos,
build-windows-x64, # build-windows-x64,
build-linux-x64, # build-linux-x64,
get-update-version, # get-update-version,
set-public-provider, # set-public-provider,
sync-temp-to-latest, # sync-temp-to-latest,
] # ]
secrets: inherit # secrets: inherit
if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3' # if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml # uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with: # with:
ref: refs/heads/dev # ref: refs/heads/dev
build_reason: Manual # build_reason: Manual
push_to_branch: dev # push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }} # new_version: ${{ needs.get-update-version.outputs.new_version }}
comment-pr-build-url: comment-pr-build-url:
needs: needs:

View File

@ -82,11 +82,11 @@ jobs:
VERSION=${{ needs.get-update-version.outputs.new_version }} VERSION=${{ needs.get-update-version.outputs.new_version }}
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}" LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}"
LINUX_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" LINUX_URL="https://github.com/janhq/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}"
WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}"
WINDOWS_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-windows-x64.outputs.FILE_NAME }}" WINDOWS_URL="https://github.com/janhq/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-windows-x64.outputs.FILE_NAME }}"
DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}" DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}"
DARWIN_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-macos.outputs.TAR_NAME }}" DARWIN_URL="https://github.com/janhq/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-macos.outputs.TAR_NAME }}"
jq --arg version "$VERSION" \ jq --arg version "$VERSION" \
--arg pub_date "$PUB_DATE" \ --arg pub_date "$PUB_DATE" \

View File

@ -29,7 +29,7 @@ jobs:
local max_retries=3 local max_retries=3
local tag local tag
while [ $retries -lt $max_retries ]; do while [ $retries -lt $max_retries ]; do
tag=$(curl -s https://api.github.com/repos/menloresearch/jan/releases/latest | jq -r .tag_name) tag=$(curl -s https://api.github.com/repos/janhq/jan/releases/latest | jq -r .tag_name)
if [ -n "$tag" ] && [ "$tag" != "null" ]; then if [ -n "$tag" ] && [ "$tag" != "null" ]; then
echo $tag echo $tag
return return

View File

@ -50,6 +50,6 @@ jobs:
- macOS Universal: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_universal.dmg - macOS Universal: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_universal.dmg
- Linux Deb: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.deb - Linux Deb: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.deb
- Linux AppImage: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.AppImage - Linux AppImage: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.AppImage
- Github action run: https://github.com/menloresearch/jan/actions/runs/{{ GITHUB_RUN_ID }} - Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}
env: env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}

View File

@ -143,7 +143,7 @@ jan/
**Option 1: The Easy Way (Make)** **Option 1: The Easy Way (Make)**
```bash ```bash
git clone https://github.com/menloresearch/jan git clone https://github.com/janhq/jan
cd jan cd jan
make dev make dev
``` ```
@ -152,8 +152,8 @@ make dev
### Reporting Bugs ### Reporting Bugs
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/menloresearch/jan/issues) - **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/janhq/jan/issues)
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/menloresearch/jan/issues/new) - If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/janhq/jan/issues/new)
- Include your system specs and error logs - it helps a ton - Include your system specs and error logs - it helps a ton
### Suggesting Enhancements ### Suggesting Enhancements

View File

@ -1,8 +1,8 @@
# Stage 1: Build stage with Node.js and Yarn v4 # Stage 1: Build stage with Node.js and Yarn v4
FROM node:20-alpine AS builder FROM node:20-alpine AS builder
ARG JAN_API_BASE=https://api-dev.jan.ai/v1 ARG MENLO_PLATFORM_BASE_URL=https://api-dev.menlo.ai/v1
ENV JAN_API_BASE=$JAN_API_BASE ENV MENLO_PLATFORM_BASE_URL=$MENLO_PLATFORM_BASE_URL
# Install build dependencies # Install build dependencies
RUN apk add --no-cache \ RUN apk add --no-cache \

View File

@ -4,10 +4,10 @@
<p align="center"> <p align="center">
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/menloresearch/jan"/> <img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/janhq/jan"/>
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/menloresearch/jan"/> <img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/janhq/jan"/>
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/menloresearch/jan"/> <img alt="Github Contributors" src="https://img.shields.io/github/contributors/janhq/jan"/>
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/menloresearch/jan"/> <img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/janhq/jan"/>
<img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/> <img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/>
</p> </p>
@ -15,7 +15,7 @@
<a href="https://www.jan.ai/docs/desktop">Getting Started</a> <a href="https://www.jan.ai/docs/desktop">Getting Started</a>
- <a href="https://discord.gg/Exe46xPMbK">Community</a> - <a href="https://discord.gg/Exe46xPMbK">Community</a>
- <a href="https://jan.ai/changelog">Changelog</a> - <a href="https://jan.ai/changelog">Changelog</a>
- <a href="https://github.com/menloresearch/jan/issues">Bug reports</a> - <a href="https://github.com/janhq/jan/issues">Bug reports</a>
</p> </p>
Jan is bringing the best of open-source AI in an easy-to-use product. Download and run LLMs with **full control** and **privacy**. Jan is bringing the best of open-source AI in an easy-to-use product. Download and run LLMs with **full control** and **privacy**.
@ -48,7 +48,7 @@ The easiest way to get started is by downloading one of the following versions f
</table> </table>
Download from [jan.ai](https://jan.ai/) or [GitHub Releases](https://github.com/menloresearch/jan/releases). Download from [jan.ai](https://jan.ai/) or [GitHub Releases](https://github.com/janhq/jan/releases).
## Features ## Features
@ -73,7 +73,7 @@ For those who enjoy the scenic route:
### Run with Make ### Run with Make
```bash ```bash
git clone https://github.com/menloresearch/jan git clone https://github.com/janhq/jan
cd jan cd jan
make dev make dev
``` ```
@ -128,7 +128,7 @@ Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full spiel
## Contact ## Contact
- **Bugs**: [GitHub Issues](https://github.com/menloresearch/jan/issues) - **Bugs**: [GitHub Issues](https://github.com/janhq/jan/issues)
- **Business**: hello@jan.ai - **Business**: hello@jan.ai
- **Jobs**: hr@jan.ai - **Jobs**: hr@jan.ai
- **General Discussion**: [Discord](https://discord.gg/FTk2MvZwJH) - **General Discussion**: [Discord](https://discord.gg/FTk2MvZwJH)

View File

@ -1,7 +1,7 @@
# Core dependencies # Core dependencies
cua-computer[all]~=0.3.5 cua-computer[all]~=0.3.5
cua-agent[all]~=0.3.0 cua-agent[all]~=0.3.0
cua-agent @ git+https://github.com/menloresearch/cua.git@compute-agent-0.3.0-patch#subdirectory=libs/python/agent cua-agent @ git+https://github.com/janhq/cua.git@compute-agent-0.3.0-patch#subdirectory=libs/python/agent
# ReportPortal integration # ReportPortal integration
reportportal-client~=5.6.5 reportportal-client~=5.6.5

View File

@ -1,46 +0,0 @@
[workspace]
resolver = "2"
members = [
"download-cef",
"get-latest",
"update-bindings",
"export-cef-dir",
"sys",
"cef",
"app",
]
[workspace.package]
version = "140.3.1+140.1.14"
edition = "2021"
license = "Apache-2.0 OR MIT"
authors = []
repository = "https://github.com/menloresearch/jan"
[workspace.dependencies]
cef = { path = "cef" }
cef-dll-sys = { version = "140.3.1", path = "sys" }
download-cef = { version = "2.2", path = "download-cef" }
anyhow = "1"
bindgen = "0.72"
clap = { version = "4", features = ["derive"] }
cmake = "0.1.52"
convert_case = "0.8"
git-cliff = "2"
git-cliff-core = "2"
plist = "1"
proc-macro2 = "1"
quote = "1"
regex = "1"
semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
syn = { version = "2", features = ["full"] }
thiserror = "2"
toml_edit = "0.23"
[workspace.dependencies.windows-sys]
version = "0.61"
features = ["Win32_System_Environment", "Win32_System_LibraryLoader"]

View File

@ -1,74 +0,0 @@
# cef-rs
Use CEF in Rust.
## Supported Targets
| Target | Linux | macOS | Windows |
| ------ | ----- | ----- | ------- |
| x86_64 | ✅ | ✅ | ✅ |
| ARM64 | ✅ | ✅ | ✅ |
## Usage
### Install Shared CEF Binaries
This step is optional, but it will make all other builds of the `cef` crate much faster. If you don't do this, the `cef-dll-sys` crate `build.rs` script will download and extract the same files under its `OUT_DIR` directory. You should repeat this step each time you upgrade to a new version of the `cef` crate.
#### Linux or macOS:
```sh
cargo run -p export-cef-dir -- --force $HOME/.local/share/cef
```
#### Windows (using PowerShell)
```pwsh
cargo run -p export-cef-dir -- --force $env:USERPROFILE/.local/share/cef
```
### Set Environment Variables
#### Linux
```sh
export CEF_PATH="$HOME/.local/share/cef"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CEF_PATH"
```
#### macOS
```sh
export CEF_PATH="$HOME/.local/share/cef"
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$CEF_PATH:$CEF_PATH/Chromium Embedded Framework.framework/Libraries"
```
#### Windows (using PowerShell)
```pwsh
$env:CEF_PATH="$env:USERPROFILE/.local/share/cef"
$env:PATH="$env:PATH;$env:CEF_PATH"
```
### Run the `app`
#### Linux
```sh
cargo run --bin app
```
#### macOS
```sh
cargo run --bin bundle_app
open target/debug/app.app
```
#### Windows (using PowerShell)
```pwsh
cp ./app/win/jan.exe.manifest ./target/debug/
cargo run --bin app
```

View File

@ -1,38 +0,0 @@
[package]
name = "app"
edition = "2024"
publish = false
[[bin]]
name = "app"
[[bin]]
name = "app_helper"
path = "src/mac/helper.rs"
[[bin]]
name = "bundle_app"
path = "src/mac/bundle_app.rs"
[features]
default = ["sandbox"]
sandbox = ["cef/sandbox"]
[dependencies]
cef.workspace = true
cef-dll-sys.workspace = true
parking_lot = "0.12"
once_cell = "1.19"
tokio = { version = "1", features = ["time"] }
egui = "0.29"
egui-wgpu = "0.29"
egui-winit = "0.29"
wgpu = "22"
winit = { version = "0.30", features = [] }
pollster = "0.4"
env_logger = "0.11"
[target.'cfg(target_os = "macos")'.dependencies]
plist.workspace = true
serde.workspace = true

View File

@ -1,182 +0,0 @@
#[cfg(target_os = "macos")]
mod mac {
use serde::Serialize;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[derive(Serialize)]
struct InfoPlist {
#[serde(rename = "CFBundleDevelopmentRegion")]
cf_bundle_development_region: String,
#[serde(rename = "CFBundleDisplayName")]
cf_bundle_display_name: String,
#[serde(rename = "CFBundleExecutable")]
cf_bundle_executable: String,
#[serde(rename = "CFBundleIdentifier")]
cf_bundle_identifier: String,
#[serde(rename = "CFBundleInfoDictionaryVersion")]
cf_bundle_info_dictionary_version: String,
#[serde(rename = "CFBundleName")]
cf_bundle_name: String,
#[serde(rename = "CFBundlePackageType")]
cf_bundle_package_type: String,
#[serde(rename = "CFBundleSignature")]
cf_bundle_signature: String,
#[serde(rename = "CFBundleVersion")]
cf_bundle_version: String,
#[serde(rename = "CFBundleShortVersionString")]
cf_bundle_short_version_string: String,
#[serde(rename = "LSEnvironment")]
ls_environment: HashMap<String, String>,
#[serde(rename = "LSFileQuarantineEnabled")]
ls_file_quarantine_enabled: bool,
#[serde(rename = "LSMinimumSystemVersion")]
ls_minimum_system_version: String,
#[serde(rename = "LSUIElement")]
ls_ui_element: Option<String>,
#[serde(rename = "NSBluetoothAlwaysUsageDescription")]
ns_bluetooth_always_usage_description: String,
#[serde(rename = "NSSupportsAutomaticGraphicsSwitching")]
ns_supports_automatic_graphics_switching: bool,
#[serde(rename = "NSWebBrowserPublicKeyCredentialUsageDescription")]
ns_web_browser_publickey_credential_usage_description: String,
#[serde(rename = "NSCameraUsageDescription")]
ns_camera_usage_description: String,
#[serde(rename = "NSMicrophoneUsageDescription")]
ns_microphone_usage_description: String,
}
const EXEC_PATH: &str = "Contents/MacOS";
const FRAMEWORKS_PATH: &str = "Contents/Frameworks";
const RESOURCES_PATH: &str = "Contents/Resources";
const FRAMEWORK: &str = "Chromium Embedded Framework.framework";
const HELPERS: &[&str] = &[
"app Helper (GPU)",
"app Helper (Renderer)",
"app Helper (Plugin)",
"app Helper (Alerts)",
"app Helper",
];
fn create_app_layout(app_path: &Path) -> PathBuf {
[EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH]
.iter()
.for_each(|p| fs::create_dir_all(app_path.join(p)).unwrap());
app_path.join("Contents")
}
fn create_app(app_path: &Path, exec_name: &str, bin: &Path, is_helper: bool) -> PathBuf {
let app_path = app_path.join(exec_name).with_extension("app");
let contents_path = create_app_layout(&app_path);
create_info_plist(&contents_path, exec_name, is_helper).unwrap();
fs::copy(bin, app_path.join(EXEC_PATH).join(exec_name)).unwrap();
app_path
}
// See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-macos
fn bundle(app_path: &Path) {
let example_path = PathBuf::from(app_path);
let main_app_path = create_app(
app_path,
"app",
&example_path.join("app"),
false,
);
let cef_path = cef_dll_sys::get_cef_dir().unwrap();
let to = main_app_path.join(FRAMEWORKS_PATH).join(FRAMEWORK);
if to.exists() {
fs::remove_dir_all(&to).unwrap();
}
copy_directory(&cef_path.join(FRAMEWORK), &to);
HELPERS.iter().for_each(|helper| {
create_app(
&main_app_path.join(FRAMEWORKS_PATH),
helper,
&example_path.join("app_helper"),
true,
);
});
}
fn create_info_plist(
contents_path: &Path,
exec_name: &str,
is_helper: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let info_plist = InfoPlist {
cf_bundle_development_region: "en".to_string(),
cf_bundle_display_name: exec_name.to_string(),
cf_bundle_executable: exec_name.to_string(),
cf_bundle_identifier: "app.jan.ai.helper".to_string(),
cf_bundle_info_dictionary_version: "6.0".to_string(),
cf_bundle_name: "cef-rs".to_string(),
cf_bundle_package_type: "APPL".to_string(),
cf_bundle_signature: "????".to_string(),
cf_bundle_version: "1.0.0".to_string(),
cf_bundle_short_version_string: "1.0".to_string(),
ls_environment: [("MallocNanoZone".to_string(), "0".to_string())]
.iter()
.cloned()
.collect(),
ls_file_quarantine_enabled: true,
ls_minimum_system_version: "11.0".to_string(),
ls_ui_element: if is_helper {
Some("1".to_string())
} else {
None
},
ns_bluetooth_always_usage_description: exec_name.to_string(),
ns_supports_automatic_graphics_switching: true,
ns_web_browser_publickey_credential_usage_description: exec_name.to_string(),
ns_camera_usage_description: exec_name.to_string(),
ns_microphone_usage_description: exec_name.to_string(),
};
plist::to_file_xml(contents_path.join("Info.plist"), &info_plist)?;
Ok(())
}
fn copy_directory(src: &Path, dst: &Path) {
fs::create_dir_all(dst).unwrap();
for entry in fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let dst_path = dst.join(entry.file_name());
if entry.file_type().unwrap().is_dir() {
copy_directory(&entry.path(), &dst_path);
} else {
fs::copy(&entry.path(), &dst_path).unwrap();
}
}
}
fn run_command(args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
let status = Command::new("cargo")
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()?;
if !status.success() {
std::process::exit(1);
}
Ok(())
}
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let app_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../target/debug");
run_command(&["build", "--bin", "app"])?;
run_command(&["build", "--bin", "app_helper"])?;
bundle(&app_path);
Ok(())
}
}
#[cfg(target_os = "macos")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
mac::main()
}
#[cfg(not(target_os = "macos"))]
fn main() {}

View File

@ -1,25 +0,0 @@
use cef::{args::Args, *};
fn main() {
let args = Args::new();
#[cfg(all(target_os = "macos", feature = "sandbox"))]
let _sandbox = {
let mut sandbox = cef::sandbox::Sandbox::new();
sandbox.initialize(args.as_main_args());
sandbox
};
#[cfg(target_os = "macos")]
let _loader = {
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true);
assert!(loader.load());
loader
};
execute_process(
Some(args.as_main_args()),
None::<&mut App>,
std::ptr::null_mut(),
);
}

View File

@ -1,782 +0,0 @@
use cef::{args::Args, rc::*, *};
use parking_lot::RwLock;
use std::sync::Arc;
use std::time::{Duration, Instant};
use winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
window::{Window, WindowAttributes, WindowId},
};
// Shared state between CEF and rendering
static TEXTURE_BUFFER: once_cell::sync::Lazy<Arc<RwLock<Vec<u8>>>> =
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(Vec::new())));
static TEXTURE_SIZE: once_cell::sync::Lazy<Arc<RwLock<(u32, u32)>>> =
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new((1024, 768))));
static CEF_INITIALIZED: once_cell::sync::Lazy<Arc<RwLock<bool>>> =
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(false)));
static BROWSER_INSTANCE: once_cell::sync::Lazy<Arc<RwLock<Option<Browser>>>> =
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(None)));
// CEF App implementation
struct DemoApp {
object: *mut RcImpl<cef_dll_sys::_cef_app_t, Self>,
}
impl DemoApp {
fn new_app() -> App {
App::new(Self {
object: std::ptr::null_mut(),
})
}
}
impl WrapApp for DemoApp {
fn wrap_rc(&mut self, object: *mut RcImpl<cef_dll_sys::_cef_app_t, Self>) {
self.object = object;
}
}
impl Clone for DemoApp {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
}
}
}
impl Rc for DemoApp {
fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl ImplApp for DemoApp {
fn get_raw(&self) -> *mut cef_dll_sys::_cef_app_t {
self.object.cast()
}
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
Some(DemoBrowserProcessHandler::new_browser_process_handler())
}
}
// Browser Process Handler
struct DemoBrowserProcessHandler {
object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>,
}
impl DemoBrowserProcessHandler {
fn new_browser_process_handler() -> BrowserProcessHandler {
BrowserProcessHandler::new(Self {
object: std::ptr::null_mut(),
})
}
}
impl Rc for DemoBrowserProcessHandler {
fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl WrapBrowserProcessHandler for DemoBrowserProcessHandler {
fn wrap_rc(
&mut self,
object: *mut RcImpl<cef_dll_sys::_cef_browser_process_handler_t, Self>,
) {
self.object = object;
}
}
impl Clone for DemoBrowserProcessHandler {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
}
}
}
impl ImplBrowserProcessHandler for DemoBrowserProcessHandler {
fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t {
self.object.cast()
}
fn on_context_initialized(&self) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
println!("CEF context initialized");
*CEF_INITIALIZED.write() = true;
let mut client = DemoClient::new_client();
let url = CefString::from("https://www.google.com");
let window_info = WindowInfo {
windowless_rendering_enabled: 1,
..Default::default()
};
let settings = BrowserSettings::default();
let _browser = browser_host_create_browser(
Some(&window_info),
Some(&mut client),
Some(&url),
Some(&settings),
Option::<&mut DictionaryValue>::None,
Option::<&mut RequestContext>::None,
);
}));
}
}
// Client
struct DemoClient {
object: *mut RcImpl<cef_dll_sys::_cef_client_t, Self>,
}
impl DemoClient {
fn new_client() -> Client {
Client::new(Self {
object: std::ptr::null_mut(),
})
}
}
impl WrapClient for DemoClient {
fn wrap_rc(&mut self, object: *mut RcImpl<cef_dll_sys::_cef_client_t, Self>) {
self.object = object;
}
}
impl Clone for DemoClient {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
}
}
}
impl Rc for DemoClient {
fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl ImplClient for DemoClient {
fn get_raw(&self) -> *mut cef_dll_sys::_cef_client_t {
self.object.cast()
}
fn render_handler(&self) -> Option<RenderHandler> {
Some(DemoRenderHandler::new_render_handler())
}
fn life_span_handler(&self) -> Option<LifeSpanHandler> {
Some(DemoLifeSpanHandler::new_life_span_handler())
}
}
// LifeSpan Handler to capture browser instance
struct DemoLifeSpanHandler {
object: *mut RcImpl<cef_dll_sys::_cef_life_span_handler_t, Self>,
}
impl DemoLifeSpanHandler {
fn new_life_span_handler() -> LifeSpanHandler {
LifeSpanHandler::new(Self {
object: std::ptr::null_mut(),
})
}
}
impl WrapLifeSpanHandler for DemoLifeSpanHandler {
fn wrap_rc(&mut self, object: *mut RcImpl<cef_dll_sys::_cef_life_span_handler_t, Self>) {
self.object = object;
}
}
impl Clone for DemoLifeSpanHandler {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
}
}
}
impl Rc for DemoLifeSpanHandler {
fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl ImplLifeSpanHandler for DemoLifeSpanHandler {
fn get_raw(&self) -> *mut cef_dll_sys::_cef_life_span_handler_t {
self.object.cast()
}
fn on_after_created(&self, browser: Option<&mut Browser>) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if let Some(browser) = browser {
// Store the browser instance for later use
*BROWSER_INSTANCE.write() = Some(browser.clone());
println!("Browser created and stored!");
}
}));
}
}
// Render Handler
struct DemoRenderHandler {
object: *mut RcImpl<cef_dll_sys::_cef_render_handler_t, Self>,
}
impl DemoRenderHandler {
fn new_render_handler() -> RenderHandler {
RenderHandler::new(Self {
object: std::ptr::null_mut(),
})
}
}
impl WrapRenderHandler for DemoRenderHandler {
fn wrap_rc(&mut self, object: *mut RcImpl<cef_dll_sys::_cef_render_handler_t, Self>) {
self.object = object;
}
}
impl Clone for DemoRenderHandler {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
}
Self {
object: self.object,
}
}
}
impl Rc for DemoRenderHandler {
fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
impl ImplRenderHandler for DemoRenderHandler {
fn get_raw(&self) -> *mut cef_dll_sys::_cef_render_handler_t {
self.object.cast()
}
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if let Some(rect) = rect {
let size = TEXTURE_SIZE.read();
rect.x = 0;
rect.y = 0;
rect.width = size.0 as i32;
rect.height = size.1 as i32;
}
}));
}
fn on_paint(
&self,
_browser: Option<&mut Browser>,
_type_: PaintElementType,
_dirty_rects_count: usize,
_dirty_rects: Option<&Rect>,
buffer: *const u8,
width: ::std::os::raw::c_int,
height: ::std::os::raw::c_int,
) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if buffer.is_null() || width <= 0 || height <= 0 {
return;
}
let size = (width * height * 4) as usize;
let slice = unsafe { std::slice::from_raw_parts(buffer, size) };
let mut texture_buffer = TEXTURE_BUFFER.write();
texture_buffer.clear();
texture_buffer.extend_from_slice(slice);
let mut texture_size = TEXTURE_SIZE.write();
*texture_size = (width as u32, height as u32);
}));
}
}
// egui + wgpu Application
struct EguiCefApp {
window: Option<Arc<Window>>,
egui_state: Option<EguiState>,
url_input: String,
frame_count: u64,
last_update: Instant,
}
struct EguiState {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'static>,
surface_config: wgpu::SurfaceConfiguration,
egui_renderer: egui_wgpu::Renderer,
egui_state: egui_winit::State,
cef_texture: Option<wgpu::Texture>,
}
impl EguiCefApp {
fn new() -> Self {
Self {
window: None,
egui_state: None,
url_input: String::from("https://www.google.com"),
frame_count: 0,
last_update: Instant::now(),
}
}
}
impl ApplicationHandler for EguiCefApp {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_some() {
return;
}
let window_attrs = WindowAttributes::default()
.with_title("egui + CEF Browser")
.with_inner_size(winit::dpi::LogicalSize::new(1024, 768));
let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
// Initialize wgpu and egui
let egui_state = pollster::block_on(async {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let surface = instance.create_surface(window.clone()).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: Default::default(),
},
None,
)
.await
.unwrap();
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(surface_caps.formats[0]);
let size = window.inner_size();
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
let egui_ctx = egui::Context::default();
let viewport_id = egui_ctx.viewport_id();
let max_texture_side = Some(device.limits().max_texture_dimension_2d as usize);
let egui_state = egui_winit::State::new(
egui_ctx,
viewport_id,
&window,
Some(window.scale_factor() as f32),
Some(winit::window::Theme::Dark),
max_texture_side,
);
let egui_renderer = egui_wgpu::Renderer::new(&device, surface_format, None, 1, false);
EguiState {
device,
queue,
surface,
surface_config,
egui_renderer,
egui_state,
cef_texture: None,
}
});
self.egui_state = Some(egui_state);
self.window = Some(window);
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let Some(window) = &self.window else {
return;
};
let Some(state) = &mut self.egui_state else {
return;
};
// Let egui handle the event first
let response = state.egui_state.on_window_event(window, &event);
if response.repaint {
window.request_redraw();
}
if response.consumed {
return;
}
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(new_size) => {
if new_size.width > 0 && new_size.height > 0 {
state.surface_config.width = new_size.width;
state.surface_config.height = new_size.height;
state.surface.configure(&state.device, &state.surface_config);
window.request_redraw();
}
}
WindowEvent::RedrawRequested => {
self.render();
}
_ => {}
}
}
}
impl EguiCefApp {
fn render(&mut self) {
let Some(window) = &self.window else {
return;
};
let Some(state) = &mut self.egui_state else {
return;
};
self.frame_count += 1;
// Build egui UI
let raw_input = state.egui_state.take_egui_input(window);
let egui_output = state.egui_state.egui_ctx().run(raw_input, |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("🦀 egui + CEF Browser Integration");
ui.separator();
ui.horizontal(|ui| {
ui.label("URL:");
ui.text_edit_singleline(&mut self.url_input);
if ui.button("Navigate").clicked() {
// Navigate to the URL
let url = self.url_input.clone();
if let Some(browser) = BROWSER_INSTANCE.read().as_ref() {
if let Some(main_frame) = browser.main_frame() {
let cef_url = CefString::from(url.as_str());
main_frame.load_url(Some(&cef_url));
}
}
}
});
ui.separator();
let cef_ready = *CEF_INITIALIZED.read();
ui.label(format!("CEF Status: {}", if cef_ready { "✅ Ready" } else { "⏳ Initializing..." }));
ui.label(format!("Frame: {}", self.frame_count));
ui.label(format!("FPS: {:.1}", 1.0 / self.last_update.elapsed().as_secs_f32()));
ui.separator();
// Display CEF texture
let texture_buffer = TEXTURE_BUFFER.read();
let size = TEXTURE_SIZE.read();
if !texture_buffer.is_empty() && size.0 > 0 && size.1 > 0 {
// Update or create wgpu texture from CEF buffer
if state.cef_texture.is_none()
|| state.cef_texture.as_ref().unwrap().width() != size.0
|| state.cef_texture.as_ref().unwrap().height() != size.1
{
state.cef_texture = Some(state.device.create_texture(&wgpu::TextureDescriptor {
label: Some("CEF Texture"),
size: wgpu::Extent3d {
width: size.0,
height: size.1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}));
}
// Convert BGRA to RGBA
let mut rgba_buffer = Vec::with_capacity(texture_buffer.len());
for chunk in texture_buffer.chunks_exact(4) {
rgba_buffer.push(chunk[2]); // R
rgba_buffer.push(chunk[1]); // G
rgba_buffer.push(chunk[0]); // B
rgba_buffer.push(chunk[3]); // A
}
if let Some(texture) = &state.cef_texture {
state.queue.write_texture(
wgpu::ImageCopyTexture {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&rgba_buffer,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * size.0),
rows_per_image: Some(size.1),
},
wgpu::Extent3d {
width: size.0,
height: size.1,
depth_or_array_layers: 1,
},
);
// Register texture with egui
let texture_id = state.egui_renderer.register_native_texture(
&state.device,
&texture.create_view(&wgpu::TextureViewDescriptor::default()),
wgpu::FilterMode::Linear,
);
ui.image(egui::ImageSource::Texture(egui::load::SizedTexture::new(
texture_id,
[size.0 as f32, size.1 as f32],
)));
}
} else {
ui.label("⏳ Waiting for CEF to render...");
}
});
});
state.egui_state.handle_platform_output(window, egui_output.platform_output);
// Render
let output = state.surface.get_current_texture().unwrap();
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = state.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
let screen_descriptor = egui_wgpu::ScreenDescriptor {
size_in_pixels: [state.surface_config.width, state.surface_config.height],
pixels_per_point: window.scale_factor() as f32,
};
let primitives = state.egui_state.egui_ctx().tessellate(egui_output.shapes, egui_output.pixels_per_point);
// Update textures
for (id, image_delta) in &egui_output.textures_delta.set {
state.egui_renderer.update_texture(&state.device, &state.queue, *id, image_delta);
}
// Update buffers
state.egui_renderer.update_buffers(
&state.device,
&state.queue,
&mut encoder,
&primitives,
&screen_descriptor,
);
// Render
{
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.1,
b: 0.1,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
// Render egui
state.egui_renderer.render(
&mut render_pass.forget_lifetime(),
&primitives,
&screen_descriptor,
);
} // render_pass dropped here automatically
for id in &egui_output.textures_delta.free {
state.egui_renderer.free_texture(id);
}
state.queue.submit(std::iter::once(encoder.finish()));
output.present();
self.last_update = Instant::now();
window.request_redraw();
}
}
fn main() {
env_logger::init();
#[cfg(target_os = "macos")]
let _loader = {
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
if !loader.load() {
eprintln!("Failed to load CEF library");
return;
}
loader
};
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
let args = Args::new();
let cmd = args.as_cmd_line().unwrap();
let switch = CefString::from("type");
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
let mut app = DemoApp::new_app();
let ret = execute_process(
Some(args.as_main_args()),
Some(&mut app),
std::ptr::null_mut(),
);
// Handle non-browser processes (renderer, GPU, etc.)
if !is_browser_process {
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
println!("launch process {process_type}");
assert!(ret >= 0, "cannot execute non-browser process");
return;
}
// Browser process - initialize CEF with external message pump
println!("launch browser process");
assert!(ret == -1, "cannot execute browser process");
let mut settings = Settings {
no_sandbox: !cfg!(feature = "sandbox") as _,
windowless_rendering_enabled: 1,
external_message_pump: 1, // Key: allows manual event loop control
..Default::default()
};
let init_result = initialize(
Some(args.as_main_args()),
Some(&mut settings),
Some(&mut app),
std::ptr::null_mut(),
);
if init_result != 1 {
eprintln!("Failed to initialize CEF: {}", init_result);
return;
}
println!("CEF initialized successfully with external message pump");
// Run event loop with manual pumping
let mut event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let mut egui_app = EguiCefApp::new();
loop {
// Pump CEF messages
do_message_loop_work();
// Pump winit events
let status = event_loop.pump_app_events(Some(Duration::ZERO), &mut egui_app);
if let PumpStatus::Exit(code) = status {
println!("Exiting with code: {}", code);
break;
}
// Small sleep to prevent busy-waiting
std::thread::sleep(Duration::from_millis(1));
}
shutdown();
}

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- https://cef-builds.spotifycdn.com/cef_binary_132.3.2+g4997b2f+chromium-132.0.6834.161_windows64.tar.bz2 -->
<!-- This compatibility section is from the standar pre - built binary package, specifically from %CEF_ROOT%/tests/app/win/compatibility.manifest -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates application support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates application support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates application support for Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--The ID below indicates application support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!--The ID below indicates application support for Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- This tag is required for XAML islands usage in the process for media scenarios. -->
<!-- This version corresponds to the Windows 10 May 2019 Update. -->
<maxversiontested Id="10.0.18362.0"/>
</application>
</compatibility>
<!--The compatibility section will be merged from build/win/compatibility.manifest -->
<dependency>
<dependentAssembly>
<assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@ -1,215 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [140.3.1+140.1.14](https://github.com/tauri-apps/cef-rs/compare/cef-v140.3.0+140.1.14...cef-v140.3.1+140.1.14) - 2025-10-03
### Fixed
- copy wrapped out-params back to pointers ([#224](https://github.com/tauri-apps/cef-rs/pull/224))
### Other
- update bindings
- *(test)* test out-params
## [140.2.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/cef-v140.1.0+140.1.13...cef-v140.2.0+140.1.14) - 2025-09-21
### Other
- *(release)* update CEF version to 140.1.14
## [140.1.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/cef-v140.0.0+140.1.13...cef-v140.1.0+140.1.13) - 2025-09-19
### Other
- release
## [140.0.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/cef-v139.8.0+139.0.40...cef-v140.0.0+140.1.13) - 2025-09-19
### Other
- update bindings
- *(release)* update CEF version to 140.1.13
## [139.8.0+139.0.40](https://github.com/tauri-apps/cef-rs/compare/cef-v139.7.2+139.0.38...cef-v139.8.0+139.0.40) - 2025-09-12
### Other
- *(release)* update CEF version to 139.0.40
## [139.7.2+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-v139.7.1+139.0.38...cef-v139.7.2+139.0.38) - 2025-09-08
### Fixed
- cleanup logic for copying back out-params
- handle out-params ([#173](https://github.com/tauri-apps/cef-rs/issues/173))
### Other
- release v139.7.2+139.0.38
- update bindings
## [139.7.1+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-v139.7.0+139.0.38...cef-v139.7.1+139.0.38) - 2025-09-07
### Other
- release v139.7.1+139.0.38
- *(deps)* update rust crate windows-sys to 0.61
## [139.7.0+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-v139.6.0+139.0.37...cef-v139.7.0+139.0.38) - 2025-08-31
### Other
- *(release)* update CEF version to 139.0.38
## [139.6.0+139.0.37](https://github.com/tauri-apps/cef-rs/compare/cef-v139.5.0+139.0.30...cef-v139.6.0+139.0.37) - 2025-08-29
### Other
- *(release)* update CEF version to 139.0.37
## [139.5.0+139.0.30](https://github.com/tauri-apps/cef-rs/compare/cef-v139.4.0+139.0.28...cef-v139.5.0+139.0.30) - 2025-08-28
### Other
- *(release)* update CEF version to 139.0.30
## [139.4.0+139.0.28](https://github.com/tauri-apps/cef-rs/compare/cef-v139.3.0+139.0.26...cef-v139.4.0+139.0.28) - 2025-08-23
### Other
- *(release)* update CEF version to 139.0.28
## [139.3.0+139.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-v139.2.1+139.0.23...cef-v139.3.0+139.0.26) - 2025-08-22
### Other
- *(release)* update CEF version to 139.0.26
## [139.2.1+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-v139.2.0+139.0.23...cef-v139.2.1+139.0.23) - 2025-08-16
### Fixed
- warnings about usize < 0 comparisons
### Other
- release
## [139.2.0+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-v139.1.0+139.0.20...cef-v139.2.0+139.0.23) - 2025-08-16
### Other
- *(release)* update CEF version to 139.0.23
## [139.1.0+139.0.20](https://github.com/tauri-apps/cef-rs/compare/cef-v139.0.1+139.0.17...cef-v139.1.0+139.0.20) - 2025-08-15
### Other
- *(release)* update CEF version to 139.0.20
## [139.0.1+139.0.17](https://github.com/tauri-apps/cef-rs/compare/cef-v139.0.0+139.0.17...cef-v139.0.1+139.0.17) - 2025-08-08
### Other
- release v139.0.1+139.0.17
## [139.0.0+139.0.17](https://github.com/tauri-apps/cef-rs/compare/cef-v138.9.0+138.0.36...cef-v139.0.0+139.0.17) - 2025-08-08
### Other
- *(release)* update CEF version to 139.0.17
## [138.9.0+138.0.36](https://github.com/tauri-apps/cef-rs/compare/cef-v138.8.0+138.0.34...cef-v138.9.0+138.0.36) - 2025-08-07
### Other
- *(release)* update CEF version to 138.0.36
## [138.8.0+138.0.34](https://github.com/tauri-apps/cef-rs/compare/cef-v138.7.1+138.0.33...cef-v138.8.0+138.0.34) - 2025-08-02
### Fixed
- remove cef version from example dependencies
### Other
- *(release)* update CEF version to 138.0.34
## [138.7.1+138.0.33](https://github.com/tauri-apps/cef-rs/compare/cef-v138.7.0+138.0.33...cef-v138.7.1+138.0.33) - 2025-07-29
### Other
- release v138.7.1+138.0.33
- move examples into separate crates
## [138.7.0+138.0.33](https://github.com/tauri-apps/cef-rs/compare/cef-v138.6.1+138.0.27...cef-v138.7.0+138.0.33) - 2025-07-29
### Other
- *(release)* update CEF version to 138.0.33
## [138.6.1+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-v138.6.0+138.0.27...cef-v138.6.1+138.0.27) - 2025-07-28
### Fixed
- embed git-cliff as a library in get-latest
### Other
- *(release)* bump version for get-latest updates
## [138.6.0+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-v138.5.1+138.0.26...cef-v138.6.0+138.0.27) - 2025-07-28
### Added
- update CEF version to 138.0.27
### Fixed
- bump version for release
## [138.5.1+138.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-v138.5.0+138.0.26...cef-v138.5.1+138.0.26) - 2025-07-22
### Other
- release
- *(doc)* regenerate CHANGELOG.md
## [138.5.0+138.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-v138.4.0+138.0.25...cef-v138.5.0+138.0.26) - 2025-07-19
### Other
- update CEF version
## [138.4.0+138.0.25](https://github.com/tauri-apps/cef-rs/compare/cef-v138.3.0+138.0.23...cef-v138.4.0+138.0.25) - 2025-07-18
### Other
- update CEF version
## [138.3.0+138.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-v138.2.2+138.0.21...cef-v138.3.0+138.0.23) - 2025-07-17
### Other
- update CEF version
## [138.2.2+138.0.21](https://github.com/tauri-apps/cef-rs/compare/cef-v138.2.1+138.0.21...cef-v138.2.2+138.0.21) - 2025-07-14
### Other
- release
- seed CHANGELOG.md files
## [138.2.1+138.0.21](https://github.com/tauri-apps/cef-rs/compare/cef-v138.2.0+138.0.21...cef-v138.2.1+138.0.21) - 2025-07-14
### Fixed
- bump major version of download-cef [#145](https://github.com/tauri-apps/cef-rs/issues/145)

View File

@ -1,26 +0,0 @@
[package]
name = "cef"
description = "Use cef in Rust"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[features]
default = ["sandbox"]
dox = ["cef-dll-sys/dox"]
sandbox = ["cef-dll-sys/sandbox"]
[package.metadata.docs.rs]
features = ["dox"]
[dependencies]
cef-dll-sys.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
windows-sys.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
libloading = "0.8"

View File

@ -1,3 +0,0 @@
# cef
Use the [Chromium Embedded Framework](https://github.com/chromiumembedded/cef) in Rust.

View File

@ -1,83 +0,0 @@
#[cfg(not(target_os = "windows"))]
use std::ffi::{c_char, CString};
use crate::*;
#[derive(Clone, Default)]
pub struct Args {
#[cfg(not(target_os = "windows"))]
_source: Vec<CString>,
#[cfg(not(target_os = "windows"))]
_argv: Vec<*const c_char>,
main_args: MainArgs,
}
impl Args {
#[cfg(target_os = "windows")]
pub fn new() -> Self {
let main_args = MainArgs {
instance: cef_dll_sys::HINSTANCE(
unsafe {
windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(std::ptr::null())
}
.cast(),
),
};
Self { main_args }
}
#[cfg(not(target_os = "windows"))]
pub fn new() -> Self {
let args = std::env::args();
let _source = args
.into_iter()
.map(|arg| CString::new(arg).unwrap())
.collect::<Vec<CString>>();
let _argv = _source
.iter()
.map(|arg| arg.as_ptr())
.collect::<Vec<*const c_char>>();
let main_args = MainArgs {
argc: _argv.len() as i32,
argv: _argv.as_ptr() as *mut *mut _,
};
Self {
_source,
_argv,
main_args,
}
}
pub fn as_main_args(&self) -> &MainArgs {
&self.main_args
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn as_cmd_line(&self) -> Option<CommandLine> {
let Some(cmd_line) = command_line_create() else {
return None;
};
cmd_line.init_from_argv(self.as_main_args().argc, self.as_main_args().argv.cast());
Some(cmd_line)
}
#[cfg(target_os = "windows")]
pub fn as_cmd_line(&self) -> Option<CommandLine> {
let cmd_line = command_line_create().and_then(|cmd_line| {
unsafe {
std::ffi::CStr::from_ptr(
windows_sys::Win32::System::Environment::GetCommandLineA().cast(),
)
}
.to_str()
.ok()
.map(|args| {
cmd_line.init_from_string(Some(&CefString::from(args)));
cmd_line
})
});
cmd_line
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,177 +0,0 @@
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
mod x86_64_unknown_linux_gnu;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub use x86_64_unknown_linux_gnu::*;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
mod aarch64_unknown_linux_gnu;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
pub use aarch64_unknown_linux_gnu::*;
#[cfg(all(target_os = "linux", target_arch = "arm"))]
mod arm_unknown_linux_gnueabi;
#[cfg(all(target_os = "linux", target_arch = "arm"))]
pub use arm_unknown_linux_gnueabi::*;
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
mod x86_64_pc_windows_msvc;
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
pub use x86_64_pc_windows_msvc::*;
#[cfg(all(target_os = "windows", target_arch = "x86"))]
mod i686_pc_windows_msvc;
#[cfg(all(target_os = "windows", target_arch = "x86"))]
pub use i686_pc_windows_msvc::*;
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
mod aarch64_pc_windows_msvc;
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
pub use aarch64_pc_windows_msvc::*;
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
mod x86_64_apple_darwin;
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
pub use x86_64_apple_darwin::*;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod aarch64_apple_darwin;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub use aarch64_apple_darwin::*;
#[cfg(test)]
mod test {
use super::*;
use crate::{rc::*, sys};
use std::{cell::RefCell, ptr};
#[derive(Default)]
struct CallInfo {
extra_info: RefCell<Option<DictionaryValue>>,
}
struct TestLifeSpanHandler {
object: *mut RcImpl<sys::_cef_life_span_handler_t, Self>,
call_info: std::rc::Rc<CallInfo>,
}
impl ImplLifeSpanHandler for TestLifeSpanHandler {
fn get_raw(&self) -> *mut sys::_cef_life_span_handler_t {
self.object.cast()
}
fn on_before_popup(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_popup_id: ::std::os::raw::c_int,
_target_url: Option<&CefString>,
_target_frame_name: Option<&CefString>,
_target_disposition: WindowOpenDisposition,
_user_gesture: ::std::os::raw::c_int,
_popup_features: Option<&PopupFeatures>,
_window_info: Option<&mut WindowInfo>,
_client: Option<&mut Option<Client>>,
_settings: Option<&mut BrowserSettings>,
extra_info: Option<&mut Option<DictionaryValue>>,
_no_javascript_access: Option<&mut ::std::os::raw::c_int>,
) -> ::std::os::raw::c_int {
let extra_info = extra_info.expect("extra_info is required");
*extra_info = self.call_info.extra_info.take();
1
}
}
impl WrapLifeSpanHandler for TestLifeSpanHandler {
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_life_span_handler_t, Self>) {
self.object = object;
}
}
impl TestLifeSpanHandler {
fn new(call_info: std::rc::Rc<CallInfo>) -> LifeSpanHandler {
LifeSpanHandler::new(Self {
object: std::ptr::null_mut(),
call_info,
})
}
}
impl Clone for TestLifeSpanHandler {
fn clone(&self) -> Self {
let object = unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
self.object
};
Self {
object,
call_info: self.call_info.clone(),
}
}
}
impl Rc for TestLifeSpanHandler {
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
std::mem::transmute(&base.cef_object)
}
}
}
#[test]
fn dictionary_value_out_param() {
#[cfg(target_os = "macos")]
unsafe {
use std::{ffi::CString, os::unix::ffi::OsStrExt};
let cef_dir = sys::get_cef_dir().expect("CEF not found");
let framework_dir = cef_dir
.join(sys::FRAMEWORK_PATH)
.canonicalize()
.expect("failed to get framework path");
let framework_dir =
CString::new(framework_dir.as_os_str().as_bytes()).expect("invalid path");
assert_eq!(sys::cef_load_library(framework_dir.as_ptr().cast()), 1);
}
assert_eq!(initialize(None, None, None, ptr::null_mut()), 0);
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
let call_info = std::rc::Rc::new(CallInfo::default());
let extra_info = dictionary_value_create().expect("failed to create dictionary");
let test_key = CefString::from("testKey");
let test_value = CefString::from("testValue");
extra_info.set_string(Some(&test_key), Some(&test_value));
*call_info.extra_info.borrow_mut() = Some(extra_info);
let mut extra_info = None;
let handler = TestLifeSpanHandler::new(call_info);
assert_eq!(
1,
handler.on_before_popup(
None,
None,
1,
None,
None,
sys::cef_window_open_disposition_t::CEF_WOD_CURRENT_TAB.into(),
0,
None,
None,
None,
None,
Some(&mut extra_info),
None,
)
);
let extra_info = extra_info.as_ref().unwrap();
assert_eq!(
"testValue",
CefString::from(&extra_info.string(Some(&test_key))).to_string()
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
#![doc = include_str!("../README.md")]
pub mod args;
pub mod rc;
pub mod string;
#[cfg(target_os = "macos")]
pub mod library_loader;
#[cfg(target_os = "macos")]
pub mod sandbox;
#[rustfmt::skip]
mod bindings;
pub use bindings::*;
pub use cef_dll_sys as sys;

View File

@ -1,46 +0,0 @@
use crate::{load_library, unload_library};
pub struct LibraryLoader {
path: std::path::PathBuf,
}
impl LibraryLoader {
const FRAMEWORK_PATH: &str =
"Chromium Embedded Framework.framework/Chromium Embedded Framework";
pub fn new(path: &std::path::Path, helper: bool) -> Self {
let resolver = if helper {
"../../../.."
} else {
"../../Frameworks"
};
let path = path
.join(resolver)
.join(Self::FRAMEWORK_PATH)
.canonicalize()
.unwrap();
Self { path }
}
// See [cef_load_library] for more documentation.
pub fn load(&self) -> bool {
Self::load_library(&self.path)
}
fn load_library(name: &std::path::Path) -> bool {
use std::os::unix::ffi::OsStrExt;
let Ok(name) = std::ffi::CString::new(name.as_os_str().as_bytes()) else {
return false;
};
unsafe { load_library(Some(&*name.as_ptr().cast())) == 1 }
}
}
impl Drop for LibraryLoader {
fn drop(&mut self) {
if unload_library() != 1 {
eprintln!("cannot unload framework {}", self.path.display());
}
}
}

View File

@ -1,380 +0,0 @@
//! Reference counted module
//!
//! Many cef types are reference counted, this module is the building block to create them. Users
//! typically don't need to uses these types, the `update-bindings` tool generates all the code
//! which should ever call them.
use std::{
fmt::Debug,
mem,
ops::Deref,
ptr::{self, NonNull},
sync::atomic::{fence, AtomicUsize, Ordering},
};
use cef_dll_sys::cef_base_ref_counted_t;
/// Reference counted trait for types has [`cef_base_ref_counted_t`].
pub trait Rc {
/// Increase the reference count by 1.
///
/// # Safety
///
/// Calling this method when you need to manually handle the reference count.
/// Otherwise, these methods shouldn't be called externally in most cases.
unsafe fn add_ref(&self) {
self.as_base().add_ref();
}
/// Decrease reference count by 1 and release the value if the count meets 0.
/// Reuturn `True` if it is released.
///
/// # Safety
///
/// Calling this method when you need to manually handle the reference count.
/// Otherwise, these methods shouldn't be called externally in most cases.
unsafe fn release(&self) -> bool {
self.as_base().release()
}
/// `True` if the reference count is exactly 1.
fn has_one_ref(&self) -> bool {
self.as_base().has_one_ref()
}
/// `True` if the reference count is larger than 0.
fn has_at_least_one_ref(&self) -> bool {
self.as_base().has_at_least_one_ref()
}
/// Get the reference of [cef_base_ref_counted_t].
fn as_base(&self) -> &cef_base_ref_counted_t;
}
impl Rc for cef_base_ref_counted_t {
unsafe fn add_ref(&self) {
if let Some(add_ref) = self.add_ref {
add_ref(ptr::from_ref(self) as *mut _);
}
}
fn has_one_ref(&self) -> bool {
if let Some(has_one_ref) = self.has_one_ref {
let result = unsafe { has_one_ref(ptr::from_ref(self) as *mut _) };
return result == 1;
}
false
}
fn has_at_least_one_ref(&self) -> bool {
if let Some(has_at_least_one_ref) = self.has_at_least_one_ref {
let result = unsafe { has_at_least_one_ref(ptr::from_ref(self) as *mut _) };
return result == 1;
}
false
}
unsafe fn release(&self) -> bool {
if let Some(release) = self.release {
return release(ptr::from_ref(self) as *mut _) == 1;
}
false
}
fn as_base(&self) -> &Self {
self
}
}
pub trait ConvertParam<T: Sized> {
fn into_raw(self) -> T;
}
impl<T, U> ConvertParam<U> for T
where
T: Sized + Into<U>,
U: Sized,
{
fn into_raw(self) -> U {
self.into()
}
}
impl<T> ConvertParam<*mut T> for &RefGuard<T>
where
T: Sized + Rc,
{
/// Access the [RefGuard] and return the raw pointer without decreasing the reference count.
///
/// # Safety
///
/// This should be used when you need to pass wrapper type to the FFI function as **parameter**, and it is **not**
/// the `self` type (usually the first parameter). This means we pass the ownership of the
/// value to the function call. Using this method elsewehre may cause incorrect reference count
/// and memory safety issues.
fn into_raw(self) -> *mut T {
unsafe { self.into_raw() }
}
}
pub struct WrapParamRef<T, P>
where
T: Sized + Into<P>,
P: Sized + Copy + Into<T>,
{
value: T,
output: Option<NonNull<P>>,
}
impl<T, P> Drop for WrapParamRef<T, P>
where
T: Sized + Into<P>,
P: Sized + Copy + Into<T>,
{
fn drop(&mut self) {
if let Some(output) = &mut self.output {
let output = unsafe { output.as_mut() };
let mut value = unsafe { mem::zeroed() };
mem::swap(&mut self.value, &mut value);
*output = value.into();
}
}
}
impl<T, P> From<*mut P> for WrapParamRef<T, P>
where
T: Sized + Into<P>,
P: Sized + Copy + Into<T>,
{
fn from(value: *mut P) -> Self {
let mut output = NonNull::new(value);
let value = output
.as_mut()
.map(|p| {
let mut value = unsafe { mem::zeroed() };
mem::swap(unsafe { p.as_mut() }, &mut value);
value.into()
})
.unwrap_or_else(|| unsafe { mem::zeroed() });
Self { value, output }
}
}
impl<T, P> From<*const P> for WrapParamRef<T, P>
where
T: Sized + Into<P>,
P: Sized + Copy + Into<T>,
{
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn from(value: *const P) -> Self {
let value = unsafe { value.as_ref() }
.map(|value| (*value).into())
.unwrap_or_else(|| unsafe { mem::zeroed() });
Self {
value,
output: None,
}
}
}
impl<T, P> AsMut<T> for WrapParamRef<T, P>
where
T: Sized + Into<P>,
P: Sized + Copy + Into<T>,
{
fn as_mut(&mut self) -> &mut T {
&mut self.value
}
}
impl<T, P> AsRef<T> for WrapParamRef<T, P>
where
T: Sized + Into<P>,
P: Sized + Copy + Into<T>,
{
fn as_ref(&self) -> &T {
&self.value
}
}
pub trait ConvertReturnValue<T: Sized> {
fn wrap_result(self) -> T;
}
impl<T, U> ConvertReturnValue<U> for T
where
T: Sized + Into<U>,
U: Sized,
{
fn wrap_result(self) -> U {
self.into()
}
}
/// A smart pointer for types from cef library.
pub struct RefGuard<T: Rc> {
object: *mut T,
}
impl<T: Debug + Rc> Debug for RefGuard<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let object_ref = unsafe { self.object.as_ref() };
write!(f, "RefGuard({object_ref:#?})")
}
}
impl<T: Rc> RefGuard<T> {
/// Create [RefGuard] from a raw C pointer.
///
/// # Safety
///
/// This should be used to get the **return value** of the FFI function. This means we get the
/// ownership of the value. The reference count of the return value is already increased when
/// you get it. So we don't need to increase it again manually. Using this method elsewhere may
/// cause incorrect reference count and memory safety issues.
pub unsafe fn from_raw(ptr: *mut T) -> RefGuard<T> {
RefGuard { object: ptr }
}
/// Create [RefGuard] from a raw C pointer and increase a reference count. This should be used
/// when you want to copy the value and create another wrapper type.
///
/// # Safety
///
/// THis should be used when you want to manually increase the reference count upon getting the
/// raw pointer. Using this method elsewhere may cause incorrect reference count and memory
/// safety issues.
pub unsafe fn from_raw_add_ref(ptr: *mut T) -> RefGuard<T> {
let guard = RefGuard { object: ptr };
guard.add_ref();
guard
}
// Get the raw pointer of [RefGuard].
//
/// # Safety
///
/// This should be used when you need to pass wrapper type to the FFI function as **parameter**, and it **is**
/// the `self` type (usually the first parameter). This means we pass the ownership of the
/// value to the function call. Using this method elsewhere may cause incorrect reference count
/// and memory safety issues.
pub unsafe fn into_raw(&self) -> *mut T {
self.object
}
/// Convert the value to another value that is also reference counted.
///
/// # Safety
///
/// This should be used when the type has type `U` as its base type. Using this method
/// elsewhere may cause memory safety issues.
pub unsafe fn convert<U: Rc>(&self) -> RefGuard<U> {
RefGuard::from_raw_add_ref(self.into_raw().cast())
}
}
unsafe impl<T: Rc> Send for RefGuard<T> {}
unsafe impl<T: Rc> Sync for RefGuard<T> {}
impl<T: Rc> Clone for RefGuard<T> {
fn clone(&self) -> RefGuard<T> {
unsafe { self.add_ref() };
RefGuard {
object: self.object,
}
}
}
impl<T: Rc> Deref for RefGuard<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.object }
}
}
impl<T: Rc> Drop for RefGuard<T> {
fn drop(&mut self) {
unsafe { self.release() };
}
}
/// There are some types require users to implement one their own in Rust and then create a raw type around it to
/// pass to sys level api. This is the wrapper type for it.
#[repr(C)]
pub struct RcImpl<T, I> {
/// Raw cef types
pub cef_object: T,
/// Rust interface of such type
pub interface: I,
ref_count: AtomicUsize,
}
impl<T, I> RcImpl<T, I> {
pub fn new(mut cef_object: T, interface: I) -> *mut RcImpl<T, I> {
let base: &mut cef_base_ref_counted_t =
unsafe { &mut *(ptr::from_mut(&mut cef_object).cast()) };
base.size = std::mem::size_of::<T>();
base.add_ref = Some(add_ref::<T, I>);
base.has_one_ref = Some(has_one_ref::<T, I>);
base.has_at_least_one_ref = Some(has_at_least_one_ref::<T, I>);
base.release = Some(release::<T, I>);
Box::into_raw(Box::new(RcImpl {
cef_object,
interface,
ref_count: AtomicUsize::new(1),
}))
}
pub fn get<'a>(ptr: *mut T) -> &'a mut RcImpl<T, I> {
unsafe { &mut *(ptr.cast()) }
}
}
extern "C" fn add_ref<T, I>(this: *mut cef_base_ref_counted_t) {
let obj = RcImpl::<T, I>::get(this.cast());
obj.ref_count.fetch_add(1, Ordering::Relaxed);
}
extern "C" fn has_one_ref<T, I>(this: *mut cef_base_ref_counted_t) -> i32 {
let obj = RcImpl::<T, I>::get(this.cast());
if obj.ref_count.load(Ordering::Relaxed) == 1 {
1
} else {
0
}
}
extern "C" fn has_at_least_one_ref<T, I>(this: *mut cef_base_ref_counted_t) -> i32 {
let obj = RcImpl::<T, I>::get(this.cast());
if obj.ref_count.load(Ordering::Relaxed) >= 1 {
1
} else {
0
}
}
pub extern "C" fn release<T, I>(this: *mut cef_base_ref_counted_t) -> i32 {
let obj = RcImpl::<T, I>::get(this.cast());
if obj.ref_count.fetch_sub(1, Ordering::Release) != 1 {
0
} else {
fence(Ordering::Acquire);
let _: Box<RcImpl<T, I>> = unsafe { Box::from_raw(this.cast()) };
1
}
}

View File

@ -1,53 +0,0 @@
use libloading::Library;
use std::{ffi::c_void, ptr::NonNull};
use crate::MainArgs;
pub struct Sandbox {
lib: Library,
context: Option<NonNull<c_void>>,
}
impl Sandbox {
const LIBCEF_SANDBOX_PATH: &str =
"../../../../Chromium Embedded Framework.framework/Libraries/libcef_sandbox.dylib";
pub fn new() -> Self {
unsafe {
let lib = Library::new(
std::env::current_exe()
.unwrap()
.join(Self::LIBCEF_SANDBOX_PATH)
.canonicalize()
.unwrap(),
)
.unwrap();
Self { lib, context: None }
}
}
pub fn initialize(&mut self, args: &MainArgs) {
let inner = unsafe {
self.lib
.get::<extern "C" fn(
argc: std::os::raw::c_int,
argv: *mut *mut ::std::os::raw::c_char,
) -> *mut c_void>(b"cef_sandbox_initialize")
.unwrap()(args.argc, args.argv)
};
self.context = NonNull::new(inner);
assert!(self.context.is_some());
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
unsafe {
if let Some(inner) = self.context {
self.lib
.get::<extern "C" fn(context: *mut c_void)>(b"cef_sandbox_destroy")
.unwrap()(inner.as_ptr());
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [2.2.0](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.1.0...download-cef-v2.2.0) - 2025-09-23
### Added
- add --mirror-url cli args
- Allow to download CEF binaries with custom base url set in env variable
## [2.1.0](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.3...download-cef-v2.1.0) - 2025-09-19
### Added
- support pre-downloaded archive ([#197](https://github.com/tauri-apps/cef-rs/pull/197))
## [2.0.3](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.2...download-cef-v2.0.3) - 2025-08-16
### Fixed
- #183
## [2.0.2](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.1...download-cef-v2.0.2) - 2025-07-22
### Other
- *(doc)* regenerate CHANGELOG.md
## [2.0.1](https://github.com/tauri-apps/cef-rs/compare/download-cef-v2.0.0...download-cef-v2.0.1) - 2025-07-14
### Other
- release
## [2.0.0](https://github.com/tauri-apps/cef-rs/compare/download-cef-v1.6.0...download-cef-v2.0.0) - 2025-07-14
### Fixed
- bump major version of download-cef [#145](https://github.com/tauri-apps/cef-rs/issues/145)
### Other
- update CEF version

View File

@ -1,23 +0,0 @@
[package]
name = "download-cef"
description = "Download and extract pre-built CEF (Chromium Embedded Framework) archives."
version = "2.2.0"
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
clap.workspace = true
regex.workspace = true
semver.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
bzip2 = "0.6"
indicatif = "0.18"
sha1_smol = "1"
tar = "0.4"
ureq = { version = "3", features = ["json", "socks-proxy"] }

View File

@ -1,4 +0,0 @@
# download-cef
Utility functions to download and extract prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
archives on any supported platform.

View File

@ -1,644 +0,0 @@
#![doc = include_str!("../README.md")]
use bzip2::bufread::BzDecoder;
use clap::ValueEnum;
use regex::Regex;
use semver::Version;
use serde::{Deserialize, Serialize};
use sha1_smol::Sha1;
use std::{
collections::HashMap,
env,
fmt::{self, Display},
fs::{self, File},
io::{self, BufReader, IsTerminal, Write},
path::{Path, PathBuf},
sync::{Mutex, OnceLock},
thread,
time::Duration,
};
#[macro_use]
extern crate thiserror;
#[derive(Debug, Error)]
pub enum Error {
#[error("Unsupported target triplet: {0}")]
UnsupportedTarget(String),
#[error("HTTP request error: {0}")]
Request(#[from] ureq::Error),
#[error("Invalid version: {0}")]
InvalidVersion(#[from] semver::Error),
#[error("Version not found: {0}")]
VersionNotFound(String),
#[error("Missing Content-Length header")]
MissingContentLength,
#[error("Opaque Content-Length header: {0}")]
OpaqueContentLength(#[from] ureq::http::header::ToStrError),
#[error("Invalid Content-Length header: {0}")]
InvalidContentLength(String),
#[error("File I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Unexpected file size: downloaded {downloaded} expected {expected}")]
UnexpectedFileSize { downloaded: u64, expected: u64 },
#[error("Bad SHA1 file hash: {0}")]
CorruptedFile(String),
#[error("Invalid archive file path: {0}")]
InvalidArchiveFile(String),
#[error("JSON serialization error: {0}")]
Json(#[from] serde_json::Error),
#[error(
"Undexpected archive version: location: {location} archive {archive} expected {expected}"
)]
VersionMismatch {
location: String,
archive: String,
expected: String,
},
#[error("Invalid regex pattern: {0}")]
InvalidRegexPattern(#[from] regex::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub const LINUX_TARGETS: &[&str] = &[
"x86_64-unknown-linux-gnu",
"arm-unknown-linux-gnueabi",
"aarch64-unknown-linux-gnu",
];
pub const MACOS_TARGETS: &[&str] = &["aarch64-apple-darwin", "x86_64-apple-darwin"];
pub const WINDOWS_TARGETS: &[&str] = &[
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"i686-pc-windows-msvc",
];
pub fn default_version(version: &str) -> String {
unwrap_cef_version(version).unwrap_or_else(|_| version.to_string())
}
fn unwrap_cef_version(version: &str) -> Result<String> {
static VERSIONS: OnceLock<Mutex<HashMap<Version, String>>> = OnceLock::new();
let mut versions = VERSIONS
.get_or_init(Default::default)
.lock()
.expect("Lock error");
Ok(versions
.entry(Version::parse(version)?)
.or_insert_with_key(|v| {
if v.build.is_empty() {
version.to_string()
} else {
v.build.to_string()
}
})
.clone())
}
pub fn check_archive_json(version: &str, location: &str) -> Result<()> {
let expected = Version::parse(&unwrap_cef_version(version)?)?;
static PATTERN: OnceLock<core::result::Result<Regex, regex::Error>> = OnceLock::new();
let pattern = PATTERN
.get_or_init(|| Regex::new(r"^cef_binary_([^+]+)(:?\+.+)?$"))
.as_ref()
.map_err(Clone::clone)?;
let archive_json: CefFile = serde_json::from_reader(File::open(archive_json_path(location))?)?;
let archive_version = pattern.replace(&archive_json.name, "$1");
let archive = Version::parse(&archive_version)?;
if archive <= expected {
Ok(())
} else {
Err(Error::VersionMismatch {
location: location.to_string(),
expected: expected.to_string(),
archive: archive.to_string(),
})
}
}
fn archive_json_path<P>(location: P) -> PathBuf
where
P: AsRef<Path>,
{
location.as_ref().join("archive.json")
}
pub const DEFAULT_CDN_URL: &str = "https://cef-builds.spotifycdn.com";
pub fn default_download_url() -> String {
env::var("CEF_DOWNLOAD_URL").unwrap_or(DEFAULT_CDN_URL.to_owned())
}
#[derive(Clone, PartialEq, Eq, Deserialize, Serialize, ValueEnum)]
#[serde(rename_all = "lowercase")]
pub enum Channel {
Stable,
Beta,
}
impl Display for Channel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Channel::Stable => write!(f, "stable"),
Channel::Beta => write!(f, "beta"),
}
}
}
#[derive(Deserialize, Serialize, Default)]
pub struct CefIndex {
pub macosarm64: CefPlatform,
pub macosx64: CefPlatform,
pub windows64: CefPlatform,
pub windowsarm64: CefPlatform,
pub windows32: CefPlatform,
pub linux64: CefPlatform,
pub linuxarm64: CefPlatform,
pub linuxarm: CefPlatform,
}
impl CefIndex {
pub fn download() -> Result<Self> {
Self::download_from(DEFAULT_CDN_URL)
}
pub fn download_from(url: &str) -> Result<Self> {
Ok(ureq::get(&format!("{url}/index.json"))
.call()?
.into_body()
.read_json()?)
}
pub fn platform(&self, target: &str) -> Result<&CefPlatform> {
match target {
"aarch64-apple-darwin" => Ok(&self.macosarm64),
"x86_64-apple-darwin" => Ok(&self.macosx64),
"x86_64-pc-windows-msvc" => Ok(&self.windows64),
"aarch64-pc-windows-msvc" => Ok(&self.windowsarm64),
"i686-pc-windows-msvc" => Ok(&self.windows32),
"x86_64-unknown-linux-gnu" => Ok(&self.linux64),
"aarch64-unknown-linux-gnu" => Ok(&self.linuxarm64),
"arm-unknown-linux-gnueabi" => Ok(&self.linuxarm),
v => Err(Error::UnsupportedTarget(v.to_string())),
}
}
}
#[derive(Deserialize, Serialize, Default)]
pub struct CefPlatform {
pub versions: Vec<CefVersion>,
}
impl CefPlatform {
pub fn version(&self, cef_version: &str) -> Result<&CefVersion> {
let version_prefix = format!("{cef_version}+");
self.versions
.iter()
.find(|v| v.cef_version.starts_with(&version_prefix))
.ok_or_else(|| Error::VersionNotFound(cef_version.to_string()))
}
pub fn latest(&self, channel: Channel) -> Result<&CefVersion> {
static PATTERN: OnceLock<core::result::Result<Regex, regex::Error>> = OnceLock::new();
let pattern = PATTERN
.get_or_init(|| Regex::new(r"^([^+]+)(:?\+.+)?$"))
.as_ref()
.map_err(Clone::clone)?;
self.versions
.iter()
.filter_map(|value| {
if value.channel == channel {
let key = Version::parse(&pattern.replace(&value.cef_version, "$1")).ok()?;
Some((key, value))
} else {
None
}
})
.max_by(|(a, _), (b, _)| a.cmp(b))
.map(|(_, v)| v)
.ok_or_else(|| Error::VersionNotFound("latest".to_string()))
}
}
#[derive(Deserialize, Serialize)]
pub struct CefVersion {
pub channel: Channel,
pub cef_version: String,
pub files: Vec<CefFile>,
}
impl CefVersion {
pub fn download_archive<P>(&self, location: P, show_progress: bool) -> Result<PathBuf>
where
P: AsRef<Path>,
{
self.download_archive_from(DEFAULT_CDN_URL, location, show_progress)
}
pub fn download_archive_from<P>(
&self,
url: &str,
location: P,
show_progress: bool,
) -> Result<PathBuf>
where
P: AsRef<Path>,
{
let file = self.minimal()?;
let (file, sha) = (file.name.as_str(), file.sha1.as_str());
fs::create_dir_all(&location)?;
let download_file = location.as_ref().join(file);
if download_file.exists() {
if calculate_file_sha1(&download_file) == sha {
if show_progress {
println!("Verified archive: {}", download_file.display());
}
return Ok(download_file);
}
if show_progress {
println!("Cleaning corrupted archive: {}", download_file.display());
}
let corrupted_file = location.as_ref().join(format!("corrupted_{file}"));
fs::rename(&download_file, &corrupted_file)?;
fs::remove_file(&corrupted_file)?;
}
let cef_url = format!("{url}/{file}");
if show_progress {
println!("Using archive url: {cef_url}");
}
let mut file = File::create(&download_file)?;
let resp = ureq::get(&cef_url).call()?;
let expected = resp
.headers()
.get("Content-Length")
.ok_or(Error::MissingContentLength)?;
let expected = expected.to_str()?;
let expected = expected
.parse::<u64>()
.map_err(|_| Error::InvalidContentLength(expected.to_owned()))?;
let downloaded = if show_progress && io::stdout().is_terminal() {
const DOWNLOAD_TEMPLATE: &str = "{msg} {spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})";
let bar = indicatif::ProgressBar::new(expected);
bar.set_style(
indicatif::ProgressStyle::with_template(DOWNLOAD_TEMPLATE)
.expect("invalid template")
.progress_chars("##-"),
);
bar.set_message("Downloading");
std::io::copy(
&mut bar.wrap_read(resp.into_body().into_reader()),
&mut file,
)
} else {
let mut reader = resp.into_body().into_reader();
std::io::copy(&mut reader, &mut file)
}?;
if downloaded != expected {
return Err(Error::UnexpectedFileSize {
downloaded,
expected,
});
}
if show_progress {
println!("Verifying SHA1 hash: {sha}...");
}
if calculate_file_sha1(&download_file) != sha {
return Err(Error::CorruptedFile(download_file.display().to_string()));
}
if show_progress {
println!("Downloaded archive: {}", download_file.display());
}
Ok(download_file)
}
pub fn download_archive_with_retry<P>(
&self,
location: P,
show_progress: bool,
retry_delay: Duration,
max_retries: u32,
) -> Result<PathBuf>
where
P: AsRef<Path>,
{
self.download_archive_with_retry_from(
DEFAULT_CDN_URL,
location,
show_progress,
retry_delay,
max_retries,
)
}
pub fn download_archive_with_retry_from<P>(
&self,
url: &str,
location: P,
show_progress: bool,
retry_delay: Duration,
max_retries: u32,
) -> Result<PathBuf>
where
P: AsRef<Path>,
{
let mut result = self.download_archive_from(&url, &location, show_progress);
let mut retry = 0;
while let Err(Error::Io(_)) = &result {
if retry >= max_retries {
break;
}
retry += 1;
thread::sleep(retry_delay * retry);
result = self.download_archive_from(&url, &location, show_progress);
}
result
}
pub fn minimal(&self) -> Result<&CefFile> {
self.files
.iter()
.find(|f| f.file_type == "minimal")
.ok_or_else(|| Error::VersionNotFound(self.cef_version.clone()))
}
pub fn write_archive_json<P>(&self, location: P) -> Result<()>
where
P: AsRef<Path>,
{
self.minimal()?.write_archive_json(location)
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct CefFile {
#[serde(rename = "type")]
pub file_type: String,
pub name: String,
pub sha1: String,
}
impl CefFile {
pub fn write_archive_json<P>(&self, location: P) -> Result<()>
where
P: AsRef<Path>,
{
let archive_version = serde_json::to_string_pretty(self)?;
let mut archive_json = File::create(archive_json_path(location))?;
archive_json.write_all(archive_version.as_bytes())?;
Ok(())
}
}
impl TryFrom<&Path> for CefFile {
type Error = Error;
fn try_from(location: &Path) -> Result<Self> {
let file_type = "minimal".to_string();
let name = location
.file_name()
.map(|f| f.display().to_string())
.ok_or_else(|| Error::InvalidArchiveFile(location.display().to_string()))?;
let sha1 = calculate_file_sha1(location);
Ok(Self {
file_type,
name,
sha1,
})
}
}
pub fn download_target_archive<P>(
target: &str,
cef_version: &str,
location: P,
show_progress: bool,
) -> Result<PathBuf>
where
P: AsRef<Path>,
{
download_target_archive_from(
DEFAULT_CDN_URL,
target,
cef_version,
location,
show_progress,
)
}
pub fn download_target_archive_from<P>(
url: &str,
target: &str,
cef_version: &str,
location: P,
show_progress: bool,
) -> Result<PathBuf>
where
P: AsRef<Path>,
{
if show_progress {
println!("Downloading CEF archive for {target}...");
}
let index = CefIndex::download_from(&url)?;
let platform = index.platform(target)?;
let version = platform.version(cef_version)?;
version.download_archive_with_retry_from(
url,
location,
show_progress,
Duration::from_secs(15),
3,
)
}
pub fn extract_target_archive<P, Q>(
target: &str,
archive: P,
location: Q,
show_progress: bool,
) -> Result<PathBuf>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
if show_progress {
println!("Extracting archive: {}", archive.as_ref().display());
}
let decoder = BzDecoder::new(BufReader::new(File::open(&archive)?));
tar::Archive::new(decoder).unpack(&location)?;
let extracted_dir = archive.as_ref().display().to_string();
let extracted_dir = extracted_dir
.strip_suffix(".tar.bz2")
.map(PathBuf::from)
.ok_or(Error::InvalidArchiveFile(extracted_dir))?;
let os_and_arch = OsAndArch::try_from(target)?;
let OsAndArch { os, arch } = os_and_arch;
let cef_dir = os_and_arch.to_string();
let cef_dir = location.as_ref().join(cef_dir);
if cef_dir.exists() {
let old_dir = location.as_ref().join(format!("old_{os}_{arch}"));
if show_progress {
println!("Cleaning up: {}", old_dir.display());
}
fs::rename(&cef_dir, &old_dir)?;
fs::remove_dir_all(old_dir)?;
}
const RELEASE_DIR: &str = "Release";
fs::rename(extracted_dir.join(RELEASE_DIR), &cef_dir)?;
if os != "macos" {
let resources = extracted_dir.join("Resources");
for entry in fs::read_dir(&resources)? {
let entry = entry?;
fs::rename(entry.path(), cef_dir.join(entry.file_name()))?;
}
}
const CMAKE_LISTS_TXT: &str = "CMakeLists.txt";
fs::rename(
extracted_dir.join(CMAKE_LISTS_TXT),
cef_dir.join(CMAKE_LISTS_TXT),
)?;
const CMAKE_DIR: &str = "cmake";
fs::rename(extracted_dir.join(CMAKE_DIR), cef_dir.join(CMAKE_DIR))?;
const INCLUDE_DIR: &str = "include";
fs::rename(extracted_dir.join(INCLUDE_DIR), cef_dir.join(INCLUDE_DIR))?;
const LIBCEF_DLL_DIR: &str = "libcef_dll";
fs::rename(
extracted_dir.join(LIBCEF_DLL_DIR),
cef_dir.join(LIBCEF_DLL_DIR),
)?;
if show_progress {
println!("Moved contents to: {}", cef_dir.display());
}
// Cleanup whatever is left in the extracted directory.
let old_dir = extracted_dir
.parent()
.map(|parent| parent.join(format!("extracted_{os}_{arch}")))
.ok_or_else(|| Error::InvalidArchiveFile(extracted_dir.display().to_string()))?;
if show_progress {
println!("Cleaning up: {}", old_dir.display());
}
fs::rename(&extracted_dir, &old_dir)?;
fs::remove_dir_all(old_dir)?;
Ok(cef_dir)
}
fn calculate_file_sha1(path: &Path) -> String {
use std::io::Read;
let mut file = BufReader::new(File::open(path).unwrap());
let mut sha1 = Sha1::new();
let mut buffer = [0; 8192];
loop {
let count = file.read(&mut buffer).unwrap();
if count == 0 {
break;
}
sha1.update(&buffer[..count]);
}
sha1.digest().to_string()
}
pub struct OsAndArch {
pub os: &'static str,
pub arch: &'static str,
}
impl Display for OsAndArch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let os = self.os;
let arch = self.arch;
write!(f, "cef_{os}_{arch}")
}
}
impl TryFrom<&str> for OsAndArch {
type Error = Error;
fn try_from(target: &str) -> Result<Self> {
match target {
"aarch64-apple-darwin" => Ok(OsAndArch {
os: "macos",
arch: "aarch64",
}),
"x86_64-apple-darwin" => Ok(OsAndArch {
os: "macos",
arch: "x86_64",
}),
"x86_64-pc-windows-msvc" => Ok(OsAndArch {
os: "windows",
arch: "x86_64",
}),
"aarch64-pc-windows-msvc" => Ok(OsAndArch {
os: "windows",
arch: "aarch64",
}),
"i686-pc-windows-msvc" => Ok(OsAndArch {
os: "windows",
arch: "x86",
}),
"x86_64-unknown-linux-gnu" => Ok(OsAndArch {
os: "linux",
arch: "x86_64",
}),
"aarch64-unknown-linux-gnu" => Ok(OsAndArch {
os: "linux",
arch: "aarch64",
}),
"arm-unknown-linux-gnueabi" => Ok(OsAndArch {
os: "linux",
arch: "arm",
}),
v => Err(Error::UnsupportedTarget(v.to_string())),
}
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
pub const DEFAULT_TARGET: &str = "aarch64-unknown-linux-gnu";
#[cfg(all(target_os = "linux", target_arch = "arm"))]
pub const DEFAULT_TARGET: &str = "arm-unknown-linux-gnueabi";
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
pub const DEFAULT_TARGET: &str = "x86_64-pc-windows-msvc";
#[cfg(all(target_os = "windows", target_arch = "x86"))]
pub const DEFAULT_TARGET: &str = "i686-pc-windows-msvc";
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
pub const DEFAULT_TARGET: &str = "aarch64-pc-windows-msvc";
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
pub const DEFAULT_TARGET: &str = "x86_64-apple-darwin";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub const DEFAULT_TARGET: &str = "aarch64-apple-darwin";

View File

@ -1,208 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [140.3.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v140.2.0+140.1.14...export-cef-dir-v140.3.0+140.1.14) - 2025-09-23
### Added
- add --mirror-url cli args
- Allow to download CEF binaries with custom base url set in env variable
## [140.2.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v140.1.0+140.1.13...export-cef-dir-v140.2.0+140.1.14) - 2025-09-21
### Other
- *(release)* update CEF version to 140.1.14
## [140.1.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v140.0.0+140.1.13...export-cef-dir-v140.1.0+140.1.13) - 2025-09-19
### Added
- support pre-downloaded archive ([#197](https://github.com/tauri-apps/cef-rs/issues/197))
### Other
- release
## [140.0.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.8.0+139.0.40...export-cef-dir-v140.0.0+140.1.13) - 2025-09-19
### Other
- *(release)* update CEF version to 140.1.13
## [139.8.0+139.0.40](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.7.2+139.0.38...export-cef-dir-v139.8.0+139.0.40) - 2025-09-12
### Other
- *(release)* update CEF version to 139.0.40
## [139.7.2+139.0.38](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.7.1+139.0.38...export-cef-dir-v139.7.2+139.0.38) - 2025-09-08
### Other
- release v139.7.2+139.0.38
## [139.7.1+139.0.38](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.7.0+139.0.38...export-cef-dir-v139.7.1+139.0.38) - 2025-09-07
### Other
- release v139.7.1+139.0.38
- *(deps)* update rust crate windows-sys to 0.61
## [139.7.0+139.0.38](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.6.0+139.0.37...export-cef-dir-v139.7.0+139.0.38) - 2025-08-31
### Other
- *(release)* update CEF version to 139.0.38
## [139.6.0+139.0.37](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.5.0+139.0.30...export-cef-dir-v139.6.0+139.0.37) - 2025-08-29
### Other
- *(release)* update CEF version to 139.0.37
## [139.5.0+139.0.30](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.4.0+139.0.28...export-cef-dir-v139.5.0+139.0.30) - 2025-08-28
### Other
- *(release)* update CEF version to 139.0.30
## [139.4.0+139.0.28](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.3.0+139.0.26...export-cef-dir-v139.4.0+139.0.28) - 2025-08-23
### Other
- *(release)* update CEF version to 139.0.28
## [139.3.0+139.0.26](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.2.1+139.0.23...export-cef-dir-v139.3.0+139.0.26) - 2025-08-22
### Other
- *(release)* update CEF version to 139.0.26
## [139.2.1+139.0.23](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.2.0+139.0.23...export-cef-dir-v139.2.1+139.0.23) - 2025-08-16
### Fixed
- [#183](https://github.com/tauri-apps/cef-rs/issues/183)
### Other
- release
## [139.2.0+139.0.23](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.1.0+139.0.20...export-cef-dir-v139.2.0+139.0.23) - 2025-08-16
### Other
- *(release)* update CEF version to 139.0.23
## [139.1.0+139.0.20](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.0.1+139.0.17...export-cef-dir-v139.1.0+139.0.20) - 2025-08-15
### Other
- *(release)* update CEF version to 139.0.20
## [139.0.1+139.0.17](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v139.0.0+139.0.17...export-cef-dir-v139.0.1+139.0.17) - 2025-08-08
### Other
- release v139.0.1+139.0.17
## [139.0.0+139.0.17](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.9.0+138.0.36...export-cef-dir-v139.0.0+139.0.17) - 2025-08-08
### Other
- *(release)* update CEF version to 139.0.17
## [138.9.0+138.0.36](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.8.0+138.0.34...export-cef-dir-v138.9.0+138.0.36) - 2025-08-07
### Other
- *(release)* update CEF version to 138.0.36
## [138.8.0+138.0.34](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.7.1+138.0.33...export-cef-dir-v138.8.0+138.0.34) - 2025-08-02
### Fixed
- remove cef version from example dependencies
### Other
- *(release)* update CEF version to 138.0.34
## [138.7.1+138.0.33](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.7.0+138.0.33...export-cef-dir-v138.7.1+138.0.33) - 2025-07-29
### Other
- release v138.7.1+138.0.33
- move examples into separate crates
## [138.7.0+138.0.33](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.6.1+138.0.27...export-cef-dir-v138.7.0+138.0.33) - 2025-07-29
### Other
- *(release)* update CEF version to 138.0.33
## [138.6.1+138.0.27](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.6.0+138.0.27...export-cef-dir-v138.6.1+138.0.27) - 2025-07-28
### Fixed
- embed git-cliff as a library in get-latest
### Other
- *(release)* bump version for get-latest updates
## [138.6.0+138.0.27](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.5.1+138.0.26...export-cef-dir-v138.6.0+138.0.27) - 2025-07-28
### Added
- update CEF version to 138.0.27
### Fixed
- bump version for release
## [138.5.1+138.0.26](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.5.0+138.0.26...export-cef-dir-v138.5.1+138.0.26) - 2025-07-22
### Other
- release
- *(doc)* regenerate CHANGELOG.md
## [138.5.0+138.0.26](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.4.0+138.0.25...export-cef-dir-v138.5.0+138.0.26) - 2025-07-19
### Other
- update CEF version
## [138.4.0+138.0.25](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.3.0+138.0.23...export-cef-dir-v138.4.0+138.0.25) - 2025-07-18
### Other
- update CEF version
## [138.3.0+138.0.23](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.2.2+138.0.21...export-cef-dir-v138.3.0+138.0.23) - 2025-07-17
### Other
- update CEF version
## [138.2.2+138.0.21](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.2.1+138.0.21...export-cef-dir-v138.2.2+138.0.21) - 2025-07-14
### Other
- release
- seed CHANGELOG.md files
## [138.2.1+138.0.21](https://github.com/tauri-apps/cef-rs/compare/export-cef-dir-v138.2.0+138.0.21...export-cef-dir-v138.2.1+138.0.21) - 2025-07-14
### Fixed
- bump major version of download-cef [#145](https://github.com/tauri-apps/cef-rs/issues/145)

View File

@ -1,16 +0,0 @@
[package]
name = "export-cef-dir"
description = "Export pre-built CEF (Chromium Embedded Framework) archives."
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
anyhow.workspace = true
clap.workspace = true
download-cef.workspace = true
semver.workspace = true
serde_json.workspace = true

View File

@ -1,28 +0,0 @@
# export-cef-dir
Export files from the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
archive on any supported platform. The structure of the exported directory matches the way that
the `cef-dll-sys` crate expects to see them.
To use the target directory when building, set the `CEF_PATH` environment variable to the path of the
exported directory, e.g., `~/.local/share/cef`.
To use the DLLs in this directory at runtime, the library loader path varies by platform:
- Linux
```sh
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CEF_PATH"
```
- macOS
```sh
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$CEF_PATH"
```
- Windows (using PowerShell)
```pwsh
$env:PATH = "$env:PATH;$env:CEF_PATH"
```

View File

@ -1,136 +0,0 @@
#![doc = include_str!("../README.md")]
use clap::Parser;
use download_cef::{CefFile, CefIndex, OsAndArch, DEFAULT_TARGET};
use std::{
fs,
path::{Path, PathBuf},
sync::OnceLock,
time::Duration,
};
fn default_version() -> &'static str {
static DEFAULT_VERSION: OnceLock<String> = OnceLock::new();
DEFAULT_VERSION
.get_or_init(|| download_cef::default_version(env!("CARGO_PKG_VERSION")))
.as_str()
}
fn default_download_url() -> &'static str {
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
DEFAULT_DOWNLOAD_URL
.get_or_init(|| download_cef::default_download_url())
.as_str()
}
#[derive(Parser, Debug)]
#[command(about, long_about = None)]
struct Args {
#[arg(short, long)]
force: bool,
#[arg(short, long)]
save_archive: bool,
#[arg(short, long, default_value = DEFAULT_TARGET)]
target: String,
#[arg(short, long, default_value = default_version())]
version: String,
#[arg(short, long, default_value = default_download_url())]
mirror_url: String,
#[arg(short, long)]
archive: Option<String>,
output: String,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let output = PathBuf::from(args.output);
let url = args.mirror_url.as_str();
let parent = PathBuf::from(
output
.parent()
.ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?,
);
if fs::exists(&output)? {
if !args.force {
return Err(anyhow::anyhow!(
"target directory already exists: {}",
output.display()
));
}
let dir = output
.file_name()
.and_then(|dir| dir.to_str())
.ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?;
let old_output = parent.join(format!("old_{dir}"));
fs::rename(&output, &old_output)?;
println!("Cleaning up: {}", old_output.display());
fs::remove_dir_all(old_output)?
}
let target = args.target.as_str();
let os_arch = OsAndArch::try_from(target)?;
let cef_dir = os_arch.to_string();
let cef_dir = parent.join(&cef_dir);
if fs::exists(&cef_dir)? {
let dir = cef_dir
.file_name()
.and_then(|dir| dir.to_str())
.ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?;
let old_cef_dir = parent.join(format!("old_{dir}"));
fs::rename(&cef_dir, &old_cef_dir)?;
println!("Cleaning up: {}", old_cef_dir.display());
fs::remove_dir_all(old_cef_dir)?
}
let (archive, extracted_dir) = match args.archive {
Some(archive) => {
let extracted_dir =
download_cef::extract_target_archive(target, &archive, &parent, true)?;
let archive = CefFile::try_from(Path::new(&archive))?;
(archive, extracted_dir)
}
None => {
let cef_version = args.version.as_str();
let index = CefIndex::download_from(url)?;
let platform = index.platform(target)?;
let version = platform.version(cef_version)?;
let archive = version.download_archive_with_retry_from(
url,
&parent,
true,
Duration::from_secs(15),
3,
)?;
let extracted_dir =
download_cef::extract_target_archive(target, &archive, &parent, true)?;
if !args.save_archive {
println!("Cleaning up: {}", archive.display());
fs::remove_file(archive)?;
}
let archive = version.minimal()?.clone();
(archive, extracted_dir)
}
};
if extracted_dir != cef_dir {
return Err(anyhow::anyhow!(
"extracted dir {extracted_dir:?} does not match cef_dir {cef_dir:?}",
));
}
archive.write_archive_json(extracted_dir)?;
if output != cef_dir {
println!("Renaming: {}", output.display());
fs::rename(cef_dir, output)?;
}
Ok(())
}

View File

@ -1,21 +0,0 @@
[package]
name = "get-latest"
publish = false
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
download-cef.workspace = true
clap.workspace = true
git-cliff.workspace = true
git-cliff-core.workspace = true
regex.workspace = true
semver.workspace = true
serde.workspace = true
thiserror.workspace = true
toml_edit.workspace = true

View File

@ -1,5 +0,0 @@
# get-latest
Download the list of versions available from the [Chromium Embedded Framework (CEF) Automated
Builds](https://cef-builds.spotifycdn.com/index.html) page and determine the latest version that is
available for every platform we support.

View File

@ -1,112 +0,0 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
# A Tera template to be rendered as the changelog's footer.
# See https://keats.github.io/tera/docs/#introduction
header = """# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
"""
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{%- if version %}
## [{{ version }}]\
{%- if previous.version == version -%}\
(<REPO>/releases/tag/{{ version }})\
{%- else -%}\
(<REPO>/compare/{{ previous.version }}...{{ version }})\
{% endif %} \
- {{ timestamp | date(format="%Y-%m-%d") }}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
{%- if commit.scope -%}
- *({{commit.scope}})* {% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message }}\
{%- if commit.links %} \
({% for link in commit.links %}[{{link.text}}]({{link.href}}) {% endfor -%})\
{% endif %}
{% else -%}
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message }}
{% endif -%}
{% endfor -%}
{% endfor %}\
{%- else -%}
{% endif -%}
"""
# A Tera template to be rendered as the changelog's footer.
# See https://keats.github.io/tera/docs/#introduction
footer = ""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# Render body even when there are no releases to process.
render_always = true
# An array of regex based postprocessors to modify the changelog.
postprocessors = [
# Replace the placeholder <REPO> with a URL.
{ pattern = '<REPO>', replace = "https://github.com/tauri-apps/cef-rs" },
{ pattern = '\[[\w\-]+-v([0-9.+]+)\]', replace = "[${1}]" },
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = true
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = true
# Require all commits to be conventional.
# Takes precedence over filter_unconventional.
require_conventional = false
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
{ pattern = '(\w+\s)?#([0-9]+)', replace = "${1}[#${2}](<REPO>/issues/${2})" },
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))" },
# Check spelling of the commit message using https://github.com/crate-ci/typos.
# If the spelling is incorrect, it will be fixed automatically.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# Prevent commits that are breaking from being excluded by commit parsers.
protect_breaking_commits = false
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "added" },
{ message = "^changed", group = "changed" },
{ message = "^deprecated", group = "deprecated" },
{ message = "^fix", group = "fixed" },
{ message = "^security", group = "security" },
{ message = "^.*", group = "other" },
]
# Exclude commits that are not matched by any commit parser.
filter_commits = false
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
link_parsers = []
# Include only the tags that belong to the current branch.
use_branch_tags = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order releases topologically instead of chronologically.
topo_order_commits = true
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "newest"
# Process submodules commits
recurse_submodules = false

View File

@ -1,235 +0,0 @@
#![doc = include_str!("../README.md")]
#[macro_use]
extern crate thiserror;
use clap::Parser;
use download_cef::{CefIndex, Channel, LINUX_TARGETS, MACOS_TARGETS, WINDOWS_TARGETS};
use git_cliff::args::*;
use regex::Regex;
use semver::{BuildMetadata, Version};
use std::{
env, fs,
io::Write,
path::PathBuf,
process::{Command, ExitStatus},
sync::OnceLock,
};
use toml_edit::{value, DocumentMut};
#[derive(Debug, Error)]
enum Error {
#[error("Download error: {0}")]
Download(#[from] download_cef::Error),
#[error("Invalid regex pattern: {0}")]
InvalidRegexPattern(#[from] regex::Error),
#[error("Invalid version: {0}")]
InvalidVersion(#[from] semver::Error),
#[error("No versions found")]
NoVersionsFound,
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid manifest file: {0}")]
InvalidManifest(#[from] toml_edit::TomlError),
#[error("Error invoking git: {0:?}")]
GitInvocation(ExitStatus),
#[error("Error running git-cliff: {0:?}")]
InvalidGitCliffArgs(#[from] clap::Error),
#[error("Error updating change log: {0:?}")]
UpdateChangeLog(#[from] git_cliff_core::error::Error),
}
type Result<T> = std::result::Result<T, Error>;
fn default_download_url() -> &'static str {
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
DEFAULT_DOWNLOAD_URL
.get_or_init(|| download_cef::default_download_url())
.as_str()
}
#[derive(Parser)]
#[command(about, long_about = None)]
struct Args {
#[arg(short, long, default_value = default_download_url())]
mirror_url: String,
#[arg(short, long, default_value = "stable")]
channel: Channel,
#[arg(short, long)]
update_version: bool,
}
fn main() -> Result<()> {
let pattern = Regex::new(r"^([^+]+)(:?\+.+)?$")?;
let args = Args::parse();
let channel = args.channel;
let url = args.mirror_url.as_str();
let index = CefIndex::download_from(url)?;
let latest_versions: Vec<_> = LINUX_TARGETS
.iter()
.chain(MACOS_TARGETS.iter())
.chain(WINDOWS_TARGETS.iter())
.map(|target| {
index
.platform(target)
.and_then(|platform| platform.latest(channel.clone()))
.map(|version| pattern.replace(&version.cef_version, "$1"))
})
.collect::<download_cef::Result<Vec<_>>>()?;
let latest_versions = latest_versions
.into_iter()
.map(|version| Ok(Version::parse(&version)?))
.collect::<Result<Vec<_>>>()?;
let latest_version = latest_versions
.into_iter()
.min()
.ok_or(Error::NoVersionsFound)?;
println!("Latest available {channel} version: {latest_version}");
if args.update_version {
let current_version =
Version::parse(&download_cef::default_version(env!("CARGO_PKG_VERSION")))?;
if current_version < latest_version {
let latest_build = BuildMetadata::new(&latest_version.to_string())?;
let mut next_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
if next_version.major < latest_version.major {
next_version.major = latest_version.major;
next_version.minor = 0;
} else {
next_version.minor += 1;
}
next_version.patch = 0;
next_version.build = latest_build.clone();
let mut manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest.pop();
let manifest = manifest.join("Cargo.toml");
let mut doc = fs::read_to_string(&manifest)?.parse::<DocumentMut>()?;
doc["workspace"]["package"]["version"] = value(next_version.to_string());
let workspace_version = Version {
build: BuildMetadata::EMPTY,
..next_version.clone()
};
doc["workspace"]["dependencies"]["cef-dll-sys"]["version"] =
value(workspace_version.to_string());
fs::write(&manifest, doc.to_string().as_bytes())?;
if let Ok(output) = env::var("GITHUB_OUTPUT") {
let mut output = fs::OpenOptions::new()
.create(true)
.append(true)
.open(output)?;
let commit_message =
format!("chore(release): update CEF version to {latest_version}");
writeln!(output, "commit-message={commit_message}",)?;
let output = Command::new("git")
.args(["commit", "-a", "-m", commit_message.as_str()])
.output()?;
std::io::stdout().write_all(&output.stdout)?;
std::io::stderr().write_all(&output.stderr)?;
if !output.status.success() {
return Err(Error::GitInvocation(output.status));
}
let export_cef_dir_tag = format!("export-cef-dir-v{next_version}");
let output = Command::new("git")
.args(["tag", "--no-sign", "-f", export_cef_dir_tag.as_str()])
.output()?;
std::io::stdout().write_all(&output.stdout)?;
std::io::stderr().write_all(&output.stderr)?;
if !output.status.success() {
return Err(Error::GitInvocation(output.status));
}
let cef_dll_sys_tag = format!("cef-dll-sys-v{next_version}");
let output = Command::new("git")
.args(["tag", "--no-sign", "-f", cef_dll_sys_tag.as_str()])
.output()?;
std::io::stdout().write_all(&output.stdout)?;
std::io::stderr().write_all(&output.stderr)?;
if !output.status.success() {
return Err(Error::GitInvocation(output.status));
}
let cef_tag = format!("cef-v{next_version}");
let output = Command::new("git")
.args(["tag", "--no-sign", "-f", cef_tag.as_str()])
.output()?;
std::io::stdout().write_all(&output.stdout)?;
std::io::stderr().write_all(&output.stderr)?;
if !output.status.success() {
return Err(Error::GitInvocation(output.status));
}
let mut config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
config_path.push("cliff.toml");
let common_opts = ["--strip", "footer", "--include-path", "Cargo.toml"];
let export_cef_dir_opts = Opt {
config: config_path.clone(),
range: Some("export-cef-dir-v138.2.0+138.0.21..".to_string()),
..Opt::try_parse_from(
common_opts.iter().chain(
[
"--include-path",
"export-cef-dir/*",
"--tag-pattern",
"^export-cef-dir-v",
"--output",
"export-cef-dir/CHANGELOG.md",
]
.iter(),
),
)?
};
git_cliff::run(export_cef_dir_opts)?;
let cef_dll_sys_opts = Opt {
config: config_path.clone(),
range: Some("cef-dll-sys-v138.2.0+138.0.21..".to_string()),
..Opt::try_parse_from(
common_opts.iter().chain(
[
"--include-path",
"sys/*",
"--tag-pattern",
"^cef-dll-sys-v",
"--output",
"sys/CHANGELOG.md",
]
.iter(),
),
)?
};
git_cliff::run(cef_dll_sys_opts)?;
let cef_opts = Opt {
config: config_path,
range: Some("cef-v138.2.0+138.0.21..".to_string()),
..Opt::try_parse_from(
common_opts.iter().chain(
[
"--include-path",
"cef/*",
"--tag-pattern",
"^cef-v",
"--output",
"cef/CHANGELOG.md",
]
.iter(),
),
)?
};
git_cliff::run(cef_opts)?;
}
}
}
Ok(())
}

View File

@ -1,202 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [140.2.0+140.1.14](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v140.1.0+140.1.13...cef-dll-sys-v140.2.0+140.1.14) - 2025-09-21
### Other
- *(release)* update CEF version to 140.1.14
## [140.1.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v140.0.0+140.1.13...cef-dll-sys-v140.1.0+140.1.13) - 2025-09-19
### Other
- release
## [140.0.0+140.1.13](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.8.0+139.0.40...cef-dll-sys-v140.0.0+140.1.13) - 2025-09-19
### Other
- update bindings
- *(release)* update CEF version to 140.1.13
## [139.8.0+139.0.40](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.7.2+139.0.38...cef-dll-sys-v139.8.0+139.0.40) - 2025-09-12
### Other
- update bindings
- *(release)* update CEF version to 139.0.40
## [139.7.2+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.7.1+139.0.38...cef-dll-sys-v139.7.2+139.0.38) - 2025-09-08
### Fixed
- handle out-params ([#173](https://github.com/tauri-apps/cef-rs/issues/173))
### Other
- release v139.7.2+139.0.38
- update bindings
## [139.7.1+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.7.0+139.0.38...cef-dll-sys-v139.7.1+139.0.38) - 2025-09-07
### Other
- release v139.7.1+139.0.38
- *(deps)* update rust crate windows-sys to 0.61
## [139.7.0+139.0.38](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.6.0+139.0.37...cef-dll-sys-v139.7.0+139.0.38) - 2025-08-31
### Other
- *(release)* update CEF version to 139.0.38
## [139.6.0+139.0.37](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.5.0+139.0.30...cef-dll-sys-v139.6.0+139.0.37) - 2025-08-29
### Other
- *(release)* update CEF version to 139.0.37
## [139.5.0+139.0.30](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.4.0+139.0.28...cef-dll-sys-v139.5.0+139.0.30) - 2025-08-28
### Other
- *(release)* update CEF version to 139.0.30
## [139.4.0+139.0.28](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.3.0+139.0.26...cef-dll-sys-v139.4.0+139.0.28) - 2025-08-23
### Other
- *(release)* update CEF version to 139.0.28
## [139.3.0+139.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.2.1+139.0.23...cef-dll-sys-v139.3.0+139.0.26) - 2025-08-22
### Other
- *(release)* update CEF version to 139.0.26
## [139.2.1+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.2.0+139.0.23...cef-dll-sys-v139.2.1+139.0.23) - 2025-08-16
### Other
- release
## [139.2.0+139.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.1.0+139.0.20...cef-dll-sys-v139.2.0+139.0.23) - 2025-08-16
### Other
- *(release)* update CEF version to 139.0.23
## [139.1.0+139.0.20](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.0.1+139.0.17...cef-dll-sys-v139.1.0+139.0.20) - 2025-08-15
### Other
- *(release)* update CEF version to 139.0.20
## [139.0.1+139.0.17](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v139.0.0+139.0.17...cef-dll-sys-v139.0.1+139.0.17) - 2025-08-08
### Other
- release v139.0.1+139.0.17
## [139.0.0+139.0.17](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.9.0+138.0.36...cef-dll-sys-v139.0.0+139.0.17) - 2025-08-08
### Other
- update bindings
- *(release)* update CEF version to 139.0.17
## [138.9.0+138.0.36](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.8.0+138.0.34...cef-dll-sys-v138.9.0+138.0.36) - 2025-08-07
### Other
- *(release)* update CEF version to 138.0.36
## [138.8.0+138.0.34](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.7.1+138.0.33...cef-dll-sys-v138.8.0+138.0.34) - 2025-08-02
### Fixed
- remove cef version from example dependencies
### Other
- *(release)* update CEF version to 138.0.34
## [138.7.1+138.0.33](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.7.0+138.0.33...cef-dll-sys-v138.7.1+138.0.33) - 2025-07-29
### Other
- release v138.7.1+138.0.33
- move examples into separate crates
## [138.7.0+138.0.33](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.6.1+138.0.27...cef-dll-sys-v138.7.0+138.0.33) - 2025-07-29
### Other
- update bindings
- *(release)* update CEF version to 138.0.33
## [138.6.1+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.6.0+138.0.27...cef-dll-sys-v138.6.1+138.0.27) - 2025-07-28
### Fixed
- embed git-cliff as a library in get-latest
### Other
- *(release)* bump version for get-latest updates
## [138.6.0+138.0.27](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.5.1+138.0.26...cef-dll-sys-v138.6.0+138.0.27) - 2025-07-28
### Added
- update CEF version to 138.0.27
### Fixed
- bump version for release
## [138.5.1+138.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.5.0+138.0.26...cef-dll-sys-v138.5.1+138.0.26) - 2025-07-22
### Other
- release
- *(doc)* regenerate CHANGELOG.md
## [138.5.0+138.0.26](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.4.0+138.0.25...cef-dll-sys-v138.5.0+138.0.26) - 2025-07-19
### Other
- update CEF version
## [138.4.0+138.0.25](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.3.0+138.0.23...cef-dll-sys-v138.4.0+138.0.25) - 2025-07-18
### Other
- update CEF version
## [138.3.0+138.0.23](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.2.2+138.0.21...cef-dll-sys-v138.3.0+138.0.23) - 2025-07-17
### Other
- update CEF version
## [138.2.2+138.0.21](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.2.1+138.0.21...cef-dll-sys-v138.2.2+138.0.21) - 2025-07-14
### Other
- release
- seed CHANGELOG.md files
## [138.2.1+138.0.21](https://github.com/tauri-apps/cef-rs/compare/cef-dll-sys-v138.2.0+138.0.21...cef-dll-sys-v138.2.1+138.0.21) - 2025-07-14
### Fixed
- bump major version of download-cef [#145](https://github.com/tauri-apps/cef-rs/issues/145)

View File

@ -1,26 +0,0 @@
[package]
name = "cef-dll-sys"
description = "cef-rs sys crate"
links = "cef_dll_wrapper"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[lib]
doctest = false
[features]
dox = []
sandbox = []
[package.metadata.docs.rs]
features = ["dox"]
[build-dependencies]
anyhow.workspace = true
cmake.workspace = true
download-cef.workspace = true
serde_json.workspace = true

View File

@ -1,4 +0,0 @@
# cef-dll-sys
Generated bindings for the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
C API on any supported platform.

View File

@ -1,131 +0,0 @@
#[cfg(not(feature = "dox"))]
fn main() -> anyhow::Result<()> {
use download_cef::{CefIndex, OsAndArch};
use std::{env, fs, path::PathBuf};
println!("cargo::rerun-if-changed=build.rs");
let target = env::var("TARGET")?;
let os_arch = OsAndArch::try_from(target.as_str())?;
println!("cargo::rerun-if-env-changed=FLATPAK");
println!("cargo::rerun-if-env-changed=CEF_PATH");
let cef_path_env = env::var("FLATPAK")
.map(|_| String::from("/usr/lib"))
.or_else(|_| env::var("CEF_PATH"));
let cef_dir = match cef_path_env {
Ok(cef_path) => {
// Allow overriding the CEF path with environment variables.
println!("Using CEF path from environment: {cef_path}");
download_cef::check_archive_json(&env::var("CARGO_PKG_VERSION")?, &cef_path)?;
PathBuf::from(cef_path)
}
Err(_) => {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let cef_dir = os_arch.to_string();
let cef_dir = out_dir.join(&cef_dir);
if !fs::exists(&cef_dir)? {
let cef_version = download_cef::default_version(&env::var("CARGO_PKG_VERSION")?);
let index = CefIndex::download()?;
let platform = index.platform(&target)?;
let version = platform.version(&cef_version)?;
let archive = version.download_archive(&out_dir, false)?;
let extracted_dir =
download_cef::extract_target_archive(&target, &archive, &out_dir, false)?;
if extracted_dir != cef_dir {
return Err(anyhow::anyhow!(
"extracted dir {extracted_dir:?} does not match cef_dir {cef_dir:?}",
));
}
version.write_archive_json(extracted_dir)?;
}
cef_dir
}
};
let cef_dir = cef_dir.display().to_string();
println!("cargo::metadata=CEF_DIR={cef_dir}");
println!("cargo::rustc-link-search=native={cef_dir}");
let mut cef_dll_wrapper = cmake::Config::new(&cef_dir);
cef_dll_wrapper
.generator("Ninja")
.profile("RelWithDebInfo")
.build_target("libcef_dll_wrapper");
let project_arch = match os_arch.arch {
"aarch64" => "arm64",
arch => arch,
};
let sandbox = if cfg!(feature = "sandbox") {
"ON"
} else {
"OFF"
};
match os_arch.os {
"linux" => {
println!("cargo::rustc-link-lib=dylib=cef");
}
"windows" => {
let sdk_libs = [
"comctl32.lib",
"delayimp.lib",
"mincore.lib",
"powrprof.lib",
"propsys.lib",
"runtimeobject.lib",
"setupapi.lib",
"shcore.lib",
"shell32.lib",
"shlwapi.lib",
"user32.lib",
"version.lib",
"wbemuuid.lib",
"winmm.lib",
]
.join(" ");
let build_dir = cef_dll_wrapper
.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded")
.define("CMAKE_OBJECT_PATH_MAX", "500")
.define("CMAKE_STATIC_LINKER_FLAGS", &sdk_libs)
.define("PROJECT_ARCH", project_arch)
.define("USE_SANDBOX", sandbox)
.build()
.display()
.to_string();
println!("cargo::rustc-link-search=native={build_dir}/build/libcef_dll_wrapper");
println!("cargo::rustc-link-lib=static=libcef_dll_wrapper");
println!("cargo::rustc-link-lib=dylib=libcef");
}
"macos" => {
println!("cargo::rustc-link-lib=framework=AppKit");
let build_dir = cef_dll_wrapper
.no_default_flags(true)
.define("PROJECT_ARCH", project_arch)
.define("USE_SANDBOX", sandbox)
.build()
.display()
.to_string();
println!("cargo::rustc-link-search=native={build_dir}/build/libcef_dll_wrapper");
println!("cargo::rustc-link-lib=static=cef_dll_wrapper");
}
os => unimplemented!("unknown target {os}"),
}
Ok(())
}
#[cfg(feature = "dox")]
fn main() {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,39 +0,0 @@
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
mod x86_64_unknown_linux_gnu;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub use x86_64_unknown_linux_gnu::*;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
mod aarch64_unknown_linux_gnu;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
pub use aarch64_unknown_linux_gnu::*;
#[cfg(all(target_os = "linux", target_arch = "arm"))]
mod arm_unknown_linux_gnueabi;
#[cfg(all(target_os = "linux", target_arch = "arm"))]
pub use arm_unknown_linux_gnueabi::*;
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
mod x86_64_pc_windows_msvc;
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
pub use x86_64_pc_windows_msvc::*;
#[cfg(all(target_os = "windows", target_arch = "x86"))]
mod i686_pc_windows_msvc;
#[cfg(all(target_os = "windows", target_arch = "x86"))]
pub use i686_pc_windows_msvc::*;
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
mod aarch64_pc_windows_msvc;
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
pub use aarch64_pc_windows_msvc::*;
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
mod x86_64_apple_darwin;
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
pub use x86_64_apple_darwin::*;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod aarch64_apple_darwin;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub use aarch64_apple_darwin::*;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,91 +0,0 @@
#![doc = include_str!("../README.md")]
#[allow(
non_snake_case,
non_camel_case_types,
non_upper_case_globals,
dead_code,
clippy::all
)]
mod bindings;
pub use bindings::*;
#[cfg(target_os = "windows")]
impl Default for HWND {
fn default() -> Self {
Self(std::ptr::null_mut())
}
}
#[cfg(target_os = "windows")]
impl Default for HINSTANCE {
fn default() -> Self {
Self(std::ptr::null_mut())
}
}
#[cfg(target_os = "macos")]
pub const FRAMEWORK_PATH: &str =
"Chromium Embedded Framework.framework/Chromium Embedded Framework";
use std::{
env::{
self,
consts::{ARCH, OS},
},
fs,
path::PathBuf,
};
pub fn get_cef_dir() -> Option<PathBuf> {
let cef_path_env = env::var("FLATPAK")
.map(|_| String::from("/usr/lib"))
.or_else(|_| env::var("CEF_PATH"));
match cef_path_env {
Ok(cef_path) => {
// Allow overriding the CEF path with environment variables.
PathBuf::from(cef_path).canonicalize().ok()
}
Err(_) => {
let out_dir = PathBuf::from(env::var("OUT_DIR").ok()?);
let cef_dir = format!("cef_{OS}_{ARCH}");
let cef_dir = out_dir.join(&cef_dir).canonicalize().ok()?;
fs::exists(&cef_dir).ok()?.then_some(cef_dir)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_cef_dir() {
let _ = get_cef_dir().expect("CEF not found");
}
#[test]
fn test_init() {
use std::ptr::*;
unsafe {
#[cfg(target_os = "macos")]
{
use std::os::unix::ffi::OsStrExt;
let cef_dir = get_cef_dir().expect("CEF not found");
let framework_dir = cef_dir
.join(FRAMEWORK_PATH)
.canonicalize()
.expect("failed to get framework path");
let framework_dir = std::ffi::CString::new(framework_dir.as_os_str().as_bytes())
.expect("invalid path");
assert_eq!(cef_load_library(framework_dir.as_ptr().cast()), 1);
}
assert_eq!(cef_initialize(null(), null(), null_mut(), null_mut()), 0);
};
}
}

View File

@ -1,33 +0,0 @@
#ifndef CEF_RUST_SYS_WRAPPER_H
#define CEF_RUST_SYS_WRAPPER_H
#ifdef __APPLE__
#include "include/wrapper/cef_library_loader.h"
#include "include/cef_sandbox_mac.h"
#endif
#include "include/cef_api_hash.h"
#include "include/cef_version.h"
#include "include/capi/cef_base_capi.h"
#include "include/capi/cef_app_capi.h"
#include "include/capi/cef_client_capi.h"
#include "include/capi/cef_urlrequest_capi.h"
#include "include/capi/views/cef_layout_capi.h"
#include "include/capi/views/cef_box_layout_capi.h"
#include "include/capi/views/cef_fill_layout_capi.h"
#include "include/capi/views/cef_button_capi.h"
#include "include/capi/views/cef_label_button_capi.h"
#include "include/capi/views/cef_menu_button_capi.h"
#include "include/capi/views/cef_textfield_capi.h"
#include "include/capi/views/cef_browser_view_capi.h"
#include "include/capi/views/cef_scroll_view_capi.h"
#include "include/capi/views/cef_window_capi.h"
#endif

View File

@ -1,22 +0,0 @@
[package]
name = "update-bindings"
publish = false
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
download-cef.workspace = true
bindgen.workspace = true
clap.workspace = true
convert_case.workspace = true
proc-macro2.workspace = true
quote.workspace = true
regex.workspace = true
semver.workspace = true
syn.workspace = true
thiserror.workspace = true

View File

@ -1,8 +0,0 @@
# update-bindings
Download the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
archive on any supported platform and run `bindgen` on the C API for the `cef-dll-sys` crate,
then regenerate the safe bindings in the `cef` crate.
You can find the latest version of the prebuilt CEF archives on the [Chromium Embedded Framework
(CEF) Automated Builds](https://cef-builds.spotifycdn.com/index.html).

View File

@ -1,2 +0,0 @@
// Empty build.rs file to set OUT_DIR environment variable
fn main() {}

View File

@ -1,39 +0,0 @@
use std::{convert::From, env, path::PathBuf};
pub fn get_manifest_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
pub fn get_out_dir() -> PathBuf {
PathBuf::from(env!("OUT_DIR"))
}
pub fn get_cef_root(os: &str, arch: &str) -> PathBuf {
env::var(format!("CEF_PATH_{os}_{arch}"))
.map(PathBuf::from)
.unwrap_or_else(|_| {
let mut out_dir = get_out_dir();
out_dir.push(format!("cef_{os}_{arch}"));
out_dir
})
}
pub fn get_sys_dir() -> crate::Result<PathBuf> {
let manifest_dir = get_manifest_dir();
let mut bindings_dir = get_manifest_dir().parent().map_or_else(
|| Err(crate::Error::MissingParent(manifest_dir)),
|parent| Ok(PathBuf::from(parent)),
)?;
bindings_dir.push("sys");
Ok(bindings_dir)
}
pub fn get_cef_dir() -> crate::Result<PathBuf> {
let manifest_dir = get_manifest_dir();
let mut webview2_com_dir = get_manifest_dir().parent().map_or_else(
|| Err(crate::Error::MissingParent(manifest_dir)),
|parent| Ok(PathBuf::from(parent)),
)?;
webview2_com_dir.push("cef");
Ok(webview2_com_dir)
}

View File

@ -1,102 +0,0 @@
#![doc = include_str!("../README.md")]
#[macro_use]
extern crate thiserror;
use clap::Parser;
use download_cef::DEFAULT_TARGET;
use std::{fs, io::Read, path::Path, sync::OnceLock};
#[derive(Debug, Error)]
pub enum Error {
#[error("Missing Parent")]
MissingParent(std::path::PathBuf),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Bindgen(#[from] bindgen::BindgenError),
#[error(transparent)]
Regex(#[from] regex::Error),
#[error(transparent)]
Syn(#[from] syn::Error),
#[error("Parsing bindgen output failed")]
Parse(#[from] parse_tree::Unrecognized),
#[error("Missing Path")]
MissingPath(std::path::PathBuf),
}
pub type Result<T> = std::result::Result<T, Error>;
mod dirs;
mod parse_tree;
mod upgrade;
fn default_version() -> &'static str {
static DEFAULT_VERSION: OnceLock<String> = OnceLock::new();
DEFAULT_VERSION
.get_or_init(|| download_cef::default_version(env!("CARGO_PKG_VERSION")))
.as_str()
}
fn default_download_url() -> &'static str {
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
DEFAULT_DOWNLOAD_URL
.get_or_init(|| download_cef::default_download_url())
.as_str()
}
#[derive(Parser, Debug)]
#[command(about, long_about = None)]
struct Args {
#[arg(short, long)]
download: bool,
#[arg(short, long)]
bindgen: bool,
#[arg(short, long, default_value = DEFAULT_TARGET)]
target: String,
#[arg(short, long, default_value = default_version())]
version: String,
#[arg(short, long, default_value = default_download_url())]
mirror_url: String,
}
fn main() -> Result<()> {
let args = Args::parse();
let target = args.target.as_str();
if args.bindgen {
if args.download {
let _ = upgrade::download(args.mirror_url.as_str(), target, args.version.as_str());
}
upgrade::sys_bindgen(target)?;
}
let bindings_file = upgrade::get_target_bindings(target);
let mut sys_bindings = dirs::get_sys_dir()?;
sys_bindings.push("src");
sys_bindings.push("bindings");
sys_bindings.push(&bindings_file);
let mut cef_bindings = dirs::get_cef_dir()?;
cef_bindings.push("src");
cef_bindings.push("bindings");
cef_bindings.push(&bindings_file);
let bindings = parse_tree::generate_bindings(&sys_bindings)?;
let source = read_bindings(&bindings)?;
let dest = read_bindings(&cef_bindings).unwrap_or_default();
if source != dest {
fs::copy(&bindings, &cef_bindings)?;
println!("Updated: {}", cef_bindings.display());
}
Ok(())
}
fn read_bindings(source_path: &Path) -> crate::Result<String> {
let mut source_file = fs::File::open(source_path)?;
let mut updated = String::default();
source_file.read_to_string(&mut updated)?;
Ok(updated)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,102 +0,0 @@
use crate::dirs;
use std::{
path::{Path, PathBuf},
process::Command,
};
const TARGETS: &[&str] = &[
// macos
"aarch64-apple-darwin",
"x86_64-apple-darwin",
// windows
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"i686-pc-windows-msvc",
// linux
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"arm-unknown-linux-gnueabi",
];
pub fn download(url: &str, target: &str, version: &str) -> PathBuf {
assert!(TARGETS.contains(&target), "unsupported target {target}");
let archive =
download_cef::download_target_archive_from(url, target, version, dirs::get_out_dir(), true)
.expect("download failed");
download_cef::extract_target_archive(target, &archive, dirs::get_out_dir(), true)
.expect("extraction failed")
}
pub fn sys_bindgen(target: &str) -> crate::Result<()> {
assert!(TARGETS.contains(&target), "unsupported target {target}");
let (os, arch) = target_to_os_arch(target);
let cef_path = dirs::get_cef_root(os, arch);
bindgen(target, &cef_path)
}
pub fn get_target_bindings(target: &str) -> String {
assert!(TARGETS.contains(&target), "unsupported target {target}");
format!("{}.rs", target.replace('-', "_"))
}
fn bindgen(target: &str, cef_path: &Path) -> crate::Result<()> {
let mut sys_bindings = dirs::get_sys_dir()?;
let mut wrapper = sys_bindings.clone();
sys_bindings.push("src");
sys_bindings.push("bindings");
sys_bindings.push(format!("{}.rs", target.replace('-', "_")));
wrapper.push("wrapper.h");
let mut bindings = bindgen::Builder::default()
.header(wrapper.display().to_string())
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: true,
})
.allowlist_type("cef_.*")
.allowlist_function("cef_.*")
.allowlist_item("CEF_API_VERSION(_.+)?")
.allowlist_item("CEF_VERSION(_.+)?")
.allowlist_item("CHROME_VERSION(_.+)?")
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.bitfield_enum(".*_mask_t")
.clang_args([
format!("-I{}", cef_path.display()),
format!("--target={target}"),
]);
if target.contains("windows") {
bindings = bindings.new_type_alias("HINSTANCE").new_type_alias("HWND");
} else if target.contains("apple") {
let sdk_path = Command::new("xcrun")
.args(["--sdk", "macosx", "--show-sdk-path"])
.output()
.unwrap()
.stdout;
bindings = bindings.clang_arg(format!(
"--sysroot={}",
String::from_utf8_lossy(&sdk_path).trim()
));
}
let bindings = bindings.generate()?;
bindings.write_to_file(&sys_bindings)?;
Ok(())
}
fn target_to_os_arch(target: &str) -> (&str, &str) {
match target {
"aarch64-apple-darwin" => ("macos", "aarch64"),
"x86_64-apple-darwin" => ("macos", "x86_64"),
"x86_64-pc-windows-msvc" => ("windows", "x86_64"),
"aarch64-pc-windows-msvc" => ("windows", "aarch64"),
"i686-pc-windows-msvc" => ("windows", "x86"),
"x86_64-unknown-linux-gnu" => ("linux", "x86_64"),
"aarch64-unknown-linux-gnu" => ("linux", "aarch64"),
"arm-unknown-linux-gnueabi" => ("linux", "arm"),
v => panic!("unsupported {v:?}"),
}
}

View File

@ -13,7 +13,7 @@ import * as core from '@janhq/core'
## Build an Extension ## Build an Extension
1. Download an extension template, for example, [https://github.com/menloresearch/extension-template](https://github.com/menloresearch/extension-template). 1. Download an extension template, for example, [https://github.com/janhq/extension-template](https://github.com/janhq/extension-template).
2. Update the source code: 2. Update the source code:

View File

@ -31,7 +31,7 @@
"@vitest/coverage-v8": "^2.1.8", "@vitest/coverage-v8": "^2.1.8",
"@vitest/ui": "^2.1.8", "@vitest/ui": "^2.1.8",
"eslint": "8.57.0", "eslint": "8.57.0",
"happy-dom": "^15.11.6", "happy-dom": "^20.0.0",
"pacote": "^21.0.0", "pacote": "^21.0.0",
"react": "19.0.0", "react": "19.0.0",
"request": "^2.88.2", "request": "^2.88.2",

View File

@ -11,6 +11,8 @@ export enum ExtensionTypeEnum {
HuggingFace = 'huggingFace', HuggingFace = 'huggingFace',
Engine = 'engine', Engine = 'engine',
Hardware = 'hardware', Hardware = 'hardware',
RAG = 'rag',
VectorDB = 'vectorDB',
} }
export interface ExtensionType { export interface ExtensionType {

View File

@ -182,6 +182,7 @@ export interface SessionInfo {
port: number // llama-server output port (corrected from portid) port: number // llama-server output port (corrected from portid)
model_id: string //name of the model model_id: string //name of the model
model_path: string // path of the loaded model model_path: string // path of the loaded model
is_embedding: boolean
api_key: string api_key: string
mmproj_path?: string mmproj_path?: string
} }

View File

@ -23,3 +23,8 @@ export { MCPExtension } from './mcp'
* Base AI Engines. * Base AI Engines.
*/ */
export * from './engines' export * from './engines'
export { RAGExtension, RAG_INTERNAL_SERVER } from './rag'
export type { AttachmentInput, IngestAttachmentsResult } from './rag'
export { VectorDBExtension } from './vector-db'
export type { SearchMode, VectorDBStatus, VectorChunkInput, VectorSearchResult, AttachmentFileInfo, VectorDBFileInput, VectorDBIngestOptions } from './vector-db'

View File

@ -0,0 +1,36 @@
import { BaseExtension, ExtensionTypeEnum } from '../extension'
import type { MCPTool, MCPToolCallResult } from '../../types'
import type { AttachmentFileInfo } from './vector-db'
export interface AttachmentInput {
path: string
name?: string
type?: string
size?: number
}
export interface IngestAttachmentsResult {
filesProcessed: number
chunksInserted: number
files: AttachmentFileInfo[]
}
export const RAG_INTERNAL_SERVER = 'rag-internal'
/**
* RAG extension base: exposes RAG tools and orchestration API.
*/
export abstract class RAGExtension extends BaseExtension {
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.RAG
}
abstract getTools(): Promise<MCPTool[]>
/**
* Lightweight list of tool names for quick routing/lookup.
*/
abstract getToolNames(): Promise<string[]>
abstract callTool(toolName: string, args: Record<string, unknown>): Promise<MCPToolCallResult>
abstract ingestAttachments(threadId: string, files: AttachmentInput[]): Promise<IngestAttachmentsResult>
}

View File

@ -0,0 +1,82 @@
import { BaseExtension, ExtensionTypeEnum } from '../extension'
export type SearchMode = 'auto' | 'ann' | 'linear'
export interface VectorDBStatus {
ann_available: boolean
}
export interface VectorChunkInput {
text: string
embedding: number[]
}
export interface VectorSearchResult {
id: string
text: string
score?: number
file_id: string
chunk_file_order: number
}
export interface AttachmentFileInfo {
id: string
name?: string
path?: string
type?: string
size?: number
chunk_count: number
}
// High-level input types for file ingestion
export interface VectorDBFileInput {
path: string
name?: string
type?: string
size?: number
}
export interface VectorDBIngestOptions {
chunkSize: number
chunkOverlap: number
}
/**
* Vector DB extension base: abstraction over local vector storage and search.
*/
export abstract class VectorDBExtension extends BaseExtension {
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.VectorDB
}
abstract getStatus(): Promise<VectorDBStatus>
abstract createCollection(threadId: string, dimension: number): Promise<void>
abstract insertChunks(
threadId: string,
fileId: string,
chunks: VectorChunkInput[]
): Promise<void>
abstract ingestFile(
threadId: string,
file: VectorDBFileInput,
opts: VectorDBIngestOptions
): Promise<AttachmentFileInfo>
abstract searchCollection(
threadId: string,
query_embedding: number[],
limit: number,
threshold: number,
mode?: SearchMode,
fileIds?: string[]
): Promise<VectorSearchResult[]>
abstract deleteChunks(threadId: string, ids: string[]): Promise<void>
abstract deleteFile(threadId: string, fileId: string): Promise<void>
abstract deleteCollection(threadId: string): Promise<void>
abstract listAttachments(threadId: string, limit?: number): Promise<AttachmentFileInfo[]>
abstract getChunks(
threadId: string,
fileId: string,
startOrder: number,
endOrder: number
): Promise<VectorSearchResult[]>
}

View File

@ -12,6 +12,8 @@ export type SettingComponentProps = {
extensionName?: string extensionName?: string
requireModelReload?: boolean requireModelReload?: boolean
configType?: ConfigType configType?: ConfigType
titleKey?: string
descriptionKey?: string
} }
export type ConfigType = 'runtime' | 'setting' export type ConfigType = 'runtime' | 'setting'

View File

@ -18,7 +18,7 @@ We try to **keep routes consistent** to maintain SEO.
## How to Contribute ## How to Contribute
Refer to the [Contributing Guide](https://github.com/menloresearch/jan/blob/main/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project. Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/main/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
### Pre-requisites and Installation ### Pre-requisites and Installation

View File

@ -1581,7 +1581,7 @@
}, },
"cover": { "cover": {
"type": "string", "type": "string",
"example": "https://raw.githubusercontent.com/menloresearch/jan/main/models/trinity-v1.2-7b/cover.png" "example": "https://raw.githubusercontent.com/janhq/jan/main/models/trinity-v1.2-7b/cover.png"
}, },
"engine": { "engine": {
"type": "string", "type": "string",

View File

@ -27,7 +27,7 @@ export const APIReference = () => {
<ApiReferenceReact <ApiReferenceReact
configuration={{ configuration={{
spec: { spec: {
url: 'https://raw.githubusercontent.com/menloresearch/docs/main/public/openapi/jan.json', url: 'https://raw.githubusercontent.com/janhq/docs/main/public/openapi/jan.json',
}, },
theme: 'alternate', theme: 'alternate',
hideModels: true, hideModels: true,

View File

@ -57,7 +57,7 @@ const Changelog = () => {
<p className="text-base mt-2 leading-relaxed"> <p className="text-base mt-2 leading-relaxed">
Latest release updates from the Jan team. Check out our&nbsp; Latest release updates from the Jan team. Check out our&nbsp;
<a <a
href="https://github.com/orgs/menloresearch/projects/30" href="https://github.com/orgs/janhq/projects/30"
className="text-blue-600 dark:text-blue-400 cursor-pointer" className="text-blue-600 dark:text-blue-400 cursor-pointer"
> >
Roadmap Roadmap
@ -150,7 +150,7 @@ const Changelog = () => {
<div className="text-center"> <div className="text-center">
<Link <Link
href="https://github.com/menloresearch/jan/releases" href="https://github.com/janhq/jan/releases"
target="_blank" target="_blank"
className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 px-4 py-3 rounded-xl inline-flex items-center" className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 px-4 py-3 rounded-xl inline-flex items-center"
> >

View File

@ -72,7 +72,7 @@ export default function CardDownload({ lastRelease }: Props) {
return { return {
...system, ...system,
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`, href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
size: asset ? formatFileSize(asset.size) : undefined, size: asset ? formatFileSize(asset.size) : undefined,
} }
}) })

View File

@ -139,7 +139,7 @@ const DropdownDownload = ({ lastRelease }: Props) => {
return { return {
...system, ...system,
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`, href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
size: asset ? formatFileSize(asset.size) : undefined, size: asset ? formatFileSize(asset.size) : undefined,
} }
}) })

View File

@ -23,7 +23,7 @@ const BuiltWithLove = () => {
</div> </div>
<div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center"> <div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center">
<a <a
href="https://github.com/menloresearch/jan" href="https://github.com/janhq/jan"
target="_blank" target="_blank"
className="dark:bg-white bg-black inline-flex w-56 px-4 py-3 rounded-xl cursor-pointer justify-center items-start space-x-4 " className="dark:bg-white bg-black inline-flex w-56 px-4 py-3 rounded-xl cursor-pointer justify-center items-start space-x-4 "
> >

View File

@ -44,7 +44,7 @@ const Hero = () => {
<div className="mt-10 text-center"> <div className="mt-10 text-center">
<div> <div>
<Link <Link
href="https://github.com/menloresearch/jan/releases" href="https://github.com/janhq/jan/releases"
target="_blank" target="_blank"
className="hidden lg:inline-block" className="hidden lg:inline-block"
> >

View File

@ -95,7 +95,7 @@ const Home = () => {
<div className="container mx-auto relative z-10"> <div className="container mx-auto relative z-10">
<div className="flex justify-center items-center mt-14 lg:mt-20 px-4"> <div className="flex justify-center items-center mt-14 lg:mt-20 px-4">
<a <a
href={`https://github.com/menloresearch/jan/releases/tag/${lastVersion}`} href={`https://github.com/janhq/jan/releases/tag/${lastVersion}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="bg-black/40 px-3 lg:px-4 rounded-full h-10 inline-flex items-center max-w-full animate-fade-in delay-100" className="bg-black/40 px-3 lg:px-4 rounded-full h-10 inline-flex items-center max-w-full animate-fade-in delay-100"
@ -270,7 +270,7 @@ const Home = () => {
data-delay="600" data-delay="600"
> >
<a <a
href="https://github.com/menloresearch/jan" href="https://github.com/janhq/jan"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -387,7 +387,7 @@ const Home = () => {
</div> </div>
<a <a
className="hidden md:block" className="hidden md:block"
href="https://github.com/menloresearch/jan" href="https://github.com/janhq/jan"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -413,7 +413,7 @@ const Home = () => {
</p> </p>
<a <a
className="md:hidden mt-4 block w-full" className="md:hidden mt-4 block w-full"
href="https://github.com/menloresearch/jan" href="https://github.com/janhq/jan"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@ -95,7 +95,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
})} })}
<li> <li>
<a <a
href="https://github.com/menloresearch/jan/releases/latest" href="https://github.com/janhq/jan/releases/latest"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -141,7 +141,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
<FaLinkedinIn className="size-5" /> <FaLinkedinIn className="size-5" />
</a> </a>
<a <a
href="https://github.com/menloresearch/jan" href="https://github.com/janhq/jan"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="rounded-lg flex items-center justify-center" className="rounded-lg flex items-center justify-center"
@ -156,7 +156,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
{/* Mobile Download Button and Hamburger */} {/* Mobile Download Button and Hamburger */}
<div className="lg:hidden flex items-center gap-3"> <div className="lg:hidden flex items-center gap-3">
<a <a
href="https://github.com/menloresearch/jan/releases/latest" href="https://github.com/janhq/jan/releases/latest"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -278,7 +278,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
<FaLinkedinIn className="size-5" /> <FaLinkedinIn className="size-5" />
</a> </a>
<a <a
href="https://github.com/menloresearch/jan" href="https://github.com/janhq/jan"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-black rounded-lg flex items-center justify-center" className="text-black rounded-lg flex items-center justify-center"
@ -296,7 +296,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
asChild asChild
> >
<a <a
href="https://github.com/menloresearch/jan/releases/latest" href="https://github.com/janhq/jan/releases/latest"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@ -120,7 +120,7 @@ export function DropdownButton({
return { return {
...option, ...option,
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${fileName}`, href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${fileName}`,
size: asset ? formatFileSize(asset.size) : 'N/A', size: asset ? formatFileSize(asset.size) : 'N/A',
} }
}) })

View File

@ -18,7 +18,7 @@ description: Development setup, workflow, and contribution guidelines for Jan Se
1. **Clone Repository** 1. **Clone Repository**
```bash ```bash
git clone https://github.com/menloresearch/jan-server git clone https://github.com/janhq/jan-server
cd jan-server cd jan-server
``` ```

View File

@ -19,7 +19,7 @@ Jan Server currently supports minikube for local development. Production Kuberne
1. **Clone the repository** 1. **Clone the repository**
```bash ```bash
git clone https://github.com/menloresearch/jan-server git clone https://github.com/janhq/jan-server
cd jan-server cd jan-server
``` ```

View File

@ -24,4 +24,4 @@ Fixes 💫
Update your product or download the latest: https://jan.ai Update your product or download the latest: https://jan.ai
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.5). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.5).

View File

@ -24,4 +24,4 @@ Jan now supports Mistral's new model Codestral. Thanks [Bartowski](https://huggi
More GGUF models can run in Jan - we rebased to llama.cpp b3012.Big thanks to [ggerganov](https://github.com/ggerganov) More GGUF models can run in Jan - we rebased to llama.cpp b3012.Big thanks to [ggerganov](https://github.com/ggerganov)
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.0). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.0).

View File

@ -28,4 +28,4 @@ Jan now understands LaTeX, allowing users to process and understand complex math
![Latex](https://catalog.jan.ai/docs/jan_update_latex.gif) ![Latex](https://catalog.jan.ai/docs/jan_update_latex.gif)
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.12). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.4.12).

View File

@ -28,4 +28,4 @@ Users can now connect to OpenAI's new model GPT-4o.
![GPT4o](https://catalog.jan.ai/docs/jan_v0_4_13_openai_gpt4o.gif) ![GPT4o](https://catalog.jan.ai/docs/jan_v0_4_13_openai_gpt4o.gif)
For more details, see the [GitHub release notes.](https://github.com/menloresearch/jan/releases/tag/v0.4.13) For more details, see the [GitHub release notes.](https://github.com/janhq/jan/releases/tag/v0.4.13)

View File

@ -16,4 +16,4 @@ More GGUF models can run in Jan - we rebased to llama.cpp b2961.
Huge shoutouts to [ggerganov](https://github.com/ggerganov) and contributors for llama.cpp, and [Bartowski](https://huggingface.co/bartowski) for GGUF models. Huge shoutouts to [ggerganov](https://github.com/ggerganov) and contributors for llama.cpp, and [Bartowski](https://huggingface.co/bartowski) for GGUF models.
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.14). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.4.14).

View File

@ -26,4 +26,4 @@ We've updated to llama.cpp b3088 for better performance - thanks to [GG](https:/
- Reduced chat font weight (back to normal!) - Reduced chat font weight (back to normal!)
- Restored the maximize button - Restored the maximize button
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.1). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.1).

View File

@ -32,4 +32,4 @@ We've restored the tooltip hover functionality, which makes it easier to access
The right-click options for thread settings are now fully operational again. You can now manage your threads with this fix. The right-click options for thread settings are now fully operational again. You can now manage your threads with this fix.
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.2). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.2).

View File

@ -23,4 +23,4 @@ We've been working on stability issues over the last few weeks. Jan is now more
- Fixed the GPU memory utilization bar - Fixed the GPU memory utilization bar
- Some UX and copy improvements - Some UX and copy improvements
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.3). For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.3).

Some files were not shown because too many files have changed in this diff Show More