Add Tauri desktop scaffolding and improve response rendering.
This updates project metadata/scripts for desktop builds, adds Tauri app files, refreshes release artifacts, and improves results preview handling for structured and think-tagged responses. Made-with: Cursor
1
.gitignore
vendored
@@ -2,6 +2,7 @@ node_modules/
|
|||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
coverage/
|
coverage/
|
||||||
|
src-tauri/target/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
.env*
|
.env*
|
||||||
|
|||||||
22
README.md
@@ -25,4 +25,26 @@ A simple web app for batch testing prompts/workflows.
|
|||||||
- `npm run dev` - start development server
|
- `npm run dev` - start development server
|
||||||
- `npm run build` - build for production
|
- `npm run build` - build for production
|
||||||
- `npm run preview` - preview production build
|
- `npm run preview` - preview production build
|
||||||
|
- `npm run desktop:dev` - start the Tauri desktop app in development
|
||||||
|
- `npm run desktop:build` - build the Tauri desktop app
|
||||||
- `npm run lint` - run TypeScript checks
|
- `npm run lint` - run TypeScript checks
|
||||||
|
|
||||||
|
## Desktop App
|
||||||
|
|
||||||
|
This project includes a Tauri v2 wrapper in `src-tauri/`.
|
||||||
|
|
||||||
|
Before running the desktop app, install the Tauri system prerequisites for your OS:
|
||||||
|
[Tauri prerequisites](https://v2.tauri.app/start/prerequisites/).
|
||||||
|
|
||||||
|
On macOS, that includes Rust/Cargo and Xcode command line tools. After prerequisites are installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run desktop:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a desktop bundle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run desktop:build
|
||||||
|
```
|
||||||
|
|||||||
237
package-lock.json
generated
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "react-example",
|
"name": "dify-batch-tester",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "react-example",
|
"name": "dify-batch-tester",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.4.0",
|
"@base-ui/react": "^1.4.0",
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.10.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
@@ -2417,6 +2418,238 @@
|
|||||||
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/cli": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"bin": {
|
||||||
|
"tauri": "tauri.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "2.10.1",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-riscv64-gnu": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "2.10.1",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "2.10.1",
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc": "2.10.1",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "2.10.1",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "2.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ts-morph/common": {
|
"node_modules/@ts-morph/common": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz",
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "react-example",
|
"name": "dify-batch-tester",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||||
|
"dev:tauri": "vite --port=3000 --host=127.0.0.1 --strictPort",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
"tauri": "tauri",
|
||||||
|
"desktop:dev": "tauri dev",
|
||||||
|
"desktop:build": "tauri build",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"lint": "tsc --noEmit"
|
"lint": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
@@ -35,6 +39,7 @@
|
|||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.10.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
|||||||
5479
src-tauri/Cargo.lock
generated
Normal file
17
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "dify-batch-tester"
|
||||||
|
version = "0.0.0"
|
||||||
|
description = "Dify Batch Tester desktop app"
|
||||||
|
authors = ["wangx"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "dify_batch_tester_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
1
src-tauri/gen/schemas/capabilities.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
2477
src-tauri/gen/schemas/desktop-schema.json
Normal file
2477
src-tauri/gen/schemas/macOS-schema.json
Normal file
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 878 B |
BIN
src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 755 B |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 16 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 540 B |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 793 B |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
7
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
3
src-tauri/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
dify_batch_tester_lib::run()
|
||||||
|
}
|
||||||
31
src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "Dify Batch Tester",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"identifier": "com.wangx.dify-batch-tester",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "npm run dev:tauri",
|
||||||
|
"devUrl": "http://localhost:3000",
|
||||||
|
"beforeBuildCommand": "npm run build",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "Dify Batch Tester",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 860,
|
||||||
|
"minWidth": 1024,
|
||||||
|
"minHeight": 700,
|
||||||
|
"resizable": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/App.tsx
@@ -858,7 +858,7 @@ export default function App() {
|
|||||||
return testCase.results.map((result, rIndex) => {
|
return testCase.results.map((result, rIndex) => {
|
||||||
const outputs = result.error
|
const outputs = result.error
|
||||||
? result.error
|
? result.error
|
||||||
: (result.data?.outputs || result.answer || JSON.stringify(result));
|
: (result.data?.outputs ?? result.answer ?? result);
|
||||||
|
|
||||||
const outputString = typeof outputs === 'object'
|
const outputString = typeof outputs === 'object'
|
||||||
? JSON.stringify(outputs, null, 2)
|
? JSON.stringify(outputs, null, 2)
|
||||||
@@ -871,6 +871,7 @@ export default function App() {
|
|||||||
query: testCase.query,
|
query: testCase.query,
|
||||||
isError: !!result.error,
|
isError: !!result.error,
|
||||||
output: outputString,
|
output: outputString,
|
||||||
|
outputRaw: outputs,
|
||||||
taskId: result.task_id || result.id || 'N/A',
|
taskId: result.task_id || result.id || 'N/A',
|
||||||
result
|
result
|
||||||
};
|
};
|
||||||
@@ -952,9 +953,7 @@ export default function App() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
<TableCell className="px-4 py-4 align-top">
|
<TableCell className="px-4 py-4 align-top">
|
||||||
<div className="w-[300px] max-w-[300px] max-h-40 overflow-auto whitespace-pre-wrap break-all font-mono text-[10px] leading-relaxed bg-slate-50 p-3 rounded-xl border border-slate-100 text-[#334155] shadow-[inset_0_1px_2px_rgba(0,0,0,0.02)]">
|
<ResponsePreview value={row.outputRaw} />
|
||||||
{row.output}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
@@ -1657,6 +1656,175 @@ function safeStringify(v: unknown): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResponsePart = {
|
||||||
|
type: 'answer' | 'think';
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function decodeEscapedResponseText(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/\\r\\n/g, '\n')
|
||||||
|
.replace(/\\n/g, '\n')
|
||||||
|
.replace(/\\t/g, '\t')
|
||||||
|
.replace(/\\"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitResponseParts(value: string): ResponsePart[] {
|
||||||
|
const text = decodeEscapedResponseText(value);
|
||||||
|
const parts: ResponsePart[] = [];
|
||||||
|
const thinkBlockPattern = /<think>([\s\S]*?)(?:<\/think>|$)/gi;
|
||||||
|
let lastIndex = 0;
|
||||||
|
|
||||||
|
for (const match of text.matchAll(thinkBlockPattern)) {
|
||||||
|
const matchIndex = match.index ?? 0;
|
||||||
|
const answer = text.slice(lastIndex, matchIndex);
|
||||||
|
if (answer.trim()) {
|
||||||
|
parts.push({ type: 'answer', content: answer });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match[1]?.trim()) {
|
||||||
|
parts.push({ type: 'think', content: match[1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex = matchIndex + match[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const answer = text.slice(lastIndex);
|
||||||
|
if (answer.trim()) {
|
||||||
|
parts.push({ type: 'answer', content: answer });
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.length ? parts : [{ type: 'answer', content: text }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonLikeString(value: string): unknown | undefined {
|
||||||
|
const text = decodeEscapedResponseText(value).trim();
|
||||||
|
if (!text || !/^[{[]/.test(text)) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResponseText({ value }: { value: string }) {
|
||||||
|
// Plain <div>s (not <pre>) so the surrounding context controls the font:
|
||||||
|
// sans-serif when used as a top-level answer, monospace when nested
|
||||||
|
// inside a structured/JSON panel that already sets font-mono.
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{splitResponseParts(value).map((part, index) => (
|
||||||
|
part.type === 'think' ? (
|
||||||
|
<details key={`${part.type}-${index}`} className="rounded-lg border border-amber-100 bg-amber-50/80 p-2">
|
||||||
|
<summary className="cursor-pointer select-none text-[10px] font-bold uppercase tracking-wider text-amber-700 hover:text-amber-800">
|
||||||
|
思考过程
|
||||||
|
</summary>
|
||||||
|
<div className="mt-2 whitespace-pre-wrap break-all text-amber-800">{part.content}</div>
|
||||||
|
</details>
|
||||||
|
) : (
|
||||||
|
<div key={`${part.type}-${index}`} className="whitespace-pre-wrap break-all">{part.content}</div>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StructuredValuePreview({ value, depth = 0 }: { value: unknown; depth?: number }) {
|
||||||
|
const parsedString = typeof value === 'string' ? parseJsonLikeString(value) : undefined;
|
||||||
|
const displayValue = parsedString ?? value;
|
||||||
|
|
||||||
|
if (typeof displayValue === 'string') {
|
||||||
|
return <ResponseText value={displayValue} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayValue === null || typeof displayValue !== 'object') {
|
||||||
|
return <pre className="m-0 whitespace-pre-wrap break-all">{String(displayValue)}</pre>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(displayValue)) {
|
||||||
|
if (!displayValue.length) {
|
||||||
|
return <span className="text-slate-400">[]</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{displayValue.map((item, index) => (
|
||||||
|
<div key={index} className="rounded-lg border border-slate-100 bg-white/70 p-2">
|
||||||
|
<div className="mb-1 text-[9px] font-bold uppercase tracking-wider text-[#94a3b8]">Item {index + 1}</div>
|
||||||
|
<StructuredValuePreview value={item} depth={depth + 1} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = Object.entries(displayValue);
|
||||||
|
if (!entries.length) {
|
||||||
|
return <span className="text-slate-400">{'{}'}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{entries.map(([key, fieldValue]) => {
|
||||||
|
const isNested = typeof fieldValue === 'object' && fieldValue !== null;
|
||||||
|
return (
|
||||||
|
<div key={key} className="rounded-lg border border-slate-100 bg-white/70 p-2">
|
||||||
|
<div className="mb-1 text-[9px] font-bold uppercase tracking-wider text-[#64748b]">{key}</div>
|
||||||
|
{isNested && depth > 0 ? (
|
||||||
|
<details>
|
||||||
|
<summary className="cursor-pointer select-none text-[10px] font-bold text-[#94a3b8] hover:text-[#0f172a]">
|
||||||
|
{Array.isArray(fieldValue) ? `${fieldValue.length} items` : 'Object'}
|
||||||
|
</summary>
|
||||||
|
<div className="mt-2">
|
||||||
|
<StructuredValuePreview value={fieldValue} depth={depth + 1} />
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
) : (
|
||||||
|
<StructuredValuePreview value={fieldValue} depth={depth + 1} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResponsePreview({ value }: { value: unknown }) {
|
||||||
|
const normalizedValue = isRecord(value) && 'answer' in value && Object.keys(value).length === 1
|
||||||
|
? value.answer
|
||||||
|
: value;
|
||||||
|
|
||||||
|
// Resolve once at the top so we can pick chrome based on the *final* shape:
|
||||||
|
// a JSON-like string is treated as structured, not as a plain text answer.
|
||||||
|
const parsedFromString = typeof normalizedValue === 'string'
|
||||||
|
? parseJsonLikeString(normalizedValue)
|
||||||
|
: undefined;
|
||||||
|
const displayValue = parsedFromString ?? normalizedValue;
|
||||||
|
|
||||||
|
if (typeof displayValue === 'string') {
|
||||||
|
// Plain text answer — match the sibling cells (sans-serif, text-xs) so
|
||||||
|
// the row reads as one piece instead of a bolted-on console panel.
|
||||||
|
return (
|
||||||
|
<div className="max-h-40 overflow-auto space-y-2 text-xs leading-relaxed text-[#0f172a]">
|
||||||
|
<ResponseText value={displayValue} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structured/JSON value — keep monospace, but lighter chrome that mirrors
|
||||||
|
// the <pre> blocks in the trace dialog (rounded-lg, no inset shadow).
|
||||||
|
return (
|
||||||
|
<div className="max-h-40 overflow-auto space-y-2 rounded-lg border border-slate-100 bg-slate-50/60 p-3 font-mono text-[10px] leading-relaxed text-[#334155]">
|
||||||
|
<StructuredValuePreview value={displayValue} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Excel cap is 32,767 chars per cell; leave headroom for safety.
|
// Excel cap is 32,767 chars per cell; leave headroom for safety.
|
||||||
const EXCEL_CELL_LIMIT = 32000;
|
const EXCEL_CELL_LIMIT = 32000;
|
||||||
const TRACE_INLINE_JSON_LIMIT = 300;
|
const TRACE_INLINE_JSON_LIMIT = 300;
|
||||||
|
|||||||