diff --git a/IFLOW.md b/IFLOW.md
index 02d4580..45236bf 100644
--- a/IFLOW.md
+++ b/IFLOW.md
@@ -12,8 +12,9 @@
- **AI 集成**: Google Generative AI SDK (Gemini)
- **数据存储**: IndexedDB (通过 idb-keyval)
- **构建工具**: Vite
+- **桌面应用**: Electron
-项目结构遵循标准的 React 应用组织方式,主要源代码位于 `src/` 目录下。
+项目结构遵循标准的 React 应用组织方式,主要源代码位于 `src/` 目录下。该项目同时支持Web和桌面应用(Electron)。
## 构建和运行
@@ -31,15 +32,23 @@
3. **启动开发服务器**:
```bash
+ # 启动Web开发服务器
npm run dev
+
+ # 启动Electron开发环境
+ npm run electron:dev
```
- 访问 `http://localhost:5173` 查看应用。
+ 访问 `http://localhost:5173` 查看Web应用。
### 构建和部署
- **构建生产版本**:
```bash
+ # 构建Web版本
npm run build
+
+ # 构建Electron桌面应用
+ npm run electron:build
```
- **预览生产构建**:
@@ -82,26 +91,39 @@
src/
├── components/ # React 组件
│ ├── ui/ # 可重用的 UI 组件
+│ │ ├── Button.tsx
+│ │ ├── Input.tsx
+│ │ └── Textarea.tsx
+│ ├── CustomTitleBar.tsx # 自定义标题栏(用于Electron应用)
+│ ├── Header.tsx # 应用头部和导航
│ ├── PromptComposer.tsx # 提示输入和工具选择
│ ├── ImageCanvas.tsx # 使用 Konva 的交互式画布
│ ├── HistoryPanel.tsx # 生成历史和变体
-│ ├── Header.tsx # 应用头部和导航
-│ └── InfoModal.tsx # 关于模态框和链接
+│ ├── InfoModal.tsx # 关于模态框和链接
+│ ├── Toast.tsx # 消息提示组件
+│ └── ToastContext.tsx # 消息提示上下文
├── services/ # 外部服务集成
│ ├── geminiService.ts # Gemini API 客户端
-│ ├── uploadService.ts # 图像上传服务
-│ ├── cacheService.ts # IndexedDB 缓存层
-│ └── referenceImageService.ts # 参考图像处理
+│ ├── indexedDBService.ts # IndexedDB 数据库服务
+│ ├── cacheService.ts # IndexedDB 缓存层(未使用)
+│ ├── referenceImageService.ts # 参考图像处理(未使用)
+│ └── uploadService.ts # 图像上传服务(未使用)
├── store/ # Zustand 状态管理
│ └── useAppStore.ts # 全局应用状态
├── hooks/ # 自定义 React 钩子
│ ├── useImageGeneration.ts # 生成和编辑逻辑
-│ └── useKeyboardShortcuts.ts # 键盘导航
+│ ├── useKeyboardShortcuts.ts # 键盘导航
+│ └── useIndexedDBListener.ts # IndexedDB监听器(未使用)
├── utils/ # 工具函数
│ ├── cn.ts # 类名工具
│ └── imageUtils.ts # 图像处理助手
-└── types/ # TypeScript 类型定义
- └── index.ts # 核心类型定义
+├── types/ # TypeScript 类型定义
+│ └── index.ts # 核心类型定义
+└── __tests__/ # 测试文件
+ ├── ImageCanvas.test.tsx
+ ├── PromptComposer.test.tsx
+ ├── useAppStore.test.ts
+ └── useImageGeneration.test.ts
```
### 组件开发
@@ -130,4 +152,50 @@ src/
3. 维护类型安全,严格使用 TypeScript 和正确定义
4. 彻底测试,确保键盘导航和可访问性
5. 记录更改,更新 README 并添加内联注释
-6. 遵守 AGPL-3.0 许可证
\ No newline at end of file
+6. 遵守 AGPL-3.0 许可证
+
+## 项目特性
+
+### AI 图像生成与编辑
+- 文本到图像生成:使用 Google Gemini 2.5 Flash Image 模型从描述性提示创建图像
+- 对话式编辑:使用自然语言指令修改现有图像
+- 区域感知选择:通过绘制遮罩来针对特定区域进行编辑
+
+### 用户界面
+- 交互式画布:支持平滑缩放、平移和导航大图像
+- 画笔工具:可变画笔尺寸以实现精确的遮罩绘制
+- 响应式设计:在所有设备上都能完美运行
+- 键盘快捷键:使用热键提高工作效率
+
+### 数据管理
+- 生成历史:跟踪所有创作和编辑记录
+- 变体比较:生成并并排比较多个版本
+- 离线缓存:使用 IndexedDB 存储以实现离线资产访问
+- 项目管理:有序存储所有生成的内容
+
+### 部署选项
+- Web 应用:标准的 Web 应用程序,可在浏览器中运行
+- 桌面应用:使用 Electron 构建的桌面应用程序,支持 Windows、macOS 和 Linux
+
+## 依赖包更新
+
+项目已清理未使用的依赖包,当前依赖包括:
+
+### 运行时依赖
+- `@google/genai`: Google Generative AI SDK
+- `@radix-ui/react-dialog`: React 对话框组件
+- `@tanstack/react-query`: 服务端状态管理
+- `class-variance-authority`, `clsx`, `tailwind-merge`: 类名工具
+- `idb-keyval`: IndexedDB 封装库
+- `konva`, `react-konva`: Canvas 图形库
+- `lucide-react`: 图标库
+- `react`, `react-dom`: React 核心库
+- `react-day-picker`: 日期选择器组件
+- `zustand`: 客户端状态管理
+
+### 开发依赖
+- `@eslint/js`, `eslint`, `typescript-eslint`: 代码质量工具
+- `@testing-library/*`: 测试工具
+- `electron-builder`: Electron 应用构建工具
+- `typescript`: TypeScript 编译器
+- `vite`: 构建工具
\ No newline at end of file
diff --git a/electron/main.js b/electron/main.js
index f552b32..eb7f071 100644
--- a/electron/main.js
+++ b/electron/main.js
@@ -1,11 +1,6 @@
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
-// Handle creating/removing shortcuts on Windows when installing/uninstalling.
-if (require('electron-squirrel-startup')) {
- app.quit();
-}
-
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
diff --git a/electron/main.ts b/electron/main.ts
index fbf3cbf..fed45dc 100644
--- a/electron/main.ts
+++ b/electron/main.ts
@@ -1,18 +1,13 @@
import { app, BrowserWindow, ipcMain } from 'electron';
import * as path from 'path';
-// Handle creating/removing shortcuts on Windows when installing/uninstalling.
-if (require('electron-squirrel-startup')) {
- app.quit();
-}
-
let mainWindow: BrowserWindow | null = null;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
- width: 1200,
- height: 800,
+ width: 1600,
+ height: 900,
frame: false, // 隐藏默认的窗口框架
icon: path.join(__dirname, '../../build/icon.ico'), // 设置应用图标
webPreferences: {
@@ -25,16 +20,15 @@ const createWindow = () => {
// and load the index.html of the app.
if (process.env.VITE_DEV_SERVER_URL) {
mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL);
+ // 仅在开发环境中打开开发者工具
+ mainWindow.webContents.openDevTools();
} else {
// 使用绝对路径从项目根目录的 dist 文件夹加载
const indexPath = path.join(__dirname, '../../dist/index.html');
console.log('Loading index.html from:', indexPath);
mainWindow.loadFile(indexPath);
- }
-
- // Open the DevTools.
- if (process.env.VITE_DEV_SERVER_URL) {
- mainWindow.webContents.openDevTools();
+ // 在生产环境中确保关闭开发者工具
+ mainWindow.webContents.closeDevTools();
}
};
diff --git a/package.json b/package.json
index 43d790d..346f849 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,8 @@
"url": "https://git.pandorastudio.cn/yuantao/Nano-Banana-AI-Image-Editor.git"
},
"author": {
- "name": "Mark Fulton",
- "email": "markfulton@example.com",
- "url": "https://markfulton.com"
+ "name": "潘哆呐科技",
+ "email": "work@pandorastudio.cn"
},
"main": "electron/index.js",
"scripts": {
@@ -30,15 +29,9 @@
"dependencies": {
"@google/genai": "^1.16.0",
"@radix-ui/react-dialog": "^1.1.15",
- "@radix-ui/react-select": "^2.2.6",
- "@radix-ui/react-slider": "^1.3.6",
- "@radix-ui/react-switch": "^1.2.6",
"@tanstack/react-query": "^5.85.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "date-fns": "^4.1.0",
- "electron-squirrel-startup": "^1.0.1",
- "fabric": "^6.7.1",
"idb-keyval": "^6.2.2",
"konva": "^9.3.22",
"lucide-react": "^0.344.0",
@@ -53,25 +46,13 @@
"@eslint/js": "^9.9.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
- "@testing-library/user-event": "^14.6.1",
"@types/jest": "^30.0.0",
- "@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react": "^4.3.1",
- "autoprefixer": "^10.4.18",
- "electron": "^38.2.1",
"electron-builder": "^26.0.12",
"eslint": "^9.9.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
- "identity-obj-proxy": "^3.0.0",
- "jest-environment-jsdom": "^30.1.2",
- "postcss": "^8.4.35",
- "pump": "^3.0.3",
- "tailwindcss": "^3.4.1",
- "tar": "^7.4.3",
- "ts-jest": "^29.4.3",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2"
diff --git a/src/App.tsx b/src/App.tsx
index be2fdf1..5648ae2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -27,6 +27,30 @@ function AppContent() {
const [previewPosition, setPreviewPosition] = useState<{x: number, y: number} | null>(null);
const [isPreviewVisible, setIsPreviewVisible] = useState(false);
+ // 在挂载时检查localStorage中的设置
+ useEffect(() => {
+ // 检查是否有存储在localStorage中的API密钥
+ const savedGeminiApiKey = localStorage.getItem('VITE_GEMINI_API_KEY');
+ if (savedGeminiApiKey) {
+ // 这里可以添加任何需要在应用启动时执行的逻辑
+ console.log('使用localStorage中的Gemini API密钥');
+ }
+
+ // 检查是否有存储在localStorage中的访问令牌
+ const savedAccessToken = localStorage.getItem('VITE_ACCESS_TOKEN');
+ if (savedAccessToken) {
+ // 这里可以添加任何需要在应用启动时执行的逻辑
+ console.log('使用localStorage中的访问令牌');
+ }
+
+ // 检查是否有存储在localStorage中的上传URL
+ const savedUploadAssetUrl = localStorage.getItem('VITE_UPLOAD_ASSET_URL');
+ if (savedUploadAssetUrl) {
+ // 这里可以添加任何需要在应用启动时执行的逻辑
+ console.log('使用localStorage中的上传资源URL');
+ }
+ }, []);
+
// 在挂载时初始化IndexedDB并清理base64数据
useEffect(() => {
const init = async () => {
diff --git a/src/components/CustomTitleBar.tsx b/src/components/CustomTitleBar.tsx
index 412cebd..6d32cb4 100644
--- a/src/components/CustomTitleBar.tsx
+++ b/src/components/CustomTitleBar.tsx
@@ -1,10 +1,12 @@
import React, { useState, useEffect } from 'react';
import { Button } from './ui/Button';
-import { HelpCircle, Minus, Square, X } from 'lucide-react';
+import { HelpCircle, Minus, Square, X, Settings } from 'lucide-react';
import { InfoModal } from './InfoModal';
+import { SettingsModal } from './SettingsModal';
export const CustomTitleBar: React.FC = () => {
const [showInfoModal, setShowInfoModal] = useState(false);
+ const [showSettingsModal, setShowSettingsModal] = useState(false);
const [isMaximized, setIsMaximized] = useState(false);
// 检查窗口是否最大化
@@ -58,6 +60,15 @@ export const CustomTitleBar: React.FC = () => {
+
+
+