commit e11c59cdc26171d79aee16b15f6e9cf2ee904885 Author: LUOJIE\coolp Date: Thu Jun 26 11:28:55 2025 +0800 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3dce414 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..1fdfcbb --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +VITE_h5_client_url=https://bqw11111111111.ii999.live:20038 +VITE_HsAppCode=2 diff --git a/.env.11_120 b/.env.11_120 new file mode 100644 index 0000000..b68ff65 --- /dev/null +++ b/.env.11_120 @@ -0,0 +1,2 @@ +VITE_h5_client_url=https://bqw-120.ii999.live:20038 +VITE_HsAppCode=1 diff --git a/.env.11_120_test b/.env.11_120_test new file mode 100644 index 0000000..775df3f --- /dev/null +++ b/.env.11_120_test @@ -0,0 +1,2 @@ +VITE_h5_client_url=http://192.168.11.200:19080 +VITE_HsAppCode=1 diff --git a/.env.13_27 b/.env.13_27 new file mode 100644 index 0000000..97cb555 --- /dev/null +++ b/.env.13_27 @@ -0,0 +1,2 @@ +VITE_h5_client_url=https://bqw.ii999.live:20038 +VITE_HsAppCode=1 diff --git a/.env.develpment b/.env.develpment new file mode 100644 index 0000000..97cb555 --- /dev/null +++ b/.env.develpment @@ -0,0 +1,2 @@ +VITE_h5_client_url=https://bqw.ii999.live:20038 +VITE_HsAppCode=1 diff --git a/.env.huashiai b/.env.huashiai new file mode 100644 index 0000000..97cb555 --- /dev/null +++ b/.env.huashiai @@ -0,0 +1,2 @@ +VITE_h5_client_url=https://bqw.ii999.live:20038 +VITE_HsAppCode=1 diff --git a/.env.internet b/.env.internet new file mode 100644 index 0000000..97cb555 --- /dev/null +++ b/.env.internet @@ -0,0 +1,2 @@ +VITE_h5_client_url=https://bqw.ii999.live:20038 +VITE_HsAppCode=1 diff --git a/.env.jiangsu b/.env.jiangsu new file mode 100644 index 0000000..0e46367 --- /dev/null +++ b/.env.jiangsu @@ -0,0 +1,2 @@ +VITE_h5_client_url=http://36.149.161.6:18900 +VITE_HsAppCode=1 diff --git a/.env.lg_mirror b/.env.lg_mirror new file mode 100644 index 0000000..46394b8 --- /dev/null +++ b/.env.lg_mirror @@ -0,0 +1,2 @@ +VITE_h5_client_url=http://192.168.14.200:18900 +VITE_HsAppCode=1 diff --git a/.env.lgzs b/.env.lgzs new file mode 100644 index 0000000..8618154 --- /dev/null +++ b/.env.lgzs @@ -0,0 +1,2 @@ +VITE_h5_client_url=http://10.102.8.56:18900 +VITE_HsAppCode=1 diff --git a/.env.lgzs_test b/.env.lgzs_test new file mode 100644 index 0000000..00e2400 --- /dev/null +++ b/.env.lgzs_test @@ -0,0 +1,2 @@ +VITE_h5_client_url=http://10.101.27.243:18900 +VITE_HsAppCode=1 diff --git a/.env.nanshan b/.env.nanshan new file mode 100644 index 0000000..4d48615 --- /dev/null +++ b/.env.nanshan @@ -0,0 +1,2 @@ +VITE_h5_client_url=http://68.68.14.199:18900 +VITE_HsAppCode=1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04e7c31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist +out +.DS_Store +.eslintcache +*.log* +package-lock.json +.cursor diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..3e22f4b --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dify_market_manager_gui.iml b/.idea/dify_market_manager_gui.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/dify_market_manager_gui.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..dabcedd --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..97ce323 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..995c32c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..23f1cc0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 0000000..b0c1c68 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..34862ff --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +electron_mirror=https://npmmirror.com/mirrors/electron/ +electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9c6b791 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +out +dist +pnpm-lock.yaml +LICENSE.md +tsconfig.json +tsconfig.*.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..35893b3 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,4 @@ +singleQuote: true +semi: false +printWidth: 100 +trailingComma: none diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..940260d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0b6b9a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,39 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "cwd": "${workspaceRoot}", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" + }, + "runtimeArgs": ["--sourcemap"], + "env": { + "REMOTE_DEBUGGING_PORT": "9222" + } + }, + { + "name": "Debug Renderer Process", + "port": 9222, + "request": "attach", + "type": "chrome", + "webRoot": "${workspaceFolder}/src/renderer", + "timeout": 60000, + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "Debug All", + "configurations": ["Debug Main Process", "Debug Renderer Process"], + "presentation": { + "order": 1 + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4c05394 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..5aec3eb --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# dify_market_manager_gui + +An Electron application with Vue + +## Recommended IDE Setup + +- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + +## Project Setup + +### Install + +```bash +$ npm install +``` + +### Development + +```bash +$ npm run dev +``` + +### Build + +```bash +# For windows +$ npm run build:win + +# For macOS +$ npm run build:mac + +# For Linux +$ npm run build:linux +``` diff --git a/bqw.sh b/bqw.sh new file mode 100644 index 0000000..a8e05db --- /dev/null +++ b/bqw.sh @@ -0,0 +1,192 @@ +#!/bin/bash + +# 设置工作目录 +# 龙岗环境 +WORK_DIR="/data_ai/ai-application/docker" +# 公司环境 +# WORK_DIR="/data/docker/images/bqw-ai" + +# 检查参数是否提供 +if [ "$#" -lt 1 ]; then + echo "Error: No arguments provided." + echo "Supported commands:" + echo " bqw up - Start services" + echo " bqw down - Stop services" + echo " bqw restart - Restart all services" + echo " bqw restart - Restart a specific container (e.g., bqw-ai-)" + echo " bqw logs - logs -f a specific container (e.g., bqw-ai-)" + echo " bqw clear cache - Clear Redis cache and restart services" + echo " bqw bak system - Backup system jar file" + echo " bqw oa data - Get OA org data" + exit 1 +fi + +# 切换到工作目录 +cd "$WORK_DIR" || { echo "Failed to enter work directory: $WORK_DIR"; exit 1; } + +# 根据参数执行不同的命令 +case "$1" in + up) + echo "Starting services with docker compose..." + sudo docker compose -p bqw up -d + ;; + down) + echo "Stopping services with docker compose..." + sudo docker compose -p bqw down + ;; + bak) + if [ "$2" == "system" ]; then + # 获取当前时间作为备份文件名的一部分 + BACKUP_TIME=$(date +"%Y-%m-%d-%H%M%S") + SOURCE_FILE="${WORK_DIR}/system/target/bqw-ai.jar" + BACKUP_FILE="${WORK_DIR}/system/target/bqw-ai.jar-${BACKUP_TIME}" + + # 检查源文件是否存在 + if [ ! -f "$SOURCE_FILE" ]; then + echo "Error: Source file $SOURCE_FILE does not exist." + exit 1 + fi + + # 执行备份 + echo "Backing up system jar file..." + cp "$SOURCE_FILE" "$BACKUP_FILE" + + if [ $? -eq 0 ]; then + echo "Backup completed successfully: $BACKUP_FILE" + else + echo "Error: Backup failed." + exit 1 + fi + else + echo "Invalid argument. Usage: bqw bak system" + exit 1 + fi + ;; + restart) + if [ "$2" == "" ]; then + # 如果没有提供名称,则重启整个服务 + echo "Restarting bqw services..." + echo "Stopping services..." + sudo docker compose -p bqw down + echo "Starting services..." + sudo docker compose -p bqw up -d + else + # 如果提供了名称,则重启指定的容器 + CONTAINER_NAME="bqw-ai-$2" + echo "Restarting container: $CONTAINER_NAME" + if sudo docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME\$"; then + sudo docker restart "$CONTAINER_NAME" + echo "Container $CONTAINER_NAME restarted successfully." + else + echo "Error: Container $CONTAINER_NAME not found." + exit 1 + fi + fi + ;; + logs) + # 如果提供了名称,则重启指定的容器 + CONTAINER_NAME="bqw-ai-$2" + echo "logs container: $CONTAINER_NAME" + if sudo docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME\$"; then + sudo docker logs --tail 500 -f "$CONTAINER_NAME" + echo "Container $CONTAINER_NAME logs successfully." + else + echo "Error: Container $CONTAINER_NAME not found." + exit 1 + fi + ;; + clear) + if [ "$2" == "cache" ]; then + echo "Stopping bqw services before clearing cache..." + sudo docker compose -p bqw down + + # 删除 Redis 缓存文件 + REDIS_CACHE_DIR="${WORK_DIR}/redis/data" + if [ -d "$REDIS_CACHE_DIR" ]; then + echo "Deleting Redis cache files in $REDIS_CACHE_DIR..." + sudo rm -rf "$REDIS_CACHE_DIR"/* + echo "Cache cleared successfully." + else + echo "Redis cache directory not found: $REDIS_CACHE_DIR" + fi + + echo "Restarting bqw services..." + sudo docker compose -p bqw up -d + else + echo "Invalid argument. Usage: bqw clear cache" + echo "Supported commands:" + echo " bqw up - Start services" + echo " bqw down - Stop services" + echo " bqw restart - Restart all services" + echo " bqw restart - Restart a specific container (e.g., bqw-ai-)" + echo " bqw logs - logs a specific container (e.g., bqw-ai-)" + echo " bqw clear cache - Clear Redis cache and restart services" + echo " bqw bak system - Backup system jar file" + echo " bqw oa token - Get OA token" + echo " bqw oa data - Get OA data and save to specified path" + exit 1 + fi + ;; + oa) + if [ "$2" == "data" ]; then + echo "Getting OA token..." + # 获取token并保存到临时变量 + TOKEN_RESPONSE=$(curl -X POST https://xtbg.lg.gov.cn/LGOA/restservices/LgoaAPINew/lgoaToken/query \ + -H "Content-Type: application/json" \ + -d '{ + "authaccount": "LGOA_ZNTPTDJ", + "authid": "20250328_073714dcf19814d6dd9e" + }') + + # 使用grep和cut提取token值 + TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) + + if [ -z "$TOKEN" ]; then + echo "Error: Failed to get token" + exit 1 + fi + + echo "Token obtained successfully: $TOKEN" + + # 处理相对路径 + if [[ "$3" == "./" ]]; then + OUTPUT_PATH="${WORK_DIR}/output.json" + else + # 构建完整的输出文件路径 + OUTPUT_PATH="${WORK_DIR}/${3}/output.json" + fi + + echo "Getting OA data with token: $TOKEN" + echo "Saving output to: $OUTPUT_PATH" + echo "Please Waiting..." + + curl -X POST https://xtbg.lg.gov.cn/LGOA/restservices/LgoaAPINew/getOrgPosUserInfo/query \ + -H "Content-Type: application/json" \ + -d "{\"token\":\"$TOKEN\"}" \ + -o "$OUTPUT_PATH" + + if [ $? -eq 0 ]; then + echo "Data successfully saved to $OUTPUT_PATH" + else + echo "Error: Failed to get data" + exit 1 + fi + else + echo "Invalid argument. Usage: bqw oa data" + exit 1 + fi + ;; + *) + echo "Error: Invalid command '$1'." + echo "Supported commands:" + echo " bqw up - Start services" + echo " bqw down - Stop services" + echo " bqw restart - Restart all services" + echo " bqw restart - Restart a specific container (e.g., bqw-ai-)" + echo " bqw logs - logs a specific container (e.g., bqw-ai-)" + echo " bqw clear cache - Clear Redis cache and restart services" + echo " bqw bak system - Backup system jar file" + echo " bqw oa data - Get OA org data" + exit 1 + ;; +esac diff --git a/build/256.png b/build/256.png new file mode 100644 index 0000000..6e2f5d5 Binary files /dev/null and b/build/256.png differ diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000..38c887b --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + + diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000..28644aa Binary files /dev/null and b/build/icon.icns differ diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..379faf4 Binary files /dev/null and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..0447e6c Binary files /dev/null and b/build/icon.png differ diff --git a/build/icons/1024x1024.png b/build/icons/1024x1024.png new file mode 100644 index 0000000..0dff116 Binary files /dev/null and b/build/icons/1024x1024.png differ diff --git a/build/icons/128x128.png b/build/icons/128x128.png new file mode 100644 index 0000000..e99e88f Binary files /dev/null and b/build/icons/128x128.png differ diff --git a/build/icons/16x16.png b/build/icons/16x16.png new file mode 100644 index 0000000..a657f34 Binary files /dev/null and b/build/icons/16x16.png differ diff --git a/build/icons/24x24.png b/build/icons/24x24.png new file mode 100644 index 0000000..6fe2681 Binary files /dev/null and b/build/icons/24x24.png differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 0000000..222ac11 Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/32x32.png b/build/icons/32x32.png new file mode 100644 index 0000000..0df63c4 Binary files /dev/null and b/build/icons/32x32.png differ diff --git a/build/icons/48x48.png b/build/icons/48x48.png new file mode 100644 index 0000000..96c225a Binary files /dev/null and b/build/icons/48x48.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 0000000..5ba807c Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/64x64.png b/build/icons/64x64.png new file mode 100644 index 0000000..e0221d0 Binary files /dev/null and b/build/icons/64x64.png differ diff --git a/build/icons/mac/icon.icns b/build/icons/mac/icon.icns new file mode 100644 index 0000000..8d27a0e Binary files /dev/null and b/build/icons/mac/icon.icns differ diff --git a/build/icons/png/1024x1024.png b/build/icons/png/1024x1024.png new file mode 100644 index 0000000..0dff116 Binary files /dev/null and b/build/icons/png/1024x1024.png differ diff --git a/build/icons/png/128x128.png b/build/icons/png/128x128.png new file mode 100644 index 0000000..e99e88f Binary files /dev/null and b/build/icons/png/128x128.png differ diff --git a/build/icons/png/16x16.png b/build/icons/png/16x16.png new file mode 100644 index 0000000..a657f34 Binary files /dev/null and b/build/icons/png/16x16.png differ diff --git a/build/icons/png/24x24.png b/build/icons/png/24x24.png new file mode 100644 index 0000000..6fe2681 Binary files /dev/null and b/build/icons/png/24x24.png differ diff --git a/build/icons/png/256x256.png b/build/icons/png/256x256.png new file mode 100644 index 0000000..222ac11 Binary files /dev/null and b/build/icons/png/256x256.png differ diff --git a/build/icons/png/32x32.png b/build/icons/png/32x32.png new file mode 100644 index 0000000..0df63c4 Binary files /dev/null and b/build/icons/png/32x32.png differ diff --git a/build/icons/png/48x48.png b/build/icons/png/48x48.png new file mode 100644 index 0000000..96c225a Binary files /dev/null and b/build/icons/png/48x48.png differ diff --git a/build/icons/png/512x512.png b/build/icons/png/512x512.png new file mode 100644 index 0000000..5ba807c Binary files /dev/null and b/build/icons/png/512x512.png differ diff --git a/build/icons/png/64x64.png b/build/icons/png/64x64.png new file mode 100644 index 0000000..e0221d0 Binary files /dev/null and b/build/icons/png/64x64.png differ diff --git a/build/icons/win/icon.ico b/build/icons/win/icon.ico new file mode 100644 index 0000000..f25b555 Binary files /dev/null and b/build/icons/win/icon.ico differ diff --git a/config/customConfig.json b/config/customConfig.json new file mode 100644 index 0000000..4cf2526 --- /dev/null +++ b/config/customConfig.json @@ -0,0 +1 @@ +{"MAIN_VITE_DIFY_HOST":"http://work.ii999.live:20040/"} \ No newline at end of file diff --git a/dev-app-update.yml b/dev-app-update.yml new file mode 100644 index 0000000..f9d03df --- /dev/null +++ b/dev-app-update.yml @@ -0,0 +1,3 @@ +provider: generic +url: "" +updaterCacheDirName: dify_market_manager_gui-updater diff --git a/electron-builder-ft.yml b/electron-builder-ft.yml new file mode 100644 index 0000000..17e5c56 --- /dev/null +++ b/electron-builder-ft.yml @@ -0,0 +1,44 @@ +appId: com.huashiai.app +compression: maximum +productName: 福田区百千万AI智能体共创平台 +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: 福田区百千万AI智能体共创平台 +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - deb + maintainer: huashiai.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: "" +electronDownload: + mirror: https://npmmirror.com/mirrors/electron/ diff --git a/electron-builder-hs.yml b/electron-builder-hs.yml new file mode 100644 index 0000000..1b90994 --- /dev/null +++ b/electron-builder-hs.yml @@ -0,0 +1,44 @@ +appId: com.huashiai.app +compression: maximum +productName: 百千万AI智能体共创平台 +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: 百千万AI智能体共创平台 +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - deb + maintainer: huashiai.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: "" +electronDownload: + mirror: https://npmmirror.com/mirrors/electron/ diff --git a/electron-builder-jiangsu.yml b/electron-builder-jiangsu.yml new file mode 100644 index 0000000..5049e39 --- /dev/null +++ b/electron-builder-jiangsu.yml @@ -0,0 +1,44 @@ +appId: com.huashiai.app +compression: maximum +productName: 苏小胭 +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: 苏小胭 +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - deb + maintainer: huashiai.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: "" +electronDownload: + mirror: https://npmmirror.com/mirrors/electron/ diff --git a/electron-builder-lg.yml b/electron-builder-lg.yml new file mode 100644 index 0000000..5d08232 --- /dev/null +++ b/electron-builder-lg.yml @@ -0,0 +1,44 @@ +appId: com.huashiai.app +compression: maximum +productName: 龙岗区百千万AI智能体共创平台 +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: 龙岗区百千万AI智能体共创平台 +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - deb + maintainer: huashiai.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: "" +electronDownload: + mirror: https://npmmirror.com/mirrors/electron/ diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 0000000..5d08232 --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,44 @@ +appId: com.huashiai.app +compression: maximum +productName: 龙岗区百千万AI智能体共创平台 +directories: + buildResources: build +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' +asarUnpack: + - resources/** +win: + executableName: 龙岗区百千万AI智能体共创平台 +nsis: + artifactName: ${name}-${version}-setup.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always +mac: + entitlementsInherit: build/entitlements.mac.plist + extendInfo: + - NSCameraUsageDescription: Application requests access to the device's camera. + - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + notarize: false +dmg: + artifactName: ${name}-${version}.${ext} +linux: + target: + - AppImage + - deb + maintainer: huashiai.org + category: Utility +appImage: + artifactName: ${name}-${version}.${ext} +npmRebuild: false +publish: + provider: generic + url: "" +electronDownload: + mirror: https://npmmirror.com/mirrors/electron/ diff --git a/electron.vite.config.1749274751168.mjs b/electron.vite.config.1749274751168.mjs new file mode 100644 index 0000000..31d658d --- /dev/null +++ b/electron.vite.config.1749274751168.mjs @@ -0,0 +1,23 @@ +// electron.vite.config.mjs +import { resolve } from "path"; +import { defineConfig, externalizeDepsPlugin } from "electron-vite"; +import vue from "@vitejs/plugin-vue"; +var electron_vite_config_default = defineConfig({ + main: { + plugins: [externalizeDepsPlugin()] + }, + preload: { + plugins: [externalizeDepsPlugin()] + }, + renderer: { + resolve: { + alias: { + "@renderer": resolve("src/renderer/src") + } + }, + plugins: [vue()] + } +}); +export { + electron_vite_config_default as default +}; diff --git a/electron.vite.config.1750654889919.mjs b/electron.vite.config.1750654889919.mjs new file mode 100644 index 0000000..31d658d --- /dev/null +++ b/electron.vite.config.1750654889919.mjs @@ -0,0 +1,23 @@ +// electron.vite.config.mjs +import { resolve } from "path"; +import { defineConfig, externalizeDepsPlugin } from "electron-vite"; +import vue from "@vitejs/plugin-vue"; +var electron_vite_config_default = defineConfig({ + main: { + plugins: [externalizeDepsPlugin()] + }, + preload: { + plugins: [externalizeDepsPlugin()] + }, + renderer: { + resolve: { + alias: { + "@renderer": resolve("src/renderer/src") + } + }, + plugins: [vue()] + } +}); +export { + electron_vite_config_default as default +}; diff --git a/electron.vite.config.mjs b/electron.vite.config.mjs new file mode 100644 index 0000000..470c4a0 --- /dev/null +++ b/electron.vite.config.mjs @@ -0,0 +1,20 @@ +import { resolve } from 'path' +import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()] + }, + preload: { + plugins: [externalizeDepsPlugin()] + }, + renderer: { + resolve: { + alias: { + '@renderer': resolve('src/renderer/src') + } + }, + plugins: [vue()] + } +}) diff --git a/electron_h5/.gitignore b/electron_h5/.gitignore new file mode 100644 index 0000000..6d949a5 --- /dev/null +++ b/electron_h5/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +electron_h5 +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/electron_h5/.vscode/extensions.json b/electron_h5/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/electron_h5/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/electron_h5/README.md b/electron_h5/README.md new file mode 100644 index 0000000..14fa460 --- /dev/null +++ b/electron_h5/README.md @@ -0,0 +1,29 @@ +# electron_h5 + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` diff --git a/electron_h5/docker-compose.yml b/electron_h5/docker-compose.yml new file mode 100644 index 0000000..2aeafb8 --- /dev/null +++ b/electron_h5/docker-compose.yml @@ -0,0 +1,126 @@ +version: '2' +services: + bqw-ai-system: + environment: + TZ: Asia/Shanghai + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + DIFY_DB_HOST: 10.102.8.56 + DIFY_API_HOST: 10.102.8.56 + DIFY_API_PORT: 30080 + restart: always + container_name: bqw-ai-system + image: java:1.8.391 + volumes: + - ./system/target/bqw-ai.jar:/application/app.jar + - ./system/logs:/logs + - ./system/data:/application/data + command: java -jar -Dfile.encoding=UTF-8 /application/app.jar + logging: + driver: "json-file" + options: + max-size: "1024m" + max-file: "3" + depends_on: + - bqw-ai-mysql + - bqw-ai-redis + bqw-ai-magic: + environment: + TZ: Asia/Shanghai + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + restart: always + container_name: bqw-ai-magic + image: java:1.8.391 + depends_on: + - bqw-ai-mysql + - bqw-ai-redis + volumes: + - ./system/target/magic-api-demo.jar:/application/app.jar + - ./system/logs:/logs + - ./system/data:/application/data + command: java -jar -Dfile.encoding=UTF-8 /application/app.jar + logging: + driver: "json-file" + options: + max-size: "1024m" + max-file: "3" + bqw-ai-mysql: + environment: + MYSQL_ROOT_PASSWORD: Mars@23600800 + MYSQL_ROOT_HOST: '%' + MYSQL_DATABASE: ai-application + TZ: Asia/Shanghai + restart: always + privileged: true + container_name: bqw-ai-mysql + image: mysql:8.0.33 + command: + --character-set-server=utf8mb4 + --collation-server=utf8mb4_general_ci + --explicit_defaults_for_timestamp=true + --lower_case_table_names=1 + --max_allowed_packet=128M + --default-authentication-plugin=caching_sha2_password + volumes: + - ./mysql/data/:/var/lib/mysql/ + - ./mysql/sql:/docker-entrypoint-initdb.d + bqw-ai-redis: + image: redis:7.0.4 + environment: + TZ: Asia/Shanghai + restart: always + container_name: bqw-ai-redis + volumes: + - ./redis/conf:/redis/config:rw + - ./redis/data/:/redis/data/:rw + command: "redis-server /redis/config/redis.conf" + privileged: true + bqw-ai-web: + image: nginx:1.23.4 + ports: + - 18900:80 + - 18901:8080 + - 18902:8081 + restart: always + environment: + TZ: Asia/Shanghai + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + container_name: bqw-ai-web + volumes: + - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/html/dist:/usr/share/nginx/html + - ./nginx/html/chat:/usr/share/nginx/chat + - ./nginx/html/llm_flow:/usr/share/nginx/llm_flow + - ./nginx/html/electron_h5:/usr/share/nginx/electron_h5 + depends_on: + - bqw-ai-system + logging: + driver: "json-file" + options: + max-size: "1024m" + max-file: "3" + bqw-ai-minio: + image: minio/minio + privileged: true + restart: always + container_name: bqw-ai-minio + # ports: + # # api端口 + # - 9000:9000 + # # 控制台页面端口 + # - 9011:9011 + environment: + TZ: Asia/Shanghai + MINIO_ACCESS_KEY: admin + MINIO_SECRET_KEY: Huashiai@2024 + volumes: + - ./minio/data:/data + - /etc/localtime:/etc/localtime:ro + command: server /data --console-address ":9011" + logging: + driver: "json-file" + options: + max-size: "1024m" + max-file: "3" diff --git a/electron_h5/index.html b/electron_h5/index.html new file mode 100644 index 0000000..c1f99ce --- /dev/null +++ b/electron_h5/index.html @@ -0,0 +1,13 @@ + + + + + + + 龙岗区百千万AI智能体共创平台 + + +
+ + + diff --git a/electron_h5/jsconfig.json b/electron_h5/jsconfig.json new file mode 100644 index 0000000..5a1f2d2 --- /dev/null +++ b/electron_h5/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/electron_h5/nginx.conf b/electron_h5/nginx.conf new file mode 100644 index 0000000..0d21b83 --- /dev/null +++ b/electron_h5/nginx.conf @@ -0,0 +1,276 @@ +user root; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + sendfile on; + #tcp_nopush on; + keepalive_timeout 65; + #gzip on; + gzip on; + gzip_min_length 1k; + gzip_comp_level 9; + gzip_types text/plain application/x-javascript text/javascript application/x-httpd-php text/css text/xml text/jsp application/eot application/ttf application/otf application/svg application/woff application/javascript application/xml image/jpeg image/gif image/png; + gzip_vary on; + gzip_disable "MSIE [1-6]."; + # 除去 Web 站点中的信用卡号 + sub_filter '(\d{4}[- ]?){3}\d{4}' '**** **** **** ****'; + sub_filter_once off; + + # 全局设置允许的最大请求体大小 + client_max_body_size 2048m; + + server { + listen 80; + server_name localhost; + location ^~/bqw-ai { + proxy_pass http://bqw-ai-system:8080/bqw-ai; + proxy_set_header Host 127.0.0.1; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 允许跨域请求 + add_header Access-Control-Allow-Origin *; + # 允许带身份验证信息的跨域请求 + add_header Access-Control-Allow-Credentials true; + # 允许的请求方法 + add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; + # 允许的请求头 + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + # 预检请求的有效期 + add_header Access-Control-Max-Age 3600; + # 处理 OPTIONS 请求 + if ($request_method = 'OPTIONS') { + add_header Content-Type 'text/plain; charset=utf-8'; + add_header Content-Length 0; + return 204; + } + } + + location /bqw-ai/websocket { + proxy_pass http://bqw-ai-system:8080/bqw-ai/websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 900s; + } + + location / { + root /usr/share/nginx/html/; + index index.html index.htm; + if (!-e $request_filename) { + rewrite ^(.*)$ /index.html?s=$1last; + break; + } + } + + location /h5_client { + alias /usr/share/nginx/llm_flow/; + index index.html index.htm; + } + + location /electron_h5 { + alias /usr/share/nginx/electron_h5/; + index index.html index.htm; + } + + # http://10.180.6.206 + # https://xtbg.lg.gov.cn/LGOA + + + location /LGOA { + proxy_pass https://xtbg.lg.gov.cn/LGOA; + } + location /OAZS { + proxy_pass http://10.180.6.206/OAZS; + } + + + location /magic { + proxy_pass http://bqw-ai-magic:9999/magic; + } + + + + + + location /magic/web/console { + proxy_pass http://bqw-ai-magic:9999/magic/web/console; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 900s; + } + + + # minio-api + location /minio-api/ { + proxy_pass http://bqw-ai-minio:9000/; + } + + #minio页面 + location /minio/login { + proxy_pass http://bqw-ai-minio:9011; + # 启用支持websocket连接 + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 1024m; + proxy_http_version 1.1; + proxy_connect_timeout 3600; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + + location /bqw-ai/sys/common/static/bqw-ai-file/ { + proxy_pass http://bqw-ai-minio:9000/bqw-ai-file/; + + # 可选:添加一些常用的代理设置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 如果需要处理大文件上传或下载,可以调整超时时间 + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + + location /h5_client/bqw-ai-file/ { + proxy_pass http://bqw-ai-minio:9000/bqw-ai-file/; + + # 可选:添加一些常用的代理设置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 如果需要处理大文件上传或下载,可以调整超时时间 + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + + + location /bqw-video/ { + proxy_pass http://10.102.8.55:8090/; + + # 可选:添加一些常用的代理设置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 如果需要处理大文件上传或下载,可以调整超时时间 + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + + location /bqw-ai-file/ { + proxy_pass http://bqw-ai-minio:9000/bqw-ai-file/; + + # 可选:添加一些常用的代理设置 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 如果需要处理大文件上传或下载,可以调整超时时间 + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + + + + + + + + + + } + server { + + listen 8081; + server_name localhost; + + client_max_body_size 2000M; + + # minio-api + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + + proxy_connect_timeout 300; + # Default is HTTP/1, keepalive is only enabled in HTTP/1.1 + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://bqw-ai-minio:9000/; + } + + + } + + server { + listen 8080; + server_name localhost; + + + + # minio-api + location /minio-api/ { + proxy_pass http://bqw-ai-minio:9000/; + } + + #minio页面 + location / { + proxy_pass http://bqw-ai-minio:9011; + # 启用支持websocket连接 + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 1024m; + proxy_http_version 1.1; + proxy_connect_timeout 3600; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + } + + include /etc/nginx/conf.d/*.conf; +} diff --git a/electron_h5/package.json b/electron_h5/package.json new file mode 100644 index 0000000..688edb8 --- /dev/null +++ b/electron_h5/package.json @@ -0,0 +1,20 @@ +{ + "name": "electron_h5", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.3", + "vite": "^6.2.4", + "vite-plugin-vue-devtools": "^7.7.2" + } +} diff --git a/electron_h5/public/favicon.ico b/electron_h5/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/electron_h5/public/favicon.ico differ diff --git a/electron_h5/src/App.vue b/electron_h5/src/App.vue new file mode 100644 index 0000000..cb98d72 --- /dev/null +++ b/electron_h5/src/App.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/electron_h5/src/MainApp.vue b/electron_h5/src/MainApp.vue new file mode 100644 index 0000000..018e70d --- /dev/null +++ b/electron_h5/src/MainApp.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/electron_h5/src/assets/base.css b/electron_h5/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/electron_h5/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/electron_h5/src/assets/electron.svg b/electron_h5/src/assets/electron.svg new file mode 100644 index 0000000..45ef09c --- /dev/null +++ b/electron_h5/src/assets/electron.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/electron_h5/src/assets/logo.png b/electron_h5/src/assets/logo.png new file mode 100644 index 0000000..ef40391 Binary files /dev/null and b/electron_h5/src/assets/logo.png differ diff --git a/electron_h5/src/assets/logo.svg b/electron_h5/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/electron_h5/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/electron_h5/src/assets/longxiaoi_I.png b/electron_h5/src/assets/longxiaoi_I.png new file mode 100644 index 0000000..5e39869 Binary files /dev/null and b/electron_h5/src/assets/longxiaoi_I.png differ diff --git a/electron_h5/src/assets/lxI_120.png b/electron_h5/src/assets/lxI_120.png new file mode 100644 index 0000000..050cbcf Binary files /dev/null and b/electron_h5/src/assets/lxI_120.png differ diff --git a/electron_h5/src/assets/lxi_200.png b/electron_h5/src/assets/lxi_200.png new file mode 100644 index 0000000..0df6fe7 Binary files /dev/null and b/electron_h5/src/assets/lxi_200.png differ diff --git a/electron_h5/src/assets/main.css b/electron_h5/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/electron_h5/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/electron_h5/src/assets/wavy-lines.svg b/electron_h5/src/assets/wavy-lines.svg new file mode 100644 index 0000000..d08c611 --- /dev/null +++ b/electron_h5/src/assets/wavy-lines.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/electron_h5/src/components/ApiConfig.vue b/electron_h5/src/components/ApiConfig.vue new file mode 100644 index 0000000..a26128b --- /dev/null +++ b/electron_h5/src/components/ApiConfig.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/electron_h5/src/components/HelloWorld.vue b/electron_h5/src/components/HelloWorld.vue new file mode 100644 index 0000000..eff59f1 --- /dev/null +++ b/electron_h5/src/components/HelloWorld.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/electron_h5/src/components/TheWelcome.vue b/electron_h5/src/components/TheWelcome.vue new file mode 100644 index 0000000..fe48afc --- /dev/null +++ b/electron_h5/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/electron_h5/src/components/Versions.vue b/electron_h5/src/components/Versions.vue new file mode 100644 index 0000000..35136c0 --- /dev/null +++ b/electron_h5/src/components/Versions.vue @@ -0,0 +1,13 @@ + + + diff --git a/electron_h5/src/components/WelcomeItem.vue b/electron_h5/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/electron_h5/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/electron_h5/src/components/icons/IconCommunity.vue b/electron_h5/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/electron_h5/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/electron_h5/src/components/icons/IconDocumentation.vue b/electron_h5/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/electron_h5/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/electron_h5/src/components/icons/IconEcosystem.vue b/electron_h5/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/electron_h5/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/electron_h5/src/components/icons/IconSupport.vue b/electron_h5/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/electron_h5/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/electron_h5/src/components/icons/IconTooling.vue b/electron_h5/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/electron_h5/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/electron_h5/src/main.js b/electron_h5/src/main.js new file mode 100644 index 0000000..8a2e5d2 --- /dev/null +++ b/electron_h5/src/main.js @@ -0,0 +1,7 @@ +import { createApp } from 'vue' +import router from './router' +import MainApp from './MainApp.vue' + +const app = createApp(MainApp) +app.use(router) +app.mount('#app') diff --git a/electron_h5/src/router/index.js b/electron_h5/src/router/index.js new file mode 100644 index 0000000..fd16382 --- /dev/null +++ b/electron_h5/src/router/index.js @@ -0,0 +1,23 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import App from '../App.vue' +import ApiConfig from '../components/ApiConfig.vue' + +const routes = [ + { + path: '/', + name: 'home', + component: App + }, + { + path: '/config', + name: 'config', + component: ApiConfig + } +] + +const router = createRouter({ + history: createWebHashHistory(), + routes +}) + +export default router \ No newline at end of file diff --git a/electron_h5/vite.config.js b/electron_h5/vite.config.js new file mode 100644 index 0000000..bbcac17 --- /dev/null +++ b/electron_h5/vite.config.js @@ -0,0 +1,22 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + base: "/electron_h5", + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, + build: { + outDir: 'electron_h5', // 指定输出目录为 electron_h5 + } +}) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..4a93e4a --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,17 @@ +import eslintConfig from '@electron-toolkit/eslint-config' +import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier' +import eslintPluginVue from 'eslint-plugin-vue' + +export default [ + { ignores: ['**/node_modules', '**/dist', '**/out'] }, + eslintConfig, + ...eslintPluginVue.configs['flat/recommended'], + { + files: ['**/*.{js,jsx,vue}'], + rules: { + 'vue/require-default-prop': 'off', + 'vue/multi-word-component-names': 'off' + } + }, + eslintConfigPrettier +] diff --git a/forge.config.js b/forge.config.js new file mode 100644 index 0000000..a0d7de2 --- /dev/null +++ b/forge.config.js @@ -0,0 +1,15 @@ +module.exports = { + packagerConfig: { + ignore: [ + /^\/src/, + /(.eslintrc.json)|(.gitignore)|(electron.vite.config.ts)|(forge.config.js)|(tsconfig.*)/, + ], + }, + rebuildConfig: {}, + makers: [ + { + name: '@electron-forge/maker-deb', + platforms: ['linux'], + } + ], +}; diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..5fb7f17 --- /dev/null +++ b/install.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash + +# 定义错误处理函数 +handle_error() { + echo "错误:$1" + exit 1 +} + +# 检查是否提供了参数 +if [ $# -eq 0 ]; then + handle_error "请提供包名作为参数\n用法: $0 <包名>" +fi + +# 获取传入的包名参数 +PACKAGE_NAME="$1" + +# 检查文件是否存在 +if [ ! -f "$PACKAGE_NAME" ]; then + handle_error "文件 '$PACKAGE_NAME' 不存在" +fi + +# 检查文件权限 +if [ ! -r "$PACKAGE_NAME" ]; then + handle_error "没有读取文件 '$PACKAGE_NAME' 的权限" +fi + +# 检查是否具有sudo权限 +if ! sudo -v &>/dev/null; then + handle_error "没有sudo权限,请确保您有足够的权限执行此脚本" +fi + +## 关闭旧应用进程 +#echo "正在关闭旧应用进程..." +#PIDS=$(pgrep -f market-manager-gui) +#if [ -z "$PIDS" ]; then +# echo "没有找到正在运行的应用进程" +#else +# echo "找到正在运行的应用进程: $PIDS" +# for PID in $PIDS; do +# echo "尝试终止进程 ID: $PID" +# if ! sudo kill -TERM "$PID"; then +# echo "警告:尝试终止进程 ID $PID 时出错" +# else +# echo "已发送终止信号给进程 ID: $PID" +# fi +# done +#fi +# +## 等待进程完全关闭 +#echo "等待进程关闭..." +#sleep 2 + +# 检查是否还有残留进程 +#PIDS=$(pgrep -f market-manager-gui) +#if [ -z "$PIDS" ]; then +# echo "所有进程已关闭" +#else +# echo "警告:仍有进程在运行,尝试强制关闭..." +# for PID in $PIDS; do +# echo "尝试强制终止进程 ID: $PID" +# if ! sudo kill -KILL "$PID"; then +# echo "警告:强制终止进程 ID $PID 时出错" +# else +# echo "已强制终止进程 ID: $PID" +# fi +# done +# sleep 1 +#fi + +# 先卸载旧版本 +echo "正在卸载旧版本..." +if ! sudo dpkg -r market-manager-gui; then + echo "警告:卸载旧版本失败,可能未安装" +fi + +# 清理APT缓存以防止冲突 +echo "清理APT缓存..." +sudo apt-get clean + +# 安装新版本 +echo "正在安装 $PACKAGE_NAME..." +if ! sudo dpkg -i "$PACKAGE_NAME"; then + echo "安装失败,尝试修复依赖..." + if ! sudo apt-get install -f -y; then + handle_error "安装失败且无法修复依赖" + fi + # 再次清理APT缓存 + sudo apt-get clean + echo "修复完成,重新尝试安装..." + if ! sudo dpkg -i "$PACKAGE_NAME"; then + handle_error "安装最终失败" + fi +fi + +# 添加调试信息 +echo "检查安装状态..." +dpkg -l | grep market-manager-gui + +# 确保脚本执行完成 +echo "安装过程完成" +exit 0 + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..97cae78 --- /dev/null +++ b/package.json @@ -0,0 +1,73 @@ +{ + "name": "market-manager-gui", + "version": "1.0.0", + "description": "苏小胭", + "main": "./out/main/index.js", + "author": "huashiai.com", + "homepage": "https://huashiai.com", + "scripts": { + "format": "prettier --write .", + "lint": "eslint --cache .", + "start": "electron-forge start", + "dev": "electron-vite --mode 13_27", + "dev:11_120_test": "electron-vite --mode 11_120_test", + "dev:lgzs": "electron-vite --mode lgzs", + "dev:lgzs_test": "electron-vite --mode lgzs_test", + "dev:lg_mirror": "electron-vite --mode lg_mirror", + "package": "electron-forge package", + "make1 ": "electron-vite build && electron-forge make", + "dev:huashiai": "electron-vite dev --mode huashiai", + "dev:internet": "electron-vite dev --mode internet", + "build": "electron-vite build", + "build:unpack": "npm run build && electron-builder --dir", + "build:win": "electron-vite build --mode 11.120 && electron-builder --win ", + "build:win_11_120": "electron-vite build --mode 11_120 && electron-builder --win ", + "build:win_13_27": "electron-vite build --mode 13_27 && electron-builder --win ", + "build:win_lgzs": "electron-vite build --mode lgzs && electron-builder --win ", + "build:win_jiangsu": "electron-vite build --mode jiangsu && electron-builder --win ", + "build:win_nanshan": "electron-vite build --mode nanshan && electron-builder --win", + "build:win_huashiai": "electron-vite build --mode huashiai && electron-builder --win", + "build:win_internet": "npm run build --mode internet && electron-builder --win", + "build:mac": "npm run build && electron-builder --mac", + "build:linux_lgzs": "electron-vite build --mode lgzs && electron-builder --linux", + "build:linux_13_27": "electron-vite build --mode 13_27 && electron-builder --linux", + "build:icon": "./node_modules/.bin/electron-icon-builder --input=./build/icon.png --output=./build/", + "make": "electron-forge make" + }, + "permissions": [ + "audioCapture", + "videoCapture" + ], + "dependencies": { + "@electron-toolkit/preload": "^3.0.1", + "@electron-toolkit/utils": "^4.0.0", + "auto-launch": "^5.0.6", + "axios": "^1.8.4", + "dayjs": "^1.11.13", + "electron-json-config": "^2.1.0", + "electron-log": "^5.4.0", + "electron-store": "^8.0.0", + "electron-updater": "^6.3.9", + "ws": "^8.18.2", + "xe-utils": "^3.7.4" + }, + "devDependencies": { + "@electron-forge/cli": "^6.2.1", + "@electron-forge/maker-deb": "^6.2.1", + "@electron-toolkit/eslint-config": "^2.0.0", + "@electron-toolkit/eslint-config-prettier": "^3.0.0", + "@vitejs/plugin-vue": "^5.2.1", + "electron": "^34.2.0", + "electron-builder": "^25.1.8", + "electron-icon-builder": "^2.0.1", + "electron-squirrel-startup": "^1.0.1", + "electron-vite": "^3.0.0", + "eslint": "^9.20.1", + "eslint-plugin-vue": "^9.32.0", + "prettier": "^3.5.1", + "vite": "^6.1.0", + "vue": "^3.5.13", + "vue-router": "^4.2.5" + }, + "__npminstall_done": false +} diff --git a/resources/1024x1024.png b/resources/1024x1024.png new file mode 100644 index 0000000..0dff116 Binary files /dev/null and b/resources/1024x1024.png differ diff --git a/resources/128x128.png b/resources/128x128.png new file mode 100644 index 0000000..e99e88f Binary files /dev/null and b/resources/128x128.png differ diff --git a/resources/16x16.png b/resources/16x16.png new file mode 100644 index 0000000..a657f34 Binary files /dev/null and b/resources/16x16.png differ diff --git a/resources/24x24.png b/resources/24x24.png new file mode 100644 index 0000000..6fe2681 Binary files /dev/null and b/resources/24x24.png differ diff --git a/resources/256x256.png b/resources/256x256.png new file mode 100644 index 0000000..222ac11 Binary files /dev/null and b/resources/256x256.png differ diff --git a/resources/32x32.png b/resources/32x32.png new file mode 100644 index 0000000..0df63c4 Binary files /dev/null and b/resources/32x32.png differ diff --git a/resources/48x48.png b/resources/48x48.png new file mode 100644 index 0000000..96c225a Binary files /dev/null and b/resources/48x48.png differ diff --git a/resources/512x512.png b/resources/512x512.png new file mode 100644 index 0000000..5ba807c Binary files /dev/null and b/resources/512x512.png differ diff --git a/resources/64x64.png b/resources/64x64.png new file mode 100644 index 0000000..e0221d0 Binary files /dev/null and b/resources/64x64.png differ diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..6e2f5d5 Binary files /dev/null and b/resources/icon.png differ diff --git a/resources/img.png b/resources/img.png new file mode 100644 index 0000000..8301d30 Binary files /dev/null and b/resources/img.png differ diff --git a/resources/lg/1024x1024.png b/resources/lg/1024x1024.png new file mode 100644 index 0000000..0dff116 Binary files /dev/null and b/resources/lg/1024x1024.png differ diff --git a/resources/lg/128x128.png b/resources/lg/128x128.png new file mode 100644 index 0000000..e99e88f Binary files /dev/null and b/resources/lg/128x128.png differ diff --git a/resources/lg/16x16.png b/resources/lg/16x16.png new file mode 100644 index 0000000..a657f34 Binary files /dev/null and b/resources/lg/16x16.png differ diff --git a/resources/lg/24x24.png b/resources/lg/24x24.png new file mode 100644 index 0000000..6fe2681 Binary files /dev/null and b/resources/lg/24x24.png differ diff --git a/resources/lg/256x256.png b/resources/lg/256x256.png new file mode 100644 index 0000000..222ac11 Binary files /dev/null and b/resources/lg/256x256.png differ diff --git a/resources/lg/32x32.png b/resources/lg/32x32.png new file mode 100644 index 0000000..0df63c4 Binary files /dev/null and b/resources/lg/32x32.png differ diff --git a/resources/lg/48x48.png b/resources/lg/48x48.png new file mode 100644 index 0000000..96c225a Binary files /dev/null and b/resources/lg/48x48.png differ diff --git a/resources/lg/512x512.png b/resources/lg/512x512.png new file mode 100644 index 0000000..5ba807c Binary files /dev/null and b/resources/lg/512x512.png differ diff --git a/resources/lg/64x64.png b/resources/lg/64x64.png new file mode 100644 index 0000000..e0221d0 Binary files /dev/null and b/resources/lg/64x64.png differ diff --git a/resources/lg/icon.png b/resources/lg/icon.png new file mode 100644 index 0000000..6e2f5d5 Binary files /dev/null and b/resources/lg/icon.png differ diff --git a/resources/lg/img.png b/resources/lg/img.png new file mode 100644 index 0000000..8301d30 Binary files /dev/null and b/resources/lg/img.png differ diff --git a/rvm-installer.sh b/rvm-installer.sh new file mode 100644 index 0000000..ffca4bb --- /dev/null +++ b/rvm-installer.sh @@ -0,0 +1,939 @@ +#!/usr/bin/env bash + +shopt -s extglob +set -o errtrace +set -o errexit +set -o pipefail + +rvm_install_initialize() +{ + DEFAULT_SOURCES=(github.com/rvm/rvm bitbucket.org/mpapis/rvm) + + BASH_MIN_VERSION="3.2.25" + if + [[ -n "${BASH_VERSION:-}" && + "$(\printf "%b" "${BASH_VERSION:-}\n${BASH_MIN_VERSION}\n" | LC_ALL=C \sort -t"." -k1,1n -k2,2n -k3,3n | \head -n1)" != "${BASH_MIN_VERSION}" + ]] + then + echo "BASH ${BASH_MIN_VERSION} required (you have $BASH_VERSION)" + exit 1 + fi + + export HOME PS4 + export rvm_trace_flag rvm_debug_flag rvm_user_install_flag rvm_ignore_rvmrc rvm_prefix rvm_path + + PS4="+ \${BASH_SOURCE##\${rvm_path:-}} : \${FUNCNAME[0]:+\${FUNCNAME[0]}()} \${LINENO} > " +} + +log() { printf "%b\n" "$*"; } +debug(){ [[ ${rvm_debug_flag:-0} -eq 0 ]] || printf "%b\n" "$*" >&2; } +warn() { log "WARN: $*" >&2 ; } +fail() { fail_with_code 1 "$*" ; } +fail_with_code() { code="$1" ; shift ; log "\nERROR: $*\n" >&2 ; exit "$code" ; } + +rvm_install_commands_setup() +{ + \which which >/dev/null 2>&1 || fail "Could not find 'which' command, make sure it's available first before continuing installation." + \which grep >/dev/null 2>&1 || fail "Could not find 'grep' command, make sure it's available first before continuing installation." + if + [[ -z "${rvm_tar_command:-}" ]] && builtin command -v gtar >/dev/null + then + rvm_tar_command=gtar + elif + ${rvm_tar_command:-tar} --help 2>&1 | GREP_OPTIONS="" \grep -- --strip-components >/dev/null + then + rvm_tar_command="${rvm_tar_command:-tar}" + else + case "$(uname)" in + (OpenBSD) + log "Trying to install GNU version of tar, might require sudo password" + if (( UID )) + then sudo pkg_add -z gtar-1 + else pkg_add -z gtar-1 + fi + rvm_tar_command=gtar + ;; + (Darwin|FreeBSD|DragonFly) # it's not possible to autodetect on OSX, the help/man does not mention all flags + rvm_tar_command=tar + ;; + (SunOS) + case "$(uname -r)" in + (5.10) + log "Trying to install GNU version of tar, might require sudo password" + if (( UID )) + then + if \which sudo >/dev/null 2>&1 + then sudo_10=sudo + elif \which /opt/csw/bin/sudo >/dev/null 2>&1 + then sudo_10=/opt/csw/bin/sudo + else fail "sudo is required but not found. You may install sudo from OpenCSW repository (https://www.opencsw.org/about)" + fi + pkginfo -q CSWpkgutil || $sudo_10 pkgadd -a $rvm_path/config/solaris/noask -d https://get.opencsw.org/now CSWpkgutil + sudo /opt/csw/bin/pkgutil -iy CSWgtar -t https://mirror.opencsw.org/opencsw/unstable + else + pkginfo -q CSWpkgutil || pkgadd -a $rvm_path/config/solaris/noask -d https://get.opencsw.org/now CSWpkgutil + /opt/csw/bin/pkgutil -iy CSWgtar -t https://mirror.opencsw.org/opencsw/unstable + fi + rvm_tar_command=/opt/csw/bin/gtar + ;; + (*) + rvm_tar_command=tar + ;; + esac + esac + builtin command -v ${rvm_tar_command:-gtar} >/dev/null || + fail "Could not find GNU compatible version of 'tar' command, make sure it's available first before continuing installation." + fi + if + [[ " ${rvm_tar_options:-} " != *" --no-same-owner "* ]] && + $rvm_tar_command --help 2>&1 | GREP_OPTIONS="" \grep -- --no-same-owner >/dev/null + then + rvm_tar_options="${rvm_tar_options:-}${rvm_tar_options:+ }--no-same-owner" + fi +} + +usage() +{ + printf "%b" " + +Usage + + rvm-installer [options] [action] + +Options + + [[--]version] + + The version or tag to install. Valid values are: + + latest - The latest tagged version. + latest-minor - The latest minor version of the current major version. + latest- - The latest minor version of version x. + latest-. - The latest patch version of version x.y. + .. - Major version x, minor version y and patch z. + + [--]branch + + The name of the branch from which RVM is installed. This option can be used + with the following formats for : + + / + + If account is rvm or mpapis, installs from one of the following: + + https://github.com/rvm/rvm/archive/master.tar.gz + https://bitbucket.org/mpapis/rvm/get/master.tar.gz + + Otherwise, installs from: + + https://github.com//rvm/archive/master.tar.gz + + / + + If account is rvm or mpapis, installs from one of the following: + + https://github.com/rvm/rvm/archive/.tar.gz + https://bitbucket.org/mpapis/rvm/get/.tar.gz + + Otherwise, installs from: + + https://github.com//rvm/archive/.tar.gz + + [/] + + Installs the branch from one of the following: + + https://github.com/rvm/rvm/archive/.tar.gz + https://bitbucket.org/mpapis/rvm/get/.tar.gz + + [--]source + + Defines the repository from which RVM is retrieved and installed in the format: + + // + + Where: + + - Is bitbucket.org, github.com or a github enterprise site serving + an RVM repository. + - Is the user account in which the RVM repository resides. + - Is the name of the RVM repository. + + Note that when using the [--]source option, one should only use the [/]branch format + with the [--]branch option. Failure to do so will result in undefined behavior. + + --trace + + Provides debug logging for the installation script. +Actions + + master - Installs RVM from the master branch at rvm/rvm on github or mpapis/rvm + on bitbucket.org. + stable - Installs RVM from the stable branch a rvm/rvm on github or mpapis/rvm + on bitbucket.org. + help - Displays this output. + +" +} + +## duplication marker 32fosjfjsznkjneuera48jae +__rvm_curl_output_control() +{ + if + (( ${rvm_quiet_curl_flag:-0} == 1 )) + then + __flags+=( "--silent" "--show-error" ) + elif + [[ " $*" == *" -s"* || " $*" == *" --silent"* ]] + then + # make sure --show-error is used with --silent + [[ " $*" == *" -S"* || " $*" == *" -sS"* || " $*" == *" --show-error"* ]] || + { + __flags+=( "--show-error" ) + } + fi +} + +## duplication marker 32fosjfjsznkjneuera48jae +# -S is automatically added to -s +__rvm_curl() +( + __rvm_which curl >/dev/null || + { + rvm_error "RVM requires 'curl'. Install 'curl' first and try again." + return 200 + } + + typeset -a __flags + __flags=( --fail --location --max-redirs 10 ) + + [[ "$*" == *"--max-time"* ]] || + [[ "$*" == *"--connect-timeout"* ]] || + __flags+=( --connect-timeout 30 --retry-delay 2 --retry 3 ) + + if [[ -n "${rvm_proxy:-}" ]] + then __flags+=( --proxy "${rvm_proxy:-}" ) + fi + + __rvm_curl_output_control + + unset curl + __rvm_debug_command \curl "${__flags[@]}" "$@" || return $? +) + +rvm_error() { printf "ERROR: %b\n" "$*"; } +__rvm_which(){ which "$@" || return $?; true; } +__rvm_debug_command() +{ + debug "Running($#): $*" + "$@" || return $? + true +} +rvm_is_a_shell_function() +{ + [[ -t 0 && -t 1 ]] || return $? + return ${rvm_is_not_a_shell_function:-0} +} + +# Searches the tags for the highest available version matching a given pattern. +# fetch_version (github.com/rvm/rvm bitbucket.org/mpapis/rvm) 1.10. -> 1.10.3 +# fetch_version (github.com/rvm/rvm bitbucket.org/mpapis/rvm) 1.10. -> 1.10.3 +# fetch_version (github.com/rvm/rvm bitbucket.org/mpapis/rvm) 1. -> 1.11.0 +# fetch_version (github.com/rvm/rvm bitbucket.org/mpapis/rvm) "" -> 2.0.1 +fetch_version() +{ + typeset _account _domain _pattern _repo _sources _values _version + _sources=(${!1}) + _pattern=$2 + for _source in "${_sources[@]}" + do + IFS='/' read -r _domain _account _repo <<< "${_source}" + _version="$( + fetch_versions ${_domain} ${_account} ${_repo} | + GREP_OPTIONS="" \grep "^${_pattern:-}" | tail -n 1 + )" + if + [[ -n ${_version} ]] + then + echo "${_version}" + return 0 + fi + done + fail_with_code 4 "Exhausted all sources trying to fetch version '$version' of RVM!" +} + +# Returns a sorted list of most recent tags from a repository +fetch_versions() +{ + typeset _account _domain _repo _url + _domain=$1 + _account=$2 + _repo=$3 + case ${_domain} in + (bitbucket.org) + _url="https://api.${_domain}/2.0/repositories/${_account}/${_repo}/refs/tags?sort=-name&pagelen=20" + ;; + (github.com) + _url=https://api.${_domain}/repos/${_account}/${_repo}/tags + ;; + + (*) + _url=https://${_domain}/api/v3/repos/${_account}/${_repo}/tags + ;; + esac + + { __rvm_curl -sS "${_url}" || warn "...the preceeding error with code $? occurred while fetching $_url" ; } | + \awk -v RS=',|values":' -v FS='"' '$2=="name"&&$4!="rvm"{print $4}' | + sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n +} + +install_release() +{ + typeset _source _sources _url _version _verify_pgp + _sources=(${!1}) + _version=$2 + debug "Downloading RVM version ${_version}" + for _source in "${_sources[@]}" + do + case ${_source} in + (bitbucket.org*) + _url="https://${_source}/get/${_version}.tar.gz" + _verify_pgp="https://${_source}/downloads/${_version}.tar.gz.asc" + ;; + (*) + _url="https://${_source}/archive/${_version}.tar.gz" + _verify_pgp="https://${_source}/releases/download/${_version}/${_version}.tar.gz.asc" + ;; + esac + get_and_unpack "${_url}" "rvm-${_version}.tgz" "$_verify_pgp" && return + done + return $? +} + +install_head() +{ + typeset _branch _source _sources _url + _sources=(${!1}) + _branch=$2 + debug "Selected RVM branch ${_branch}" + for _source in "${_sources[@]}" + do + case ${_source} in + (bitbucket.org*) + _url=https://${_source}/get/${_branch}.tar.gz + ;; + (*) + _url=https://${_source}/archive/${_branch}.tar.gz + ;; + esac + get_and_unpack "${_url}" "rvm-${_branch//\//_}.tgz" && return + done + return $? +} + +# duplication marker dfkjdjngdfjngjcszncv +# Drop in cd which _doesn't_ respect cdpath +__rvm_cd() +{ + typeset old_cdpath ret + ret=0 + old_cdpath="${CDPATH}" + CDPATH="." + chpwd_functions="" builtin cd "$@" || ret=$? + CDPATH="${old_cdpath}" + return $ret +} + +get_package() +{ + typeset _url _file + _url="$1" + _file="$2" + log "Downloading ${_url}" + __rvm_curl -sS ${_url} > ${rvm_archives_path}/${_file} || + { + _return=$? + case $_return in + # duplication marker lfdgzkngdkjvnfjknkjvcnbjkncvjxbn + (60) + log " +Could not download '${_url}', you can read more about it here: +https://rvm.io/support/fixing-broken-ssl-certificates/ +To continue in insecure mode run 'echo insecure >> ~/.curlrc'. +" + ;; + # duplication marker lfdgzkngdkjvnfjknkjvcnbjkncvjxbn + (77) + log " +It looks like you have old certificates, you can read more about it here: +https://rvm.io/support/fixing-broken-ssl-certificates/ +" + ;; + # duplication marker lfdgzkngdkjvnfjknkjvcnbjkncvjxbn + (141) + log " +Curl returned 141 - it is result of a segfault which means it's Curls fault. +Try again and if it crashes more than a couple of times you either need to +reinstall Curl or consult with your distribution manual and contact support. +" + ;; + (*) + log " +Could not download '${_url}'. + curl returned status '$_return'. +" + ;; + esac + return $_return + } +} + +# duplication marker flnglfdjkngjndkfjhsbdjgfghdsgfklgg +rvm_install_gpg_setup() +{ + export rvm_gpg_command + { + rvm_gpg_command="$( \which gpg2 2>/dev/null )" && + [[ ${rvm_gpg_command} != "/cygdrive/"* ]] + } || { + rvm_gpg_command="$( \which gpg 2>/dev/null )" && + [[ ${rvm_gpg_command} != "/cygdrive/"* ]] + } || rvm_gpg_command="" + + debug "Detected GPG program: '$rvm_gpg_command'" + + [[ -n "$rvm_gpg_command" ]] || return $? +} + +# duplication marker rdjgndfnghdfnhgfdhbghdbfhgbfdhbn +verify_package_pgp() +{ + if + "${rvm_gpg_command}" --verify "$2" "$1" + then + log "GPG verified '$1'" + else + typeset _return=$? + + log "\ +GPG signature verification failed for '$1' - '$3'! Try to install GPG v2 and then fetch the public key: + + ${SUDO_USER:+sudo }${rvm_gpg_command##*/} --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB + +or if it fails: + + command curl -sSL https://rvm.io/mpapis.asc | ${SUDO_USER:+sudo }${rvm_gpg_command##*/} --import - + command curl -sSL https://rvm.io/pkuczynski.asc | ${SUDO_USER:+sudo }${rvm_gpg_command##*/} --import - + +In case of further problems with validation please refer to https://rvm.io/rvm/security +" + + exit ${_return} + fi +} + +verify_pgp() +{ + [[ -n "${1:-}" ]] || + { + debug "No PGP url given, skipping." + return 0 + } + + get_package "$1" "$2.asc" || + { + debug "PGP url given but does not exist: '$1'" + return 0 + } + + rvm_install_gpg_setup || + { + log "Found PGP signature at: '$1', +but no GPG software exists to validate it, skipping." + return 0 + } + + verify_package_pgp "${rvm_archives_path}/$2" "${rvm_archives_path}/$2.asc" "$1" +} + +get_and_unpack() +{ + typeset _url _file _patern _return _verify_pgp + _url="$1" + _file="$2" + _verify_pgp="$3" + + get_package "$_url" "$_file" || return $? + verify_pgp "$_verify_pgp" "$_file" || return $? + + [[ -d "${rvm_src_path}/rvm" ]] || \mkdir -p "${rvm_src_path}/rvm" + __rvm_cd "${rvm_src_path}/rvm" || + { + _return=$? + log "Could not change directory '${rvm_src_path}/rvm'." + return $_return + } + + # Remove existing installation + typeset _cleanup_cmd + _cleanup_cmd="rm -rf ${rvm_src_path}/rvm/{,.[!.],..?}*" + + $_cleanup_cmd || { + _return=$? + log "Could not remove old RVM sources. Try:\n\n\tsudo $_cleanup_cmd\n\nThen retry your task again." + return $_return + } + + # Unpack sources + __rvm_debug_command $rvm_tar_command xzf ${rvm_archives_path}/${_file} ${rvm_tar_options:-} --strip-components 1 || + { + _return=$? + log "Could not extract RVM sources." + return $_return + } +} + +rvm_install_default_settings() +{ + # Tracing, if asked for. + if + [[ "$*" == *--trace* ]] || (( ${rvm_trace_flag:-0} > 0 )) + then + set -o xtrace + rvm_trace_flag=1 + fi + + # Variable initialization, remove trailing slashes if they exist on HOME + true \ + ${rvm_trace_flag:=0} ${rvm_debug_flag:=0}\ + ${rvm_ignore_rvmrc:=0} HOME="${HOME%%+(\/)}" + + if + (( rvm_ignore_rvmrc == 0 )) + then + for rvmrc in /etc/rvmrc "$HOME/.rvmrc" + do + if + [[ -s "$rvmrc" ]] + then + if + GREP_OPTIONS="" \grep '^\s*rvm .*$' "$rvmrc" >/dev/null 2>&1 + then + printf "%b" " + Error: $rvmrc is for rvm settings only. + rvm CLI may NOT be called from within $rvmrc. + Skipping the loading of $rvmrc + " + exit 1 + else + source "$rvmrc" + fi + fi + done + fi + + if + [[ -z "${rvm_path:-}" ]] + then + if + (( UID == 0 )) + then + rvm_user_install_flag=0 + rvm_prefix="/usr/local" + rvm_path="${rvm_prefix}/rvm" + else + rvm_user_install_flag=1 + rvm_prefix="$HOME" + rvm_path="${rvm_prefix}/.rvm" + fi + fi + if [[ -z "${rvm_prefix}" ]] + then rvm_prefix=$( dirname $rvm_path ) + fi + + # duplication marker kkdfkgnjfndgjkndfjkgnkfjdgn + [[ -n "${rvm_user_install_flag:-}" ]] || + case "$rvm_path" in + (/usr/local/rvm) rvm_user_install_flag=0 ;; + ($HOME/*|/${USER// /_}*) rvm_user_install_flag=1 ;; + (*) rvm_user_install_flag=0 ;; + esac +} + +rvm_install_parse_params() +{ + install_rubies=() + install_gems=() + flags=( ./scripts/install ) + forwarded_flags=() + while + (( $# > 0 )) + do + token="$1" + shift + case "$token" in + + (--trace) + set -o xtrace + rvm_trace_flag=1 + flags=( -x "${flags[@]}" "$token" ) + forwarded_flags+=( "$token" ) + ;; + + (--debug|--quiet-curl) + flags+=( "$token" ) + forwarded_flags+=( "$token" ) + token=${token#--} + token=${token//-/_} + export "rvm_${token}_flag"=1 + printf "%b" "Turning on ${token/_/ } mode.\n" + ;; + + (--path) + if [[ -n "${1:-}" ]] + then + rvm_path="$1" + shift + else + fail "--path must be followed by a path." + fi + ;; + + (--branch|branch) # Install RVM from a given branch + if [[ -n "${1:-}" ]] + then + case "$1" in + (/*) + branch=${1#/} + ;; + (*/) + branch=master + if [[ "${1%/}" -ne rvm ]] && [[ "${1%/}" -ne mpapis ]] + then sources=(github.com/${1%/}/rvm) + fi + ;; + (*/*) + branch=${1#*/} + if [[ "${1%%/*}" -ne rvm ]] && [[ "${1%%/*}" -ne mpapis ]] + then sources=(github.com/${1%%/*}/rvm) + fi + ;; + (*) + branch="$1" + ;; + esac + shift + else + fail "--branch must be followed by a branchname." + fi + ;; + + (--source|source) + if [[ -n "${1:-}" ]] + then + if [[ "$1" = */*/* ]] + then + sources=($1) + shift + else + fail "--source must be in the format //." + fi + else + fail "--source must be followed by a source." + fi + ;; + + (--user-install|--ignore-dotfiles) + token=${token#--} + token=${token//-/_} + export "rvm_${token}_flag"=1 + printf "%b" "Turning on ${token/_/ } mode.\n" + ;; + + (--auto-dotfiles) + flags+=( "$token" ) + export "rvm_auto_dotfiles_flag"=1 + printf "%b" "Turning on auto dotfiles mode.\n" + ;; + + (--auto) + export "rvm_auto_dotfiles_flag"=1 + printf "%b" "Warning, --auto is deprecated in favor of --auto-dotfiles.\n" + ;; + + (--verify-downloads) + if [[ -n "${1:-}" ]] + then + export rvm_verify_downloads_flag="$1" + forwarded_flags+=( "$token" "$1" ) + shift + else + fail "--verify-downloads must be followed by level(0|1|2)." + fi + ;; + + (--autolibs=*) + flags+=( "$token" ) + export rvm_autolibs_flag="${token#--autolibs=}" + forwarded_flags+=( "$token" ) + ;; + + (--without-gems=*|--with-gems=*|--with-default-gems=*) + flags+=( "$token" ) + value="${token#*=}" + token="${token%%=*}" + token="${token#--}" + token="${token//-/_}" + export "rvm_${token}"="${value}" + printf "%b" "Installing RVM ${token/_/ }: ${value}.\n" + ;; + + (--version|version) + version="$1" + shift + ;; + + (head|master) + version="head" + branch="master" + ;; + + (stable) + version="latest" + ;; + + (latest|latest-*|+([[:digit:]]).+([[:digit:]]).+([[:digit:]])) + version="$token" + ;; + + (--ruby) + install_rubies+=( ruby ) + ;; + + (--ruby=*) + token=${token#--ruby=} + install_rubies+=( ${token//,/ } ) + ;; + + (--rails) + install_gems+=( rails ) + ;; + + (--gems=*) + token=${token#--gems=} + install_gems+=( ${token//,/ } ) + ;; + + (--add-to-rvm-group) + export rvm_add_users_to_rvm_group="$1" + shift + ;; + + (help) + usage + exit 0 + ;; + + (*) + usage + exit 1 + ;; + + esac + done + + if (( ${#install_gems[@]} > 0 && ${#install_rubies[@]} == 0 )) + then install_rubies=( ruby ) + fi + + true "${version:=head}" + true "${branch:=master}" + + if [[ -z "${sources[@]}" ]] + then sources=("${DEFAULT_SOURCES[@]}") + fi + + rvm_src_path="$rvm_path/src" + rvm_archives_path="$rvm_path/archives" + rvm_releases_url="https://rvm.io/releases" +} + +rvm_install_validate_rvm_path() +{ + case "$rvm_path" in + (*[[:space:]]*) + printf "%b" " +It looks you are one of the happy *space* users (in home dir name), +RVM is not yet fully ready for it, use this trick to fix it: + + sudo mkdir -p /${USER// /_}.rvm + sudo chown -R \"$USER:\" /${USER// /_}.rvm + echo \"export rvm_path=/${USER// /_}.rvm\" >> \"$HOME/.rvmrc\" + +and start installing again. + +" + exit 2 + ;; + (/usr/share/ruby-rvm) + printf "%b" " +It looks you are one of the happy Ubuntu users, +RVM packaged by Ubuntu is old and broken, +follow this link for details how to fix: + + https://stackoverflow.com/a/9056395/497756 + +" + [[ "${rvm_uses_broken_ubuntu_path:-no}" == "yes" ]] || exit 3 + ;; + esac + + if [[ "$rvm_path" != "/"* ]] + then fail "The rvm install path must be fully qualified. Tried $rvm_path" + fi +} + +rvm_install_validate_volume_mount_mode() +{ + \typeset path partition test_exec + + path=$rvm_path + + # Directory $rvm_path might not exists at this point so we need to traverse the tree upwards + while [[ -n "$path" ]] + do + if [[ -d $path ]] + then + partition=`df -P $path | awk 'END{print $1}'` + + test_exec=$(mktemp $path/rvm-exec-test.XXXXXX) + echo '#!/bin/sh' > "$test_exec" + chmod +x "$test_exec" + + if ! "$test_exec" + then + rm -f "$test_exec" + printf "%b" " +It looks that scripts located in ${path}, which would be RVM destination ${rvm_path}, +are not executable. One of the reasons might be that partition ${partition} holding this location +is mounted in *noexec* mode, which prevents RVM from working correctly. Please verify your setup +and re-mount partition ${partition} without the noexec option." + exit 2 + fi + + rm -f "$test_exec" + break + fi + + path=${path%/*} + done +} + +rvm_install_select_and_get_version() +{ + typeset dir _version_release _version + + for dir in "$rvm_src_path" "$rvm_archives_path" + do + [[ -d "$dir" ]] || mkdir -p "$dir" + done + + _version_release="${version}" + case "${version}" in + (head) + _version_release="${branch}" + install_head sources[@] ${branch:-master} + ;; + + (latest) + _version=$(fetch_version sources[@]) + install_release sources[@] "$_version" + ;; + + (latest-minor) + version="$(<"$rvm_path/VERSION")" + _version=$(fetch_version sources[@] ${version%.*}) + install_release sources[@] "$_version" + ;; + + (latest-*) + _version=$(fetch_version sources[@] ${version#latest-}) + install_release sources[@] "$_version" + ;; + + (+([[:digit:]]).+([[:digit:]]).+([[:digit:]])) # x.y.z + install_release sources[@] ${version} + ;; + + (*) + fail "Something went wrong, unrecognized version '$version'" + ;; + esac + echo "${_version_release}" > "$rvm_path/RELEASE" +} + +rvm_install_main() +{ + [[ -f ./scripts/install ]] || + { + log "'./scripts/install' can not be found for installation, something went wrong, it usually means your 'tar' is broken, please report it here: https://github.com/rvm/rvm/issues" + return 127 + } + + # required flag - path to install + flags+=( --path "$rvm_path" ) + \command bash "${flags[@]}" +} + +rvm_install_ruby_and_gems() +( + if + (( ${#install_rubies[@]} > 0 )) + then + source ${rvm_scripts_path:-${rvm_path}/scripts}/rvm + source ${rvm_scripts_path:-${rvm_path}/scripts}/functions/version + __rvm_print_headline + + for _ruby in ${install_rubies[@]} + do command rvm "${forwarded_flags[@]}" install ${_ruby} + done + # set the first one as default, skip rest + for _ruby in ${install_rubies[@]} + do + rvm "${forwarded_flags[@]}" alias create default ${_ruby} + break + done + + for _gem in ${install_gems[@]} + do rvm "${forwarded_flags[@]}" all do gem install ${_gem} + done + + printf "%b" " + * To start using RVM you need to run \`source $rvm_path/scripts/rvm\` + in all your open shell windows, in rare cases you need to reopen all shell windows. +" + + if + [[ "${install_gems[*]}" == *"rails"* ]] + then + printf "%b" " + * To start using rails you need to run \`rails new \`. +" + fi + fi +) + +rvm_install() +{ + rvm_install_initialize + rvm_install_commands_setup + rvm_install_default_settings + rvm_install_parse_params "$@" + rvm_install_validate_rvm_path + rvm_install_validate_volume_mount_mode + rvm_install_select_and_get_version + rvm_install_main + rvm_install_ruby_and_gems +} + +rvm_install "$@" diff --git a/scp.sh b/scp.sh new file mode 100644 index 0000000..c9cda1e --- /dev/null +++ b/scp.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +scp -r ./dist/* huashiai@192.168.13.27:/home/huashiai/workspace/bqw-ai/nginx/html/update_files diff --git a/src/main/config.js b/src/main/config.js new file mode 100644 index 0000000..f54de09 --- /dev/null +++ b/src/main/config.js @@ -0,0 +1,36 @@ +import logger from './utils/logger' +import { join } from 'path' + +let configUrl=join(__dirname, '../../config/customConfig.json') + +logger.info("configUrl:"+configUrl) + +let configPath=configUrl +const config = require('electron-json-config').factory(configPath) +logger.info(config) + +logger.info("================config.get(MAIN_VITE_DIFY_HOST):") +logger.info(config.get('MAIN_VITE_DIFY_HOST')) +logger.info(config.get('MAIN_VITE_DIFY_HOST')) +logger.info(config.get('MAIN_VITE_DIFY_HOST')) +logger.info(config.get('MAIN_VITE_DIFY_HOST')) + + + function initFunc() { + if(typeof config.get('MAIN_VITE_DIFY_HOST')=="undefined"){ + logger.info("set config to local json:import.meta.env.MAIN_VITE_DIFY_HOST:"+import.meta.env.MAIN_VITE_DIFY_HOST) + config.set("MAIN_VITE_DIFY_HOST", import.meta.env.MAIN_VITE_DIFY_HOST) + } +} + + + + +export default { + set(key, value) { + config.set(key, value) + }, + get(param) { + return config.get(param) + } +} diff --git a/src/main/dify.js b/src/main/dify.js new file mode 100644 index 0000000..f0d4158 --- /dev/null +++ b/src/main/dify.js @@ -0,0 +1,56 @@ +import axios from 'axios'; +import { getStoreValue } from './store.js'; +import logger from './utils/logger' + +let difyRetryRequesTimer = null; + +export async function difyRetryRequestTimer() { + // 如果定时器已经存在,则不再重复启动 + if (difyRetryRequesTimer !== null) { + logger.info("定时器已存在,不再重复启动。"); + return; + } + + // 启动定时器 + difyRetryRequesTimer = setInterval(async () => { + try { + // 从 store 中获取必要的数据 + const difySite = getStoreValue("difySite"); + const accessToken = getStoreValue("dify_access_token"); + + // 检查必要的参数是否存在 + if (!difySite || !accessToken) { + console.error("缺少必要的参数:difySite 或 dify_access_token"); + return; + } + + // 构造请求 URL 和 Headers + const url = `${difySite}/console/api/workspaces/current`; + const response = await axios.get(url, { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + } + }); + + // 打印成功响应 + logger.info("======================== 请求成功 ========================"); + logger.info("difySite:", difySite); + logger.info("dify_access_token:", accessToken); + logger.info("响应数据:", response.data); + + } catch (error) { + // 捕获并处理错误 + console.error("======================== 请求失败 ========================"); + console.error("错误信息:", error.message || error); + } + }, 5000); // 每 5 秒执行一次 +} + +export function stopDifyRetryRequestTimer() { + if (difyRetryRequesTimer !== null) { + clearInterval(difyRetryRequesTimer); + difyRetryRequesTimer = null; + logger.info("定时器已停止。"); + } +} diff --git a/src/main/index.js b/src/main/index.js new file mode 100644 index 0000000..66e98b2 --- /dev/null +++ b/src/main/index.js @@ -0,0 +1,176 @@ +import { app, shell, BrowserWindow, ipcMain, Menu, session, screen, dialog } from 'electron' +import { electronApp, optimizer } from '@electron-toolkit/utils' +import Store from 'electron-store' +import { createWindow, createDrageWindow, unregisterAllShortcuts } from './window.js' +import { setupIPC } from './ipc.js' +import { createTray, destroyTray } from './tray.js' +import { getStoreValue } from './store.js' +import XEUtils from 'xe-utils' + +import logger from './utils/logger' + +import AutoLaunch from 'auto-launch' + +import WebSocketClient from './utils/WebSocketClient'; + +let wsClient=null + +wsClient=new WebSocketClient({ + autoReconnect: true, + autoReconnectAttempts: 9999, + autoReconnectInterval: 5000, + timeout: 30000, +}) + +wsClient.on('open', () => { + logger.info('WebSocket连接已打开') +}); + + +var minecraftAutoLauncher = new AutoLaunch({ + name: '百千万AI智能体共创平台', + path: app.getPath('exe') +}); + +app.commandLine.appendSwitch('allow-insecure-localhost'); // 允许本地回环地址使用不安全连接 +app.commandLine.appendSwitch('ignore-certificate-errors'); // 忽略证书错误(开发时可用) + +const h5_client_url=getStoreValue("h5_client_url") +if(!XEUtils.isEmpty(h5_client_url)){ + logger.info("=======================h5_client_url 非空,设置 unsafely-treat-insecure-origin-as-secure的值为:"+h5_client_url) + app.commandLine.appendSwitch('unsafely-treat-insecure-origin-as-secure', h5_client_url); +} + +let difySite=getStoreValue("difySite") +if(!XEUtils.isEmpty(difySite)){ + difySite = XEUtils.parseUrl(difySite) + logger.info("=======================h5_client_url 非空,设置 unsafely-treat-insecure-origin-as-secure的值为:"+h5_client_url+","+difySite.origin) + app.commandLine.appendSwitch('unsafely-treat-insecure-origin-as-secure', h5_client_url+","+difySite.origin); +} + + +// 设置控制台编码为 UTF-8 +logger.info(`当前运行平台: ${process.platform}`) +if (process.platform === 'win32') { + logger.info('Windows 系统,设置控制台编码为 UTF-8') + require('child_process').execSync('chcp 65001', { stdio: 'ignore' }) +} else { + logger.info('非 Windows 系统,使用默认编码') +} + +logger.info('%cRed text. %cGreen text', 'color: red', 'color: green') + +const store = new Store() + +app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') +app.commandLine.appendSwitch('ignore-certificate-errors') + +app.disableHardwareAcceleration() + +logger.info(app.getPath('userData')) + + + +// 检查是否为第一个实例 +const gotTheLock = app.requestSingleInstanceLock() + +if (!gotTheLock) { + // 如果不是第一个实例,尝试激活第一个实例的窗口 + const windows = BrowserWindow.getAllWindows() + if (windows.length > 0) { + const mainWindow = windows[0] + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + mainWindow.show() + mainWindow.focus() + } + app.quit() // 退出当前实例 +} else { + // 这是第一个实例 + + // 监听第二个实例的启动 + app.on('second-instance', (event, commandLine, workingDirectory) => { + // 当运行第二个实例时,将显示第一个实例的窗口 + const windows = BrowserWindow.getAllWindows() + if (windows.length > 0) { + const mainWindow = windows[0] + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + mainWindow.show() + mainWindow.focus() + } + }) + + app.whenReady().then(() => { + + // 获取默认会话并全局设置允许第三方 Cookie + const defaultSession = session.defaultSession; + + // 设置 Cookie 策略以允许第三方 Cookie + defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { + callback(true); + }); + + electronApp.setAppUserModelId('com.electron') + + app.on('browser-window-created', (_, window) => { + optimizer.watchWindowShortcuts(window) + }) + + + + + // 根据存储的状态决定是否创建悬浮窗口 + if (getStoreValue('showDrageWindow')) { + createDrageWindow() + } + + + createWindow() + + + createTray() + setupIPC() + + + minecraftAutoLauncher.isEnabled() + .then(function(isEnabled){ + if(isEnabled){ + return; + } + minecraftAutoLauncher.enable(); + }) + .catch(function(err){ + logger.info(err) + }); + + + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) + }) + + // 修改窗口关闭行为 + app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + if (!app.isQuiting) { + // 如果不是主动退出,则隐藏所有窗口 + BrowserWindow.getAllWindows().forEach(window => { + window.hide() + }) + } else { + // 如果是主动退出,则销毁托盘并退出应用 + destroyTray() + app.quit() + } + } + }) + + // 在应用退出时注销所有快捷键 + app.on('will-quit', () => { + unregisterAllShortcuts() + }) +} diff --git a/src/main/ipc.js b/src/main/ipc.js new file mode 100644 index 0000000..1ba9168 --- /dev/null +++ b/src/main/ipc.js @@ -0,0 +1,242 @@ +import { BrowserWindow, ipcMain, shell, Notification, app } from "electron"; +import { setStoreValue, getStoreValue } from './store.js' +import { createNewWindow, createWindow, getMainWindow, getDrageWindow, closeApiConfigWindow, closeConfigWindow } from './window.js' +import { difyRetryRequestTimer } from './dify.js' +import axios from 'axios' +import log from 'electron-log/main'; +log.initialize(); +import {checkForUpdates} from "./utils/updateUtils" +import logger from './utils/logger' + + +function isValidUrl(_url) { + const containsApp = _url.includes('/app/'); + const containsConfigurationOrWorkflow = _url.includes('/configuration') || _url.includes('/workflow'); + + // 只有当 URL 包含 'app' 并且包含 'configuration' 或 'workflow' 时,才返回 true + return containsApp && containsConfigurationOrWorkflow; +} + +export function setupIPC() { + ipcMain.handle('initClientData', (event, data) => { + logger.info('=======================================initClientData') + logger.info(data) + + setStoreValue("token", data.token); + setStoreValue("dify_access_token:", data.dify_access_token); + setStoreValue("dify_refresh_token", data.dify_refresh_token); + setStoreValue("userInfo", data.userInfo); + + setStoreValue("apiUrl", data.apiUrl); + + setStoreValue("difySite", data.difySite); + + setStoreValue("test_12222222", "test_12222222"); + + + + difyRetryRequestTimer() + + return 'pong' + }) + + ipcMain.handle('openNewWindow', (event, _url, dify_access_token, refresh_token) => { + logger.info('=======================================openNewWindow') + logger.info(_url) + const _sandbox=isValidUrl(_url) + logger.info('=======================================openNewWindow _sandbox:',_sandbox) + createNewWindow(_url, dify_access_token, refresh_token,_sandbox) + return 'pong' + }) + + ipcMain.handle('setMainWindowTitle', (event, title) => { + + const mainWindow = getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.setTitle(title); + } + + return 'pong' + }) + + ipcMain.handle('setLocalStorage', (event, key, value) => { + setStoreValue("token", data.token); + return 'pong' + }) + + ipcMain.handle('setGuiLocalStorage', (event, key, value) => { + logger.info("=============================================: setGuiLocalStorage key", key) + logger.info("=============================================: setGuiLocalStorage value", value) + setStoreValue(key, value); + return 'pong' + }) + + ipcMain.handle('getGuiLocalStorage', (event, key) => { + return getStoreValue(key) + }) + + ipcMain.handle('difyWebUploadFileToApi', async (event, datasetId, data) => { + logger.info("=============================================: difyWebUploadFileToApi"); + logger.info("=============================================: datasetId: " + datasetId); + logger.info("=============================================: data: "); + logger.info(data) + + try { + const java_api = getStoreValue("apiUrl") + "/bqw-ai" + "/app/api/knowledge/addDocumentToKnowledge"; + const token = getStoreValue("token"); + + if (!java_api || !token) { + throw new Error("API URL or Token is missing in the store."); + } + + const params = { + kg_id: datasetId, + files: datasetId, + knowledgeDocumentParams: JSON.parse(data) + }; + + logger.info("=======================java_api:"+java_api); + logger.info("=======================params:") + logger.info(params) + + const response = await axios.post(java_api, params, { + headers: { + 'x-access-token': `${token}`, + 'Content-Type': 'application/json' + } + }); + + logger.info("Response from server:", response.data); + + // 获取当前窗口并关闭 + const currentWindow = event.sender.getOwnerBrowserWindow(); + if (currentWindow && !currentWindow.isDestroyed()) { + currentWindow.close(); + } + + return response.data; + } catch (error) { + console.error("Error making POST request:", error.message); + throw error; + } + }); + + ipcMain.on('difyWebUpdateDatasetsInfo', (event) => { + return 'pong' + }) + + ipcMain.on('app:window:set-position', (event, x, y) => { + const drageWindow = getDrageWindow(); + if (drageWindow) { + drageWindow.setPosition(x, y) + } + return 'pong' + }) + + ipcMain.handle('app:window:switch-show-main-window', (event) => { + const mainWindow = getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed()) { + if (mainWindow.isVisible()) { + mainWindow.hide(); + } else { + mainWindow.show(); + } + } else { + createWindow(); + } + }); + + + + ipcMain.handle('app:window:get-position', (event) => { + const drageWindow = getDrageWindow(); + if (drageWindow) { + const [x, y] = drageWindow.getPosition(); + return { x, y } + } + return { x: 0, y: 0 } + }) + + // 获取存储值 + ipcMain.handle('getStoreValue', (event, key) => { + return getStoreValue(key) + }) + + // 设置存储值 + ipcMain.handle('setStoreValue', (event, key, value) => { + return setStoreValue(key, value) + }) + // 设置存储值 + ipcMain.handle('setStoreValueByConfig', (event, key, value) => { + return setStoreValue(key, value) + }) + + // 关闭配置窗口 + ipcMain.on('closeApiConfigWindow', () => { + closeApiConfigWindow() + }) + + ipcMain.on('closeConfigWindow', () => { + closeConfigWindow() + }) + + // 添加新的 IPC 监听处理函数 + ipcMain.handle('openNewPage', (event, url) => { + const newWindow = new BrowserWindow({ + fullscreen: false, + width: 1200, + height: 700, + minWidth: 1200, + minHeight: 700, + webPreferences: { + webSecurity: false, // 禁用 Web 安全 + nodeIntegration: true, + contextIsolation: false, + } + }); + + newWindow.loadURL(url); + + // 可选:添加错误处理 + newWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('页面加载失败:', errorDescription); + }); + + return 'success'; + }); + + // 添加新的 IPC 监听处理函数 + ipcMain.handle('openCSDNAiPage', (event, url) => { + const newWindow = new BrowserWindow({ + fullscreen: false, + width: 1600, + height: 900, + webPreferences: { + webSecurity: false, // 禁用 Web 安全 + nodeIntegration: false, + contextIsolation: false, + } + }); + + newWindow.loadURL(url); + + // 可选:添加错误处理 + newWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('页面加载失败:', errorDescription); + }); + + return 'success'; + }); + + + ipcMain.handle('openNewPagebySystemBrowser', (event, url) => { + shell.openExternal(url); + return 'success'; + }); + + + // 添加新的 IPC 监听处理函数 + ipcMain.handle('updateToNewVersion', async (event) => { + checkForUpdates({},false) + }); +} diff --git a/src/main/menu.js b/src/main/menu.js new file mode 100644 index 0000000..768ea88 --- /dev/null +++ b/src/main/menu.js @@ -0,0 +1,6 @@ +import { Menu } from 'electron'; + +export function createMenu(mainWindow, difyfullScreenWindow) { + // 移除应用菜单 + Menu.setApplicationMenu(null) +} \ No newline at end of file diff --git a/src/main/store.js b/src/main/store.js new file mode 100644 index 0000000..03c3d41 --- /dev/null +++ b/src/main/store.js @@ -0,0 +1,26 @@ +import Store from 'electron-store'; + +const store = new Store({ + defaults: { + showDrageWindow: true // 默认显示悬浮窗口 + } +}); + +export const getStore = () => store; + + +export const deleteStore = (key) => { + store.delete(key); +}; + +export const setStoreValue = (key, value) => { + store.set(key, value); +}; + +export const getStoreValue = (key) => { + return store.get(key); +}; + +export function clearStore() { + store.clear(); +} diff --git a/src/main/tray.js b/src/main/tray.js new file mode 100644 index 0000000..eaf0a20 --- /dev/null +++ b/src/main/tray.js @@ -0,0 +1,186 @@ +import { Tray, Menu, app, BrowserWindow } from 'electron' +import { join } from 'path' +import icon from '../../resources/icon.png?asset' +import { getMainWindow, createWindow, createApiConfigWindow, createConfigWindow ,getDragWindow} from './window.js' +import { getStoreValue, setStoreValue, clearStore } from './store.js' +import { createDrageWindow, getDrageWindow } from './window.js' +import logger from './utils/logger' +import fs from 'fs' + +let tray = null + +import {checkForUpdates} from "./utils/updateUtils" + +async function clearBrowserCache() { + try { + // 清除所有窗口的浏览器缓存 + const windows = BrowserWindow.getAllWindows() + for (const window of windows) { + const session = window.webContents.session + await session.clearStorageData({ + storages: [ + 'appcache', + 'cookies', + 'filesystem', + 'indexdb', + 'localstorage', + 'shadercache', + 'websql', + 'serviceworkers', + 'cachestorage' + ] + }) + } + + logger.info('浏览器缓存清除成功') + } catch (error) { + logger.error('清除浏览器缓存失败:', error) + } +} + +export function createTray() { + // 创建托盘图标 + tray = new Tray(icon) + + // 设置托盘图标的提示文本 + tray.setToolTip('Dify Market Manager') + + // 创建右键菜单 + const contextMenu = Menu.buildFromTemplate([ + { + label: '显示主窗口', + click: () => { + const mainWindow = getMainWindow() + if (mainWindow) { + mainWindow.show() + } else { + createWindow() + } + } + }, + { + label: '隐藏主窗口', + click: () => { + const mainWindow = getMainWindow() + if (mainWindow) { + mainWindow.hide() + } + } + }, + { type: 'separator' }, + { + label: '配置', + submenu: [ + { type: 'separator' }, + { + label: '配置客户端地址', + click: () => { + createConfigWindow() + } + } + ] + }, + { type: 'separator' }, + { + label: '显示隐藏桌面悬浮', + click: () => { + const drageWindow = getDrageWindow() + if (!drageWindow) { + createDrageWindow() + } else { + if (drageWindow.isVisible()) { + drageWindow.hide() + setStoreValue('showDrageWindow', false) + } else { + drageWindow.show() + setStoreValue('showDrageWindow', true) + } + } + + + + } + }, + { type: 'separator' }, + { + label: '检查更新', + click: async (menuItem) => { + checkForUpdates(menuItem,true) + } + }, + { type: 'separator' }, + { + label: '清除缓存', + click: async () => { + await clearBrowserCache() + + // 删除配置文件 + try { + const userDataPath = app.getPath('userData') + const configPath = join(userDataPath, 'config.json') + if (fs.existsSync(configPath)) { + fs.unlinkSync(configPath) + logger.info('配置文件删除成功') + } + } catch (error) { + logger.error('删除配置文件失败:', error) + } + + // 重新加载所有窗口 + const windows = BrowserWindow.getAllWindows() + for (const window of windows) { + window.reload() + } + } + }, + { type: 'separator' }, + { + label: '退出登录', + click: async () => { + await clearBrowserCache() + // 重新加载主窗口 + const mainWindow = getMainWindow() + if (mainWindow) { + mainWindow.reload() + } + logger.info('用户已退出登录') + } + }, + { type: 'separator' }, + { + label: '退出应用', + click: () => { + app.isQuiting = true + // 确保所有窗口都被关闭 + BrowserWindow.getAllWindows().forEach(window => { + window.destroy() + }) + app.quit() + } + } + ]) + + // 设置托盘图标的右键菜单 + tray.setContextMenu(contextMenu) + + // 点击托盘图标时显示/隐藏主窗口 + tray.on('click', () => { + const mainWindow = getMainWindow() + if (mainWindow) { + if (mainWindow.isVisible()) { + mainWindow.hide() + } else { + mainWindow.show() + } + } else { + createWindow() + } + }) +} + +export function destroyTray() { + if (tray) { + tray.destroy() + tray = null + } +} diff --git a/src/main/utils/WebSocketClient.js b/src/main/utils/WebSocketClient.js new file mode 100644 index 0000000..557bd3b --- /dev/null +++ b/src/main/utils/WebSocketClient.js @@ -0,0 +1,222 @@ +import WebSocket from 'ws'; +import logger from './logger' +import { getStoreValue } from '../store.js' + +import { Notification } from "electron"; + +class WebSocketClient { + constructor(options = {}) { + this.reconnectInterval = 5000; // 重连间隔时间 + this.lockReconnect = false; + this.ws = null; + this.pingTimeout = null; + this.reconnectAttempts = 0; + this.messageHandlers = new Map(); + this.isConnected = false; + + // 初始化时立即创建连接 + this.createConnect(); + } + + createConnect() { + try { + const apiUrl = getStoreValue("apiUrl"); + const userInfo = getStoreValue("userInfo"); + const token = getStoreValue("token"); + + // 检查必要的连接信息是否存在 + if (!apiUrl || !userInfo || !token) { + logger.error("WebSocket连接信息不完整,等待重试"); + logger.error("apiUrl:", apiUrl); + logger.error("userInfo:", userInfo); + logger.error("token:", token); + this.scheduleReconnect(); + return; + } + + + // 处理基础URL + let baseUrl = apiUrl.replace("https://", "wss://").replace("http://", "ws://"); + // 移除末尾的斜杠(如果有) + baseUrl = baseUrl.replace(/\/$/, ''); + + // 构建完整的WebSocket URL + this.url = `${baseUrl}/bqw-ai/websocket/${userInfo.id}`; + // this.url = `${baseUrl}/bqw-ai/${userInfo.id}`; + + logger.info("==================== WebSocketClient create WebSocketClient"); + logger.info("WebSocket URL: " + this.url); + + // 设置WebSocket选项 + this.options = { + headers: { + 'User-Agent': 'WebSocketClient', + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Version': '13' + }, + handshakeTimeout: 10000, + perMessageDeflate: false, + followRedirects: true, + rejectUnauthorized: false + }; + + logger.info("Token:", token); // 添加token日志 + // 创建连接 + this.connect(); + } catch (error) { + logger.error("创建WebSocket连接时发生错误:", error); + this.scheduleReconnect(); + } + } + + scheduleReconnect() { + if (this.lockReconnect) return; + + this.lockReconnect = true; + logger.info("==================== WebSocketClient schedule reconnect"); + + setTimeout(() => { + this.lockReconnect = false; + this.createConnect(); // 重新获取本地缓存并创建连接 + }, this.reconnectInterval); + } + + connect() { + logger.info("==================== WebSocketClient begin connect"); + if (this.lockReconnect) return; + + try { + const token = getStoreValue("token"); + // 直接在构造函数中传递协议 + this.ws = new WebSocket(this.url, token, this.options); + + this.ws.on('open', () => { + logger.info('😀😀😀😀 WebSocket connect create ok 😀😀😀😀'); + logger.info('😀😀😀😀 WebSocket connect create ok 😀😀😀😀'); + logger.info('😀😀😀😀 WebSocket connect create ok 😀😀😀😀'); + logger.info('WebSocket protocol:', this.ws.protocol); // 添加协议日志 + this.isConnected = true; + this.reconnectAttempts = 0; + this.lockReconnect = false; + this.startHeartbeat(); + this.emit('open'); + }); + + this.ws.on('message', (data) => { + try { + logger.info('收到消息:', data.toString()); + const message = JSON.parse(data); + this.handleMessage(message); + } catch (error) { + logger.error('消息解析错误:', error); + } + }); + + this.ws.on('close', (code, reason) => { + logger.info(`WebSocket连接已关闭,代码: ${code}, 原因: ${reason}`); + this.isConnected = false; + this.stopHeartbeat(); + this.emit('close', { code, reason }); + this.scheduleReconnect(); + }); + + this.ws.on('error', (error) => { + logger.error('WebSocket error:', error); + this.emit('error', error); + this.scheduleReconnect(); + }); + + this.ws.on('unexpected-response', (request, response) => { + logger.info(`收到响应: ${response.statusCode} ${response.statusMessage}`); + logger.info('响应头:', response.headers); + if (response.statusCode === 101) { + logger.info('WebSocket升级成功'); + return; + } + this.scheduleReconnect(); + }); + + } catch (error) { + logger.error('WebSocket connect error:', error); + this.scheduleReconnect(); + } + } + + startHeartbeat() { + this.pingTimeout = setInterval(() => { + if (this.isConnected) { + this.ws.send("heartcheck"); + } + }, 30000); + } + + stopHeartbeat() { + if (this.pingTimeout) { + clearInterval(this.pingTimeout); + this.pingTimeout = null; + } + } + + send(message) { + if (!this.isConnected) { + console.error('WebSocket未连接'); + return false; + } + + try { + const data = typeof message === 'string' ? message : JSON.stringify(message); + this.ws.send(data); + return true; + } catch (error) { + console.error('发送消息失败:', error); + return false; + } + } + + on(event, handler) { + if (!this.messageHandlers.has(event)) { + this.messageHandlers.set(event, new Set()); + } + this.messageHandlers.get(event).add(handler); + } + + off(event, handler) { + if (this.messageHandlers.has(event)) { + this.messageHandlers.get(event).delete(handler); + } + } + + emit(event, data) { + if (this.messageHandlers.has(event)) { + this.messageHandlers.get(event).forEach(handler => { + try { + handler(data); + } catch (error) { + console.error(`事件处理器错误 (${event}):`, error); + } + }); + } + } + + handleMessage(message) { + if (message.cmd==="Notification") { + logger.info("Notification:", message) + const msg=JSON.parse(message.msgTxt) + // systemMsgType + const isNotification = getStoreValue("Notification_"+msg.systemMsgType); + if (isNotification!=0){ + new Notification({ title: msg.title, body: msg.content }).show() + } + } + this.emit('message', message); + } + + close() { + if (this.ws) { + this.ws.close(); + } + } +} + +export default WebSocketClient; diff --git a/src/main/utils/difyUtils.js b/src/main/utils/difyUtils.js new file mode 100644 index 0000000..b071733 --- /dev/null +++ b/src/main/utils/difyUtils.js @@ -0,0 +1,16 @@ +// 检查路径是否包含 '/api/datasets/' 并以 'documents' 结尾 +export function isApiDatasetsDocumentsPath(path) { + const containsApiDatasets = path.includes('/api/datasets/'); + const endsWithDocuments = path.endsWith('documents'); + return containsApiDatasets && endsWithDocuments; +} + +// 提取路径中的 ID 字符串 +export function extractIdFromPath(path) { + const regex = /\/api\/datasets\/([^\/]+)\/documents$/; + const match = path.match(regex); + if (match && match[1]) { + return match[1]; // 返回捕获组中的 ID + } + return null; // 如果没有匹配成功,返回 null +} diff --git a/src/main/utils/index.js b/src/main/utils/index.js new file mode 100644 index 0000000..4489eac --- /dev/null +++ b/src/main/utils/index.js @@ -0,0 +1,49 @@ +import * as http from "node:http"; +import * as https from "node:https" +import path, { join } from "path"; +import fs from "fs" +import { app } from "electron"; + +export function download(fileUrl, filePath=null) { + if(filePath==null){ + const filename = path.basename(new URL(fileUrl).pathname); + const updateDir = join(app.getPath('userData')) + filePath = path.resolve(updateDir, filename); + } + + return new Promise((resolve, reject) => { + // 根据 URL 前缀选择不同的下载方式 + const protocol = fileUrl.startsWith('https://') ? https : + fileUrl.startsWith('http://') ? http : + null; + + if (!protocol) { + reject(new Error('不支持的 URL 协议')); + return; + } + + protocol.get(fileUrl, (response) => { + // 检查响应状态码 + if (response.statusCode !== 200) { + reject(new Error(`下载失败,状态码: ${response.statusCode}`)); + return; + } + + const file = fs.createWriteStream(filePath); + + response.pipe(file); + + file.on('finish', () => { + file.close(); + resolve(filePath); + }); + + file.on('error', (err) => { + fs.unlink(filePath, () => {}); // 删除不完整的文件 + reject(err); + }); + }).on('error', (err) => { + reject(err); + }); + }); +} diff --git a/src/main/utils/logger.js b/src/main/utils/logger.js new file mode 100644 index 0000000..a745033 --- /dev/null +++ b/src/main/utils/logger.js @@ -0,0 +1,14 @@ +import log from 'electron-log/main'; + +log.initialize(); + + +export const logger = { + log: log.info, + info: log.info, + error: log.error, + warn: log.warn, + debug: log.debug +} + +export default logger diff --git a/src/main/utils/updateUtils.js b/src/main/utils/updateUtils.js new file mode 100644 index 0000000..af16739 --- /dev/null +++ b/src/main/utils/updateUtils.js @@ -0,0 +1,75 @@ + +import { autoUpdater} from "electron-updater" +import { dialog} from "electron"; +import { getStoreValue } from "../store"; + +/** + * updater.js + * + * Please use manual update only when it is really required, otherwise please use recommended non-intrusive auto update. + * + * Import steps: + * 1. create `updater.js` for the code snippet + * 2. require `updater.js` for menu implementation, and set `checkForUpdates` callback from `updater` for the click property of `Check Updates...` MenuItem. + */ + +let updater +autoUpdater.autoDownload = false + +autoUpdater.on('error', (error) => { + dialog.showErrorBox('Error: ', error == null ? "unknown" : (error.stack || error).toString()) +}) + +autoUpdater.on('update-available', () => { + dialog.showMessageBox({ + type: 'info', + title: '发现新版本', + message: '发现新版本, 是否需要现在更新?', + buttons: ['立即更新', '取消'] + }).then((buttonIndex) => { + if (buttonIndex.response === 0) { + autoUpdater.downloadUpdate() + } + else { + updater.enabled = true + updater = null + } + }) +}) + +autoUpdater.on('update-not-available', () => { + if (updater.toast){ + dialog.showMessageBox({ + type: 'info', + title: '暂未发现新版本', + message: '当前版本已经是最新的了' + }) + } + updater.enabled = true + updater = null +}) + +autoUpdater.on('update-downloaded', () => { + dialog.showMessageBox({ + type: 'info', + title: '安装更新', + message: '新版本已经下载结束, 应用即将退出并重新安装...' + }).then(() => { + setImmediate(() => autoUpdater.quitAndInstall()) + }) +}) + +// export this to MenuItem click callback +export function checkForUpdates (menuItem,toast=true) { + if(menuItem!=null){ + updater = menuItem + updater.enabled = false + updater.toast = toast + + const h5_client_url=getStoreValue("h5_client_url")+"/update_files" + + autoUpdater.setFeedURL(h5_client_url) + + autoUpdater.checkForUpdates() + } +} diff --git a/src/main/window.js b/src/main/window.js new file mode 100644 index 0000000..b627ac6 --- /dev/null +++ b/src/main/window.js @@ -0,0 +1,612 @@ +import { BrowserWindow, screen, Menu, shell, session, app, globalShortcut, dialog ,systemPreferences } from 'electron' +import { join } from 'path' +import { is } from '@electron-toolkit/utils' +import icon from '../../resources/icon.png?asset' +import { isApiDatasetsDocumentsPath, extractIdFromPath } from './utils/difyUtils.js' +import XEUtils from 'xe-utils' +import logger from './utils/logger' +import { createMenu } from './menu.js' +import { setStoreValue, getStoreValue,deleteStore } from './store.js' + +import {checkForUpdates} from "./utils/updateUtils" +import dayjs from 'dayjs' + + +let mainWindow = null +let difyfullScreenWindow = null +let drageWindow = null +let apiConfigWindow = null +let configWindow = null + +// 权限授权 +async function checkMediaAccess(mediaType){ + const result = systemPreferences.getMediaAccessStatus(mediaType) + logger.info(`=====================systemPreferences.getMediaAccessStatus:${mediaType} result:${result}`) + if(result !== "granted"){ + await systemPreferences.askForMediaAccess(mediaType) + + } +} + + +async function checkAndApplyDeviceAccessPrivilege() { + if (process.platform === "darwin" || process.platform === 'win32') { + // 检查并申请摄像头权限 + const cameraPrivilege = systemPreferences.getMediaAccessStatus("camera"); + if (cameraPrivilege !== "granted") { + await systemPreferences.askForMediaAccess("camera"); + } + + // 检查并申请麦克风权限 + const micPrivilege = systemPreferences.getMediaAccessStatus("microphone"); + if (micPrivilege !== "granted") { + await systemPreferences.askForMediaAccess("microphone"); + } + + // 检查访问屏幕权限 + const screenPrivilege = systemPreferences.getMediaAccessStatus("screen"); + if (screenPrivilege !== 'granted') { + // 没有屏幕访问权限,做后续处理... + } + } +} + + +export async function createWindow() { + // 如果窗口已经存在,直接显示并返回 + if (mainWindow) { + mainWindow.show() + return + } + + logger.info(`启动主窗口`) + + Menu.setApplicationMenu(null) + + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 420, + height: 900, + show: false, + media: { + audio: true, + video: true + }, + icon: icon, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + nodeIntegrationInWorker: true, + preload: join(__dirname, '../preload/index.js'), + // sandbox: false + } + }) + + // 创建菜单 + createMenu(mainWindow, difyfullScreenWindow) + + let code = `localStorage.setItem("IsHsAiApp","IsHsAiApp");localStorage.setItem("HsAppCode",${import.meta.env.VITE_HsAppCode});` + + + const isAdmin = getStoreValue("isAdmin") + let display=''; + + + + await checkAndApplyDeviceAccessPrivilege() + + mainWindow.webContents.on('did-finish-load', () => { + mainWindow.webContents + .executeJavaScript(code) + .then((result) => { + }) + .catch((error) => { + console.error('Error:', error) + }) + }) + + // 监听快捷键 F12 来打开/关闭 DevTools + mainWindow.webContents.on('before-input-event', (event, input) => { + if (input.key === 'F12') { + mainWindow.webContents.toggleDevTools() + } + }) + + + mainWindow.on('resize',async () => { + // logger.info('窗口大小发生变化...'); + // 触发窗口内容重新布局,而不是重新加载 + + mainWindow.webContents.executeJavaScript(` + if (document.body) { + // 触发窗口重排 + window.dispatchEvent(new Event('resize')); + } + `).catch(err => { + console.error('执行布局调整时出错:', err); + }); + }); + + + + mainWindow.on('ready-to-show', () => { + mainWindow.show() + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + mainWindow.on('close', (event) => { + if (!app.isQuiting) { + event.preventDefault() + mainWindow.hide() + return false + } + }) + + + // setStoreValue("h5_client_url", "") + // 检查本地存储中是否已有 h5_client_url + const existingH5ClientUrl = getStoreValue("h5_client_url") + if (!existingH5ClientUrl || existingH5ClientUrl.trim() === '') { + // 只有当值不存在或为空时,才设置默认值 + // setStoreValue("h5_client_url", "http://work.ii999.live:20040") + // setStoreValue("h5_client_url", "http://10.102.8.56:18900") + // setStoreValue("h5_client_url", "http://68.66.24.160:18900") + setStoreValue("h5_client_url", import.meta.env.VITE_h5_client_url) + } + + const h5_client_url=getStoreValue("h5_client_url")+"/h5_client/" + + logger.info("==================================== mainWindow.loadURL:"+h5_client_url) + // 加载存储的 URL + mainWindow.loadURL(h5_client_url) + + // 超过30分钟不活动,则退出登录 + await tokenExpireTimer() + + setTimeout(()=>{ + try { + // 注册全局快捷键 + registerShortcuts(mainWindow) + checkForUpdates({},false) + }catch (e) { + logger.info(e) + } + },1500) + + + session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { + if (permission === 'microphone') { + // 用户是否允许使用麦克风 + callback(true); // 或者根据实际情况返回false + } else { + // 对于其他权限,拒绝或根据实际情况处理 + callback(false); + } + }); + + +} + +// 添加一个专门的快捷键注册函数 +function registerShortcuts(window) { + // 注册 F5 刷新快捷键 + globalShortcut.register('F5', () => { + logger.info('F5 快捷键触发') + if (window && !window.isDestroyed()) { + window.reload() + } + }) + + + const isRegistered_F12 = globalShortcut.isRegistered('F12'); + logger.info(`Is CommandOrControl+X registered: ${isRegistered_F12}`); + + // 桌面端快要退出的时候,注销快捷键 + app.on('will-quit', () => { + globalShortcut.unregisterAll() // 注销所有快捷键 + }) +} + +export async function createNewWindow(url, access_token, refresh_token,sandbox=false) { + const origin_url = url + logger.info('==========createNewWindow') + logger.info('==========createNewWindow sandbox',sandbox) + + await checkAndApplyDeviceAccessPrivilege() + + difyfullScreenWindow = new BrowserWindow({ + width: 1920, + height: 1200, + show: false, + icon: icon, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + nodeIntegrationInWorker: true, + preload: join(__dirname, '../preload/index.js'), + sandbox: sandbox, + webSecurity: false, // 注意:在生产环境中应谨慎使用 + enableRemoteModule: true, // Electron 10+ 默认禁用,需要显式启用 + } + }) + + difyfullScreenWindow.on('ready-to-show', () => { + difyfullScreenWindow.show() + }) + + let code = `localStorage.setItem("IsHsAiApp","IsHsAiApp");localStorage.setItem("console_token","${access_token}");localStorage.setItem("refresh_token","12");` + const isAdmin = getStoreValue("isAdmin") + const hs_version = getStoreValue("hs_version") + let display=''; + + if (isAdmin==true){ + display = ` + (function() { + const stickyDivs = document.querySelectorAll('.sticky.top-0.left-0.right-0.basis-auto.shrink-0'); + stickyDivs.forEach(div => { + // div.style.display = 'none'; + }); + })(); + ` + }else { + display = ` + (function() { + const stickyDivs = document.querySelectorAll('.sticky.top-0.left-0.right-0.basis-auto.shrink-0'); + stickyDivs.forEach(div => { + div.style.display = 'none'; + }); + })(); + ` + } + + code=code+`document.cookie="hs_version=${hs_version}";` + + difyfullScreenWindow.webContents.on('did-finish-load', () => { + difyfullScreenWindow.webContents + .executeJavaScript(code + display) + .then((result) => { + }) + .catch((error) => { + console.error('Error:', error) + }) + let times = 0 + let timer = setInterval(() => { + if (times > 3) { + clearInterval(timer) + timer = null + times = 0 + return + } + if (difyfullScreenWindow != null) { + difyfullScreenWindow.webContents + .executeJavaScript(code + display) + .then((result) => { + times = times + 1 + }) + .catch((error) => { + console.error('Error:', error) + }) + } + }, 2000) + }) + + // 监听快捷键 F12 来打开/关闭 DevTools + difyfullScreenWindow.webContents.on('before-input-event', (event, input) => { + if (input.key === 'F12') { + difyfullScreenWindow.webContents.toggleDevTools() + } + }) + + difyfullScreenWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + difyfullScreenWindow.on('closed', () => { + logger.info('主窗口已关闭') + difyfullScreenWindow = null + }) + + difyfullScreenWindow.webContents.on('did-navigate', (event, url) => { + logger.info('navigate to new URL:', url) + }) + + session.defaultSession.webRequest.onHeadersReceived((details, callback) => { + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Access-Control-Allow-Origin': ['*'], + 'Access-Control-Allow-Methods': ['GET, POST, PUT, DELETE, OPTIONS'], + 'Access-Control-Allow-Headers': ['Content-Type, Authorization'] + } + }) + }) + let times = 0 + difyfullScreenWindow.webContents.on('did-navigate-in-page', (event, url) => { + logger.info('navigate URL change:', url) + logger.info(`===============current:${access_token}`) + if (url.includes('/signin')) { + + logger.info("token 注入失效,重新注入并且加载页面") + + if (times>3){ + difyfullScreenWindow.close() + difyfullScreenWindow=null + times=0; + return + } + + let _display = ` + (function() { + const stickyDivs = document.querySelectorAll('body'); + stickyDivs.forEach(div => { + div.style.display = 'none'; + }); + })(); + ` + difyfullScreenWindow.webContents + .executeJavaScript(code + _display) + .then((result) => { + logger.info('load url:', origin_url) + setTimeout(()=>{ + times=times+1; + difyfullScreenWindow.loadURL(origin_url) + },1000) + }) + .catch((error) => { + console.error('Error:', error) + }) + } + }) + + let dify_site = 'http://df.1024web.cn' + const filter = { urls: [`${dify_site}/*`] } + + session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { + if (isApiDatasetsDocumentsPath(details.url)) { + logger.info('========== onBeforeRequest url:', details.url) + logger.info('============datasetId :', extractIdFromPath(details.url)) + logger.info(details) + + const url = new URL(details.url) + const pathAndQuery = url.pathname + url.search + const redirectURL = `http://192.168.50.22:8001/items/` + + logger.info(`[basehttp:setProxy] redirectURL = ${redirectURL}`) + callback({ redirectURL }) + } else { + callback({ cancel: false }) + } + }) + + difyfullScreenWindow.on('resize', () => { + logger.info('窗口大小发生变化,重新加载页面...'); + if (difyfullScreenWindow!=null){ + difyfullScreenWindow.webContents.executeJavaScript(` + if (document.body) { + // 触发窗口重排 + window.dispatchEvent(new Event('resize')); + } + `).catch(err => { + console.error('执行布局调整时出错:', err); + }); + + } + // 触发窗口内容重新布局,而不是重新加载 + + }); + + difyfullScreenWindow.loadURL(url) + + + + +} + +export function createDrageWindow() { + logger.info('开始创建悬浮窗口') + + if (drageWindow) { + drageWindow.focus() + return + } + + + drageWindow = new BrowserWindow({ + width: 120, + height: 120, + frame: false, + show: true, + skipTaskbar: true, + transparent: true, + resizable: false, + alwaysOnTop: true, + autoHideMenuBar: true, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + nodeIntegrationInWorker: true, + preload: join(__dirname, '../preload/index.js') + } + }) + + + drageWindow.setAlwaysOnTop(true, 'screen-saver') + + drageWindow.on('ready-to-show', () => { + drageWindow.show() + }) + + drageWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + drageWindow.on('close', (event) => { + if (!app.isQuiting) { + event.preventDefault() + drageWindow.hide() + return false + } + }) + + + const h5_client_url=getStoreValue("h5_client_url")+"/electron_h5/" + + + logger.log("======================== drageWindow :") + logger.log(h5_client_url) + drageWindow.loadURL(h5_client_url) + + + const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize + drageWindow.setPosition(screenWidth - drageWindow.getSize()[0] -100, screenHeight - drageWindow.getSize()[1] - 100) +} + +export async function tokenExpireTimer(){ + const tokenExpireTimer = setInterval(async () => { + logger.info("tokenExpireTimer 触发") + const lastActiveTime = getStoreValue("lastActiveTime")||null + if (lastActiveTime!=null){ + logger.info("tokenExpireTimer 触发 对比时间戳") + try { + const nowTime=dayjs(); + const lastTime=dayjs(lastActiveTime); + const diff=nowTime.diff(lastTime, 'minute') + logger.info("tokenExpireTimer 触发 对比时间戳差距为:"+diff) + if ( diff> 30) { + deleteStore("lastActiveTime") + try { + // 清除所有窗口的浏览器缓存 + const windows = BrowserWindow.getAllWindows() + for (const window of windows) { + const session = window.webContents.session + await session.clearStorageData({ + storages: [ + 'appcache', + 'cookies', + 'filesystem', + 'indexdb', + 'localstorage', + 'shadercache', + 'websql', + 'serviceworkers', + 'cachestorage' + ] + }) + } + + logger.info('浏览器缓存清除成功') + } catch (error) { + logger.error('清除浏览器缓存失败:', error) + } + + if (mainWindow) { + mainWindow.reload() + } + logger.info('用户已退出登录') + + } + } catch (e) { + + } + } + }, 1000 * 10) +} + +export function getMainWindow() { + if (!mainWindow || mainWindow.isDestroyed()) { + return null + } + return mainWindow +} + +export function getDragWindow() { + if (!drageWindow || drageWindow.isDestroyed()) { + return null + } + return drageWindow +} + +export function getDifyFullScreenWindow() { + return difyfullScreenWindow; +} + +export function getDrageWindow() { + return drageWindow; +} + +// 在应用退出时注销所有快捷键 +export function unregisterAllShortcuts() { + globalShortcut.unregisterAll() +} + + +export function closeApiConfigWindow() { + if (apiConfigWindow) { + apiConfigWindow.close() + apiConfigWindow = null + } +} + +export function createConfigWindow() { + if (configWindow) { + configWindow.focus() + return + } + + configWindow = new BrowserWindow({ + width: 600, + height: 400, + resizable: false, + minimizable: false, + maximizable: false, + parent: getMainWindow(), + modal: true, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + nodeIntegrationInWorker: true, + preload: join(__dirname, '../preload/index.js') + }, + permissions: { + microphone: 'allow' + } + }) + + + const h5_client_url=getStoreValue("h5_client_url")+"/electron_h5/#/config" + + + logger.info("======================== configWindow 11111111111:") + logger.info(h5_client_url) + // configWindow.loadURL(h5_client_url) + + + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + configWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/#/config') + } else { + configWindow.loadFile(join(__dirname, '../renderer/index.html'), { hash: '/config' }) + } +// + + + configWindow.on('closed', () => { + configWindow = null + }) + + + +} + +// 添加关闭配置窗口的方法 +export function closeConfigWindow() { + if (configWindow) { + configWindow.close() + configWindow = null + } +} diff --git a/src/preload/index.js b/src/preload/index.js new file mode 100644 index 0000000..2b2448a --- /dev/null +++ b/src/preload/index.js @@ -0,0 +1,27 @@ +import { contextBridge , ipcRenderer} from 'electron' +import { electronAPI } from '@electron-toolkit/preload' + +// Custom APIs for renderer +const api = {} + +// Use `contextBridge` APIs to expose Electron APIs to +// renderer only if context isolation is enabled, otherwise +// just add to the DOM global. +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('electron', electronAPI) + contextBridge.exposeInMainWorld('api', api) + + ipcRenderer.on('data-from-main', (event, key,value) => { + localStorage.setItem(key, value); + }); + + } catch (error) { + console.error(error) + } +} else { + window.electron = electronAPI + window.api = api + + +} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..0898743 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,24 @@ + + + + + Dify Market Manager + + + + + + +
+ + + diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue new file mode 100644 index 0000000..cb98d72 --- /dev/null +++ b/src/renderer/src/App.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/renderer/src/MainApp.vue b/src/renderer/src/MainApp.vue new file mode 100644 index 0000000..018e70d --- /dev/null +++ b/src/renderer/src/MainApp.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/src/renderer/src/assets/base.css b/src/renderer/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/src/renderer/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/renderer/src/assets/electron.svg b/src/renderer/src/assets/electron.svg new file mode 100644 index 0000000..45ef09c --- /dev/null +++ b/src/renderer/src/assets/electron.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/renderer/src/assets/logo.png b/src/renderer/src/assets/logo.png new file mode 100644 index 0000000..ef40391 Binary files /dev/null and b/src/renderer/src/assets/logo.png differ diff --git a/src/renderer/src/assets/logo.svg b/src/renderer/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/src/renderer/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/src/renderer/src/assets/longxiaoi_I.png b/src/renderer/src/assets/longxiaoi_I.png new file mode 100644 index 0000000..5e39869 Binary files /dev/null and b/src/renderer/src/assets/longxiaoi_I.png differ diff --git a/src/renderer/src/assets/lxI_120.png b/src/renderer/src/assets/lxI_120.png new file mode 100644 index 0000000..050cbcf Binary files /dev/null and b/src/renderer/src/assets/lxI_120.png differ diff --git a/src/renderer/src/assets/lxi_200.png b/src/renderer/src/assets/lxi_200.png new file mode 100644 index 0000000..0df6fe7 Binary files /dev/null and b/src/renderer/src/assets/lxi_200.png differ diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/src/renderer/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/src/renderer/src/assets/wavy-lines.svg b/src/renderer/src/assets/wavy-lines.svg new file mode 100644 index 0000000..d08c611 --- /dev/null +++ b/src/renderer/src/assets/wavy-lines.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/components/ApiConfig.vue b/src/renderer/src/components/ApiConfig.vue new file mode 100644 index 0000000..a26128b --- /dev/null +++ b/src/renderer/src/components/ApiConfig.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/renderer/src/components/HelloWorld.vue b/src/renderer/src/components/HelloWorld.vue new file mode 100644 index 0000000..eff59f1 --- /dev/null +++ b/src/renderer/src/components/HelloWorld.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/renderer/src/components/TheWelcome.vue b/src/renderer/src/components/TheWelcome.vue new file mode 100644 index 0000000..fe48afc --- /dev/null +++ b/src/renderer/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/renderer/src/components/Versions.vue b/src/renderer/src/components/Versions.vue new file mode 100644 index 0000000..35136c0 --- /dev/null +++ b/src/renderer/src/components/Versions.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/renderer/src/components/WelcomeItem.vue b/src/renderer/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/src/renderer/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/renderer/src/components/icons/IconCommunity.vue b/src/renderer/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/src/renderer/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/src/renderer/src/components/icons/IconDocumentation.vue b/src/renderer/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/src/renderer/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/src/renderer/src/components/icons/IconEcosystem.vue b/src/renderer/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/src/renderer/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/src/renderer/src/components/icons/IconSupport.vue b/src/renderer/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/src/renderer/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/src/renderer/src/components/icons/IconTooling.vue b/src/renderer/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/src/renderer/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/src/renderer/src/main.js b/src/renderer/src/main.js new file mode 100644 index 0000000..8a2e5d2 --- /dev/null +++ b/src/renderer/src/main.js @@ -0,0 +1,7 @@ +import { createApp } from 'vue' +import router from './router' +import MainApp from './MainApp.vue' + +const app = createApp(MainApp) +app.use(router) +app.mount('#app') diff --git a/src/renderer/src/router/index.js b/src/renderer/src/router/index.js new file mode 100644 index 0000000..fd16382 --- /dev/null +++ b/src/renderer/src/router/index.js @@ -0,0 +1,23 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import App from '../App.vue' +import ApiConfig from '../components/ApiConfig.vue' + +const routes = [ + { + path: '/', + name: 'home', + component: App + }, + { + path: '/config', + name: 'config', + component: ApiConfig + } +] + +const router = createRouter({ + history: createWebHashHistory(), + routes +}) + +export default router \ No newline at end of file diff --git a/test.html b/test.html new file mode 100644 index 0000000..20706bc --- /dev/null +++ b/test.html @@ -0,0 +1,202 @@ + + + + + Title + + +
+
+ + + +
+

目录

+

一、前言背景

+

二、工具介绍以及配置方法

+

1. 工具介绍 

+

2. 配置方法

+

2.1 安装CherryStudio 

+

2.2 获取API密钥

+

2.2.1 获取硅基流动API_KEY(个人强推!白嫖DeepSeek-R1&V3等)

+

2.2.2 获取Github models模型服务API_KEY(白嫖GPT-4o、DeepSeek-R1&V3等,但GitHub访问慢)

+

2.2.3 获取深度求索API_KEY(DeepSeek官方,2月13日起注册赠送10元已取消,白嫖党可跳过)

+

2.2.4 获取OpenRouter模型服务API_KEY(白嫖DeepSeek、Gemma等免费服务,但有次数限制)

+

2.2.5 获取各种云提供的API_KEY(笔者暂未试用,请自取)

+

2.2.6 获取Ollama本地模型(笔者未本地部署,若对数据安全有要求,可配置一台不错的电脑后再进行本地搭建)

+

2.3 配置CherryStudio

+

2.3.1 配置模型服务  

+

2.3.2 设置默认模型

+

2.4 使用CherryStudio

+

2.4.1 智能体&助手

+

2.4.2 绘画

+

2.4.3 知识库 

+

2.4.4 翻译

+

2.4.5 小程序 

+

三、总结

+
+

+

+
+

一、前言背景

+

当人工智能技术逐步渗透各个领域,每个人都面临新的机遇与挑战:如何在保持创造力的同时提升生产效率?CherryStudio、DeepSeek与GPT三大技术体系的深度协同,正在构建智能工作的新范式。本文将解析这一技术组合如何重塑现代开发者的工作流。

+

二、工具介绍以及配置方法

+

1. 工具介绍 

+

CherryStudio 是一款集多模型对话、知识库管理、AI 绘画、翻译等功能于一体的全能 AI 助手平台。 CherryStudio的高度自定义的设计、强大的扩展能力和友好的用户体验,使其成为专业用户和 AI 爱好者的理想选择。无论是零基础用户还是开发者,都能在 CherryStudio 中找到适合自己的AI功能,提升工作效率和创造力。CherryStudio 能通过可视化界面和远程 API 接口调用各类模型,大幅降低对本地硬件的依赖,为个人和企业提供了一个高效的解决方案

+

DeepSeek是一家国内创新型科技公司,长久以来专注于开发先进的大语言模型(LLM)和相关技术。本文特指其提供的用于语义理解、问答系统及其他相关任务的大模型。

+

GPT是一种先进的人工智能语言模型,它通过深度学习技术,特别是Transformer架构理解和生成自然语言文本。GPT通过在大量文本数据上的预训练,学习语言的模式和结构,使其能够预测和生成连贯、有意义的文本内容。GPT模型可以广泛应用于文本生成、对话系统、自动摘要等多种自然语言处理任务。

+

2. 配置方法

+

2.1 安装CherryStudio 

+

点击进入官网,根据自己的电脑系统下载安装包,例如Cherry-Studio-1.1.16-setup.exe

+

+

双击下载的exe文件,按照指引进行安装,过程很简单,这里就不再赘述。安装完成后启动 Cherry Studio,开始配置。

+

2.2 获取API密钥

+
2.2.1 获取硅基流动API_KEY(个人强推!白嫖DeepSeek-R1&V3等)
+
+

截至2025.03.28,硅基赠送的金额能抵扣R1&V3模型使用费用,且无请求次数限制(2025.02.22增加的30次/小时、100次/天已于2025.03.07去除)!

+
+

点击注册硅基流动账号,注册成功后会获得14元平台额度(2000万Tokens)。平台提供多个模型使用。 

+

+

点击【API密钥】-【新建API密钥】,输入任意描述信息,点击【新建密钥】即可完成硅基流动API_KEY新增。点击即复制列表中的密钥。

+

+

粘贴到CherryStudio对应的API密钥,选择任意模型,以DeepSeek-R1为例点击【检查】,提示“连接成功”即表示可以使用。

+

+

+

+
2.2.2 获取Github models模型服务API_KEY(白嫖GPT-4o、DeepSeek-R1&V3等,但GitHub访问慢
+
+

Github 提供了一些免费的大语言模型,如 DeepSeek R1、GPT-4o 等,对于国内无法使用 OpenAI 服务的人来说,Github model 简直是意外惊喜。

+
+

点击打开Github models,选择任一模型点击进入,如OpenAI GPT-4o

+

 跳到该模型界面后,点击【Use this model】去创建 Key。

+

 在弹出的对话框中点击 “Get developer key”。

+

 点击 “Generate new token”,选择第一个 “Generate new token”。 

+

+

填写 Token name 和有效期,然后点击 “Generate token”。 点击复制按钮,将其粘贴到 CherryStuido的 “API 密钥” 处。

+

+

点击【添加】,模型 ID 输出 “DeepSeek-R1”,点击【添加模型】

+

+

 点击【检查】,根据自己的需求选择模型,然后点击右上角的【开启】开启 Github models即可。

+

+
2.2.3 获取深度求索API_KEY(DeepSeek官方,2月13日起注册赠送10元已取消,白嫖党可跳过)
+

点击DeepSeek开放平台注册,注册成功后点击【API Keys】-【创建 API key】,创建成功后复制到CherryStudio的 “API 密钥” 处,再点击【检查】

+

+

在弹出的对话框中选择 “DeepSeek Reasoner”,点击【确定】。 配置完成后,点击右上角的【开启】即可。

+

+
+

截至2025.03.28,注册DeepSeek开放平台账户赠送10元额度还未恢复,所以连接提示“402 余额不足”。若需使用此方式,可使用钞能力充值体验(个人使用的话,充10元能玩很久啦~)

+

+

+
2.2.4 获取OpenRouter模型服务API_KEY(白嫖DeepSeek、Gemma等免费服务,但有次数限制)
+

点击打开OpenRouter,注册/登录(可使用github授权)后,点击右上角菜单中的“Keys”,点击【API Keys】-【Create Key】

+

+

在弹窗中输入任意名称后,点击【Create】 

+

+

 点击【复制】按钮,将其粘贴到 CherryStuido的 “API 密钥” 处。

+

+

连接成功后,点击CherryStudio的【设置】-【模型服务】-【OpenRouter】-【管理】,把自己想用的服务添加到配置中,最后点击右上角【开启】即可。

+

+

+
2.2.5 获取各种云提供的API_KEY(笔者暂未试用,请自取)
+
+

华为云、阿里云、京东云、腾讯云、火山引擎、华为昇腾社区、联通云、百度智能云等云厂商都已接入DeepSeek,且提供一定的免费体验额度(有些是天数,有些是tokens数)。

+
+

百度智能云:

+

https://cloud.baidu.com/product-s/qianfan_home

+

华为云:

+

https://cloud.siliconflow.cn/i/adxzw8w3af

+

阿里云:

+

https://pai.console.aliyun.com/#/quick-start/models

+

华为昇腾社区:

+

https://www.huaweicloud.com/

+

腾讯云:

+

https://cloud.tencent.com/

+

火山引擎:

+

https://www.volcengine.com/docs/6459/1449739

+

联通云:

+

https://www.cucloud.cn/

+

京东云: 

+

https://www.jdcloud.com/

+
2.2.6 获取Ollama本地模型(笔者未本地部署,若对数据安全有要求,可配置一台不错的电脑后再进行本地搭建)
+
+

本地部署对硬件有严格要求。如果你的设备性能较低,可能会导致运行速度较慢。建议使用上述其他方式来使用大模型。

+
+

点击官网下载Ollama并安装运行,安装完成,运行Ollama,电脑出现羊驼图标即表示成功(一路点击就行,不再赘述)

+

+

官网继续搜索并选择相应模型,笔者以deepseek-r1为例,根据电脑配置选择不同模型后,点击【复制】对应下载命令到电脑命令行中运行。

+

+

+

下载进度会实时显示,完成后提示“Model loaded successfully”‌

+

+

回到CherryStudio,进入【设置】-【模型设置】-【Ollama】确认本地模型正确加载出来就行,其他选项维持默认就好

+

+

2.3 配置CherryStudio

+
2.3.1 配置模型服务  
+
+

CherryStudio支持主流大模型服务,配置过程也基本相似,可以根据自己的需求配置。

+
+

 点击【设置】-【模型服务】,选择任意模型平台,将从对应模型平台获取的API_KEY复制到【API密钥】后,点击【检查】,连接成功后,开启即可。后续笔者将重点介绍几种免费获取API密钥配置DeepSeek、GPT模型的方法以供使用。

+

+

模型平台连接成功后,点击底部【管理】,按需添加各类模型。

+
+

CherryStudio知识库功能必须配置嵌入式模型。不同模型平台提供的模型以及收费标准可能存在差异,请按需选择添加。以“硅基流动”模型平台为例:BAAI/bge-m3模型是免费的,Pro/BAAI/bge-m3模型是收费的。

+

M Tokens 表示每处理100万个Token的价格是XX元。在这里,Token是一个计算机科学术语,通常用于表示一个单词、一个标点符号或者一个数字。在自然语言处理中,我们通常会将文本分割成Token,然后对每个Token进行处理。在自然语言处理中,中文字符和英文字符通常被视为一个Token。因此,无论是一个中文字还是一个英文字母,它们都会消耗一个Token。 

+
+

+

+
2.3.2 设置默认模型
+

CherryStudio支持不同的对话使用不同的模型,我们可以设置默认的模型。

+
+

当助手未设置默认助手模型时,其新对话当中默认选择的模型为此处设置的模型。

+

另外新建助手时,优化提示词所使用的模型也是此处设置的模型。

+
+

点击【设置】 -【默认模型】,定义默认助手/话题命名/翻译模型,笔者配置的是 DeepSeek-R1

+

+

2.4 使用CherryStudio

+
2.4.1 智能体&助手
+

智能体即是一个助手广场,你可以想象你是一个招工的老板,可以在这里选择/搜索你想要的马仔(模型预设),点击卡片后即可将“马仔候选人”添加在对话页面的助手列表当中。

+

我们可以根据不同的需求创建不同的智能体,比如翻译官、图表专家、写作达人、开发工程师等,等于同时拥有多个专业马仔帮你打工。

+

例如,想独自开发运营一个网站,那么我们可以挑选出产品、开发、运营、测试、法务等一系列智能体到助手列表中

+

+

只要提示词和饲料喂的足,就能拥有一个自己的24小时专属精英马仔!

+

+

+

点击【聊天】-【添加助手】,输入助手名,点击即助手正式上岗。

+

+

助手上岗后,可以点击最上面的模型来切换大语言模型来完成你安排的工作。比如让 GPT-4o 帮我写个2048小游戏。

+

+

等待生成完后,点击【预览】就能玩游戏了。

+

+

游戏效果如下,这写代码的速度也太快了!玩起来也很流程~

+

+
2.4.2 绘画
+
+

截至2025.03.29,绘画功能仅支持硅基流动的绘画模型。Gemini图片生成需要在对话界面使用,因为Gemini是多模态交互式的图片生成,也不支持参数调节。

+
+

输入提示词即可生成图片。Tips:可以结合智能体助手生成更精准的图片描述

+
2.4.3 知识库 
+

 点击【知识库】-【添加】,输入任意名称后,选择一个模型,笔者设置的Pro/BAAI/bge-m3

+

+

新增完成后,给知识库上传文件和设置网站

+
+
  • CherryStudio支持多种方式添加数据:

  • 添加文件:点击添加文件的按钮,打开文件选择;

  • 选择文件:选择支持的文件格式,如 pdf,docx,pptx,xlsx,txt,md,mdx 等,并打开;

  • 向量化:系统会自动进行向量化处理,当显示完成时(绿色 ✓),代表向量化已完成。

+
  • 文件夹目录:可以添加整个文件夹目录,该目录下支持格式的文件会被自动向量化;

  • 网址链接:支持网址 url,如 产品简介 - SiliconFlow

  • 站点地图:支持 xml 格式的站点地图,如 https://docs.siliconflow.cn/sitemap.xml

  • 纯文本笔记:支持输入纯文本的自定义内容。

  • 当显示绿色 "√" 表示向量化完成,点击【探索知识库】即可开始查询

+
+

 返回助手界面,新建/编辑一个助手的知识库配置后,我们就可以来提问了,比如“总结一下”

+

+

简直是文献/阅读福音!麻麻再也不担心我费时又找不到地方了!

+
2.4.4 翻译
+

CherryStudio会自动识别源语言并进行翻译为设置的目标语言

+

+
2.4.5 小程序 
+

CherryStudio内可以使用各大服务商的网页版AI相关程序,目前暂不支持自定义添加和删除。

+

+

三、总结

+

通过本文的教程,相信你已经掌握了如何使用 CherryStudio+DeepSeek+GPT搭建构建智能工作流的技巧。无论你是个人用户还是企业团队,都可以利用这一工具大幅提升效率,让工作学习变得更加轻松高效。

+

如果你觉得这篇文章对你有帮助,不妨点个赞或转发给更多需要的人吧!也欢迎在评论区留言,分享你的使用心得~

+

 

+
+
+
+ +