Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tauri v2 and mobile platforms #5927

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ For enterprise inquiries, please contact: **[email protected]**
## Features

- **Deploy for free with one-click** on Vercel in under 1 minute
- Compact client (~5MB) on Linux/Windows/MacOS, [download it now](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
- Compact client (~5MB) on Linux/Windows/MacOS/IOS/Android, [download it now](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
- Fully compatible with self-deployed LLMs, recommended for use with [RWKV-Runner](https://github.com/josStorer/RWKV-Runner) or [LocalAI](https://github.com/go-skynet/LocalAI)
- Privacy first, all data is stored locally in the browser
- Markdown support: LaTex, mermaid, code highlight, etc.
Expand Down Expand Up @@ -113,7 +113,7 @@ For enterprise inquiries, please contact: **[email protected]**
## 主要功能

- 在 1 分钟内使用 Vercel **免费一键部署**
- 提供体积极小(~5MB)的跨平台客户端(Linux/Windows/MacOS), [下载地址](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
- 提供体积极小(~5MB)的跨平台客户端(Linux/Windows/MacOS/IOS/Android), [下载地址](https://github.com/Yidadaa/ChatGPT-Next-Web/releases)
- 完整的 Markdown 支持:LaTex 公式、Mermaid 流程图、代码高亮等等
- 精心设计的 UI,响应式设计,支持深色模式,支持 PWA
- 极快的首屏加载速度(~100kb),支持流式响应
Expand Down
4 changes: 2 additions & 2 deletions app/components/exporter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ export function ImagePreviewer(props: {

if (isMobile || (isApp && window.__TAURI__)) {
if (isApp && window.__TAURI__) {
const result = await window.__TAURI__.dialog.save({
const result = await window.__TAURI__.core.dialog.save({
defaultPath: `${props.topic}.png`,
filters: [
{
Expand All @@ -491,7 +491,7 @@ export function ImagePreviewer(props: {
const response = await fetch(blob);
const buffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(buffer);
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
await window.__TAURI__.core.fs.writeBinaryFile(result, uint8Array);
showToast(Locale.Download.Success);
} else {
showToast(Locale.Download.Failed);
Expand Down
2 changes: 1 addition & 1 deletion app/config/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const getBuildConfig = () => {

const buildMode = process.env.BUILD_MODE ?? "standalone";
const isApp = !!process.env.BUILD_APP;
const version = "v" + tauriConfig.package.version;
const version = "v" + tauriConfig.version;

const commitInfo = (() => {
try {
Expand Down
50 changes: 26 additions & 24 deletions app/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,34 @@ declare module "*.svg";

declare interface Window {
__TAURI__?: {
writeText(text: string): Promise<void>;
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
core: {
writeText(text: string): Promise<void>;
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
fs: {
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
writeTextFile(path: string, data: string): Promise<void>;
};
notification: {
requestPermission(): Promise<Permission>;
isPermissionGranted(): Promise<boolean>;
sendNotification(options: string | Options): void;
};
updater: {
checkUpdate(): Promise<UpdateResult>;
installUpdate(): Promise<void>;
onUpdaterEvent(
handler: (status: UpdateStatusResult) => void,
): Promise<UnlistenFn>;
};
http: {
fetch<T>(
url: string,
options?: Record<string, unknown>,
): Promise<Response<T>>;
};
};
dialog: {
save(options?: Record<string, unknown>): Promise<string | null>;
};
Comment on lines 41 to 43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Inconsistent dialog API usage found in codebase

The codebase shows two different patterns for using the dialog API:

  • In app/utils.ts: Using window.__TAURI__.dialog.save
  • In app/components/exporter.tsx: Using window.__TAURI__.core.dialog.save

This inconsistency needs to be addressed:

  • app/utils.ts needs to be updated to use the new core namespace pattern
  • The type definition in global.d.ts should be moved under the core namespace to match the actual usage in exporter.tsx
🔗 Analysis chain

Review dialog API placement and mobile compatibility

Two concerns to address:

  1. The dialog API remains outside the core namespace while other APIs have been moved inside. Consider moving it to maintain consistency with the new structure.
  2. The PR mentions that export configuration doesn't work on mobile platforms. Since this likely involves the dialog.save method, consider adding platform-specific type guards.

Run the following script to check how dialog API is used across the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for dialog usage patterns
rg "dialog\.save" --type ts

Length of output: 193


Script:

#!/bin/bash
# Let's check the full context of both files to understand the dialog usage better
echo "=== app/utils.ts ==="
rg "dialog\.save" app/utils.ts -B 5 -A 5

echo -e "\n=== app/components/exporter.tsx ==="
rg "dialog\.save" app/components/exporter.tsx -B 5 -A 5

Length of output: 947

fs: {
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
writeTextFile(path: string, data: string): Promise<void>;
};
notification: {
requestPermission(): Promise<Permission>;
isPermissionGranted(): Promise<boolean>;
sendNotification(options: string | Options): void;
};
updater: {
checkUpdate(): Promise<UpdateResult>;
installUpdate(): Promise<void>;
onUpdaterEvent(
handler: (status: UpdateStatusResult) => void,
): Promise<UnlistenFn>;
};
http: {
fetch<T>(
url: string,
options?: Record<string, unknown>,
): Promise<Response<T>>;
};
};
}
10 changes: 5 additions & 5 deletions app/store/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,22 @@ export const useUpdateStore = createPersistStore(
set(() => ({
remoteVersion: remoteId,
}));
if (window.__TAURI__?.notification && isApp) {
if (window.__TAURI__?.core.notification && isApp) {
// Check if notification permission is granted
await window.__TAURI__?.notification
await window.__TAURI__?.core.notification
.isPermissionGranted()
.then((granted) => {
if (!granted) {
return;
} else {
// Request permission to show notifications
window.__TAURI__?.notification
window.__TAURI__?.core.notification
.requestPermission()
.then((permission) => {
if (permission === "granted") {
if (version === remoteId) {
// Show a notification using Tauri
window.__TAURI__?.notification.sendNotification({
window.__TAURI__?.core.notification.sendNotification({
title: "NextChat",
body: `${Locale.Settings.Update.IsLatest}`,
icon: `${ChatGptIcon.src}`,
Expand All @@ -114,7 +114,7 @@ export const useUpdateStore = createPersistStore(
const updateMessage =
Locale.Settings.Update.FoundUpdate(`${remoteId}`);
// Show a notification for the new version using Tauri
window.__TAURI__?.notification.sendNotification({
window.__TAURI__?.core.notification.sendNotification({
title: "NextChat",
body: updateMessage,
icon: `${ChatGptIcon.src}`,
Expand Down
8 changes: 4 additions & 4 deletions app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function trimTopic(topic: string) {
export async function copyToClipboard(text: string) {
try {
if (window.__TAURI__) {
window.__TAURI__.writeText(text);
window.__TAURI__.core.writeText(text);
} else {
await navigator.clipboard.writeText(text);
}
Expand Down Expand Up @@ -61,7 +61,7 @@ export async function downloadAs(text: string, filename: string) {

if (result !== null) {
try {
await window.__TAURI__.fs.writeTextFile(result, text);
await window.__TAURI__.core.fs.writeTextFile(result, text);
showToast(Locale.Download.Success);
} catch (error) {
showToast(Locale.Download.Failed);
Expand Down Expand Up @@ -396,11 +396,11 @@ export function getOperationId(operation: {

export function clientUpdate() {
// this a wild for updating client app
return window.__TAURI__?.updater
return window.__TAURI__?.core.updater
.checkUpdate()
.then((updateResult) => {
if (updateResult.shouldUpdate) {
window.__TAURI__?.updater
window.__TAURI__?.core.updater
.installUpdate()
.then((result) => {
showToast(Locale.Settings.Update.Success);
Expand Down
2 changes: 1 addition & 1 deletion app/utils/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function fetch(url: string, options?: RequestInit): Promise<Response> {
for (const item of new Headers(_headers || {})) {
headers[item[0]] = item[1];
}
return window.__TAURI__
return window.__TAURI__.core
.invoke("stream_fetch", {
method: method.toUpperCase(),
url,
Expand Down
15 changes: 15 additions & 0 deletions docs/mobile-build-instruction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Build Instruction for IOS and Android
## Android
Run command
```
yarn
yarn tauri android build
```
## IOS
IOS build requires Mac and a developer account or ios certificate.
See [here](https://tauri.app/distribute/sign/ios/) for detail.\
After you have the signing requirement. Run command to build
```
yarn
yarn tauri ios build
```
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"@hello-pangea/dnd": "^16.5.0",
"@next/third-parties": "^14.1.0",
"@svgr/webpack": "^6.5.1",
"@tauri-apps/plugin-clipboard-manager": "^2.2.0",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-fs": "~2",
"@tauri-apps/plugin-http": "~2",
"@tauri-apps/plugin-notification": "~2",
"@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-updater": "~2",
"@tauri-apps/plugin-window-state": "^2.2.0",
"@vercel/analytics": "^0.1.11",
"@vercel/speed-insights": "^1.0.2",
"axios": "^1.7.5",
Expand All @@ -49,15 +57,15 @@
"remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"rt-client": "https://github.com/Azure-Samples/aoai-realtime-audio-sdk/releases/download/js/v0.5.0/rt-client-0.5.0.tgz",
"sass": "^1.59.2",
"spark-md5": "^3.0.2",
"use-debounce": "^9.0.4",
"zustand": "^4.3.8",
"rt-client": "https://github.com/Azure-Samples/aoai-realtime-audio-sdk/releases/download/js/v0.5.0/rt-client-0.5.0.tgz"
"zustand": "^4.3.8"
},
"devDependencies": {
"@tauri-apps/api": "^1.6.0",
"@tauri-apps/cli": "1.5.11",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/cli": "^2.1.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
Expand Down
Loading