Merge pull request #917 from zanllp/feat/smart-organize

feat: add smart organize feature with AI-powered file organization
pull/918/head
zanllp 2026-02-17 17:31:33 +08:00 committed by GitHub
commit f6b185e8a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
123 changed files with 3238 additions and 437 deletions

View File

@ -1,6 +1,6 @@
> 🌐 在线体验: http://39.105.110.128:0721 这是我一个空闲的2c2g3m的云主机没有cdn > 🌐 在线体验: http://39.105.110.128:0721 这是我一个空闲的2c2g3m的云主机没有cdn
> >
# Stable-Diffusion-WebUI无边图像浏览 # 无边图像浏览
[查看近期更新](https://github.com/zanllp/sd-webui-infinite-image-browsing/wiki/Change-log) [查看近期更新](https://github.com/zanllp/sd-webui-infinite-image-browsing/wiki/Change-log)
@ -69,6 +69,27 @@
### 🧠 Topic/Tag 分析 ### 🧠 Topic/Tag 分析
- 标签关系图可视化与主题聚类联动。 - 标签关系图可视化与主题聚类联动。
### 🗂️ 智能整理
AI 驱动的自动文件整理
- **语义聚类**:基于 AI 向量化技术,自动将语义相似的图片分组
- **智能命名**AI 自动生成有意义的文件夹名称,支持多语言
- **预览确认**:执行前可预览整理方案,支持跳过或调整特定分组
- **后台处理**:大文件夹在后台异步处理,不影响继续使用
- **灵活配置**:支持移动/复制、设置最小聚类大小、递归处理子文件夹
**使用方法:**
1. 进入需要整理的文件夹
2. 点击地址栏中的「智能整理」按钮
3. 配置选项(目标文件夹、最小聚类大小等)
4. 等待 AI 分析完成
5. 预览整理方案
6. 确认执行
> **前置条件**:与自然语言搜索相同 - 需要配置 `OPENAI_BASE_URL`、`OPENAI_API_KEY`,以及 Python 依赖 `numpy`、`hnswlib`
>
> 📸 查看下方[智能整理预览](#智能整理-1)获取截图和视频演示
### 🌐 多语言支持 ### 🌐 多语言支持
- 目前支持简体中文/繁体中文/英文/德语。 - 目前支持简体中文/繁体中文/英文/德语。
- 如果您希望添加新的语言,请参考 [i18n.ts](https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/src/i18n/zh-hans.ts) 并提交相关的代码。 - 如果您希望添加新的语言,请参考 [i18n.ts](https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/src/i18n/zh-hans.ts) 并提交相关的代码。
@ -173,6 +194,20 @@ https://user-images.githubusercontent.com/25872019/230768207-daab786b-d4ab-489f-
<img width="768" alt="image" src="https://user-images.githubusercontent.com/25872019/230064879-c95866ac-999d-4d4b-87ea-3e38c8479415.png"> <img width="768" alt="image" src="https://user-images.githubusercontent.com/25872019/230064879-c95866ac-999d-4d4b-87ea-3e38c8479415.png">
## 智能整理
AI 驱动的自动文件整理 - 自动将相似图片分组并创建有意义的文件夹。
<img width="500" alt="智能整理配置弹窗" src="docs/imgs/smart-organize-config-modal.png" />
<img width="500" alt="智能整理生成标题" src="docs/imgs/smart-organize-generate-title.png" />
<img width="800" alt="智能整理预览" src="docs/imgs/smart-organize-preview.png" />
<img width="800" alt="智能整理预览列表" src="docs/imgs/smart-organize-preview-list.png" />
https://github.com/user-attachments/assets/c1279556-d255-4e71-b230-48523a4859bf
## 自然语言分类&搜索(实验性) ## 自然语言分类&搜索(实验性)
这个功能用于把图片按**提示词语义相似度**自动分组(主题),并支持用一句自然语言做**语义检索**(类似 RAG 的召回阶段)。 这个功能用于把图片按**提示词语义相似度**自动分组(主题),并支持用一句自然语言做**语义检索**(类似 RAG 的召回阶段)。

View File

@ -7,7 +7,7 @@
[Installation / Running](#installation--running) [Installation / Running](#installation--running)
# Stable Diffusion webui Infinite Image Browsing # Infinite Image Browsing (IIB)
### Software Support and Development Progress Overview ### Software Support and Development Progress Overview
| Software | Support | Provided by | | Software | Support | Provided by |
@ -84,6 +84,27 @@ If you would like to support more software, please refer to: [parsers](https://g
### 🧠 Topic/Tag Analysis ### 🧠 Topic/Tag Analysis
- Tag relationship graph visualization for topic clusters. - Tag relationship graph visualization for topic clusters.
### 🗂️ Smart Organize
AI-powered automatic file organization
- **Semantic Clustering**: Automatically groups similar images based on prompt semantics using AI embeddings
- **Auto-Generated Folder Names**: AI generates meaningful folder names in your preferred language
- **Preview Before Action**: Review the proposed organization before confirming - skip or adjust as needed
- **Background Processing**: Large folders are processed in the background, you can continue working
- **Flexible Options**: Choose between move or copy, set minimum cluster size, include subfolders recursively
**How to use:**
1. Navigate to the folder you want to organize
2. Click the "Smart Organize" button in the address bar
3. Configure options (target folder, min cluster size, etc.)
4. Wait for AI analysis to complete
5. Preview the proposed organization
6. Confirm to execute
> **Requirements**: Same as Topic Search - requires `OPENAI_BASE_URL`, `OPENAI_API_KEY`, and Python dependencies `numpy`, `hnswlib`
>
> 📸 See [Smart Organize Preview](#smart-organize) below for screenshots and video demo.
### 🌐 Multilingual Support ### 🌐 Multilingual Support
- Currently supports Simplified Chinese/Traditional Chinese/English/German. - Currently supports Simplified Chinese/Traditional Chinese/English/German.
- If you would like to add a new language, please refer to [i18n.ts](https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/src/i18n/zh-hans.ts) and submit the relevant code. - If you would like to add a new language, please refer to [i18n.ts](https://github.com/zanllp/sd-webui-infinite-image-browsing/blob/main/vue/src/i18n/zh-hans.ts) and submit the relevant code.
@ -190,6 +211,20 @@ https://user-images.githubusercontent.com/25872019/230768207-daab786b-d4ab-489f-
<img width="768" alt="image" src="https://user-images.githubusercontent.com/25872019/230064879-c95866ac-999d-4d4b-87ea-3e38c8479415.png"> <img width="768" alt="image" src="https://user-images.githubusercontent.com/25872019/230064879-c95866ac-999d-4d4b-87ea-3e38c8479415.png">
## Smart Organize
AI-powered automatic file organization - groups similar images and creates meaningful folders.
<img width="500" alt="Smart Organize Config Modal" src="docs/imgs/smart-organize-config-modal.png" />
<img width="500" alt="Smart Organize Generate Title" src="docs/imgs/smart-organize-generate-title.png" />
<img width="800" alt="Smart Organize Preview" src="docs/imgs/smart-organize-preview.png" />
<img width="800" alt="Smart Organize Preview List" src="docs/imgs/smart-organize-preview-list.png" />
https://github.com/user-attachments/assets/c1279556-d255-4e71-b230-48523a4859bf
## Natural Language Categorization & Search (Experimental) ## Natural Language Categorization & Search (Experimental)
This feature groups images by **semantic similarity of prompts** and supports **natural-language retrieval** (similar to the retrieval stage in RAG). This feature groups images by **semantic similarity of prompts** and supports **natural-language retrieval** (similar to the retrieval stage in RAG).

View File

@ -1,6 +1,43 @@
[跳到中文](#中文) [跳到中文](#中文)
# English # English
## 2026-02-17
### 🗂️ Smart Organize - AI-Powered File Organization \
Automatically organize scattered images into meaningful folders using AI.
**Features:**
- **Semantic Clustering**: Groups images based on prompt similarity using AI embeddings
- **Auto-Generated Names**: AI creates meaningful folder names in your preferred language
- **Preview & Confirm**: Review the proposed organization before executing - skip or adjust any cluster
- **Background Processing**: Large folders process asynchronously without blocking your workflow
- **Flexible Options**: Move or copy, set minimum cluster size, include subfolders recursively
**How to use:**
1. Navigate to the folder you want to organize
2. Click **"Smart Organize"** button in the address bar
3. Configure options in the modal
4. Wait for AI analysis (embedding → clustering → title generation)
5. Preview the result - hover over filenames to see image thumbnails
6. Confirm to execute
<img width="500" alt="Smart Organize Config Modal" src="docs/imgs/smart-organize-config-modal.png" />
<img width="500" alt="Smart Organize Generate Title" src="docs/imgs/smart-organize-generate-title.png" />
<img width="800" alt="Smart Organize Preview" src="docs/imgs/smart-organize-preview.png" />
<img width="800" alt="Smart Organize Preview List" src="docs/imgs/smart-organize-preview-list.png" />
https://github.com/user-attachments/assets/c1279556-d255-4e71-b230-48523a4859bf
> Requirements: Same as Topic Search - `OPENAI_BASE_URL`, `OPENAI_API_KEY`, Python deps `numpy`, `hnswlib`
### Flatten Folder
New feature to move all files from subfolders to the current folder with conflict detection.
## 2026-02-01 ## 2026-02-01
### Drag-and-drop into folders and safer move/copy ### Drag-and-drop into folders and safer move/copy
- Support drag-and-drop into folders with right-panel open fixes and UI adjustments. - Support drag-and-drop into folders with right-panel open fixes and UI adjustments.
@ -654,6 +691,42 @@ Triggered under the same circumstances as above, there will be a button to updat
# 中文 # 中文
## 2026-02-17
### 🗂️ 智能整理 - AI 驱动的文件整理
使用 AI 自动将零散图片整理到有意义的文件夹中。
**功能特性:**
- **语义聚类**:基于 AI 向量化技术,自动将语义相似的图片分组
- **智能命名**AI 自动生成有意义的文件夹名称,支持多语言
- **预览确认**:执行前可预览整理方案,支持跳过或调整特定分组
- **后台处理**:大文件夹在后台异步处理,不影响继续使用
- **灵活配置**:支持移动/复制、设置最小聚类大小、递归处理子文件夹
**使用方法:**
1. 进入需要整理的文件夹
2. 点击地址栏中的「智能整理」按钮
3. 在弹窗中配置选项
4. 等待 AI 分析(向量化 → 聚类 → 生成标题)
5. 预览整理方案 - 鼠标悬停文件名可查看缩略图
6. 确认执行
<img width="500" alt="智能整理配置弹窗" src="docs/imgs/smart-organize-config-modal.png" />
<img width="500" alt="智能整理生成标题" src="docs/imgs/smart-organize-generate-title.png" />
<img width="800" alt="智能整理预览" src="docs/imgs/smart-organize-preview.png" />
<img width="800" alt="智能整理预览列表" src="docs/imgs/smart-organize-preview-list.png" />
https://github.com/user-attachments/assets/c1279556-d255-4e71-b230-48523a4859bf
> 前置条件:与自然语言搜索相同 - `OPENAI_BASE_URL`、`OPENAI_API_KEY`、Python 依赖 `numpy`、`hnswlib`
### 压平文件夹
新功能:将所有子文件夹中的文件移动到当前文件夹,支持文件名冲突检测。
## 2026-02-01 ## 2026-02-01
### 支持拖拽到文件夹与更安全的移动/复制 ### 支持拖拽到文件夹与更安全的移动/复制
- 支持拖拽文件到文件夹,并修复右侧打开文件夹与界面细节。 - 支持拖拽文件到文件夹,并修复右侧打开文件夹与界面细节。

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -13,8 +13,8 @@ Promise.resolve().then(async () => {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infinite Image Browsing</title> <title>Infinite Image Browsing</title>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-2865012e.js"></script> <script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-c24b5c8e.js"></script>
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-1293e61d.css"> <link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-6d9e7c10.css">
</head> </head>
<body> <body>

View File

@ -63,6 +63,7 @@ from scripts.iib.db.datamodel import (
from scripts.iib.db.update_image_data import update_image_data, rebuild_image_index, add_image_data_single from scripts.iib.db.update_image_data import update_image_data, rebuild_image_index, add_image_data_single
from scripts.iib.topic_cluster import mount_topic_cluster_routes from scripts.iib.topic_cluster import mount_topic_cluster_routes
from scripts.iib.tag_graph import mount_tag_graph_routes from scripts.iib.tag_graph import mount_tag_graph_routes
from scripts.iib.organize_files import mount_organize_routes
from scripts.iib.logger import logger from scripts.iib.logger import logger
from scripts.iib.seq import seq from scripts.iib.seq import seq
import urllib.parse import urllib.parse
@ -942,6 +943,111 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
check_path_trust(req.path) check_path_trust(req.path)
open_file_with_default_app(req.path) open_file_with_default_app(req.path)
# ========== Flatten Folder API ==========
class FlattenFolderReq(BaseModel):
folder_path: str
dry_run: bool = True # If True, only check for conflicts without moving
class FlattenFolderResp(BaseModel):
success: bool
total_files: int
conflicts: List[str] # List of duplicate file names
moved_files: int = 0
errors: List[str] = []
@app.post(
api_base + "/flatten_folder",
dependencies=[Depends(verify_secret), Depends(write_permission_required)],
)
async def flatten_folder(req: FlattenFolderReq):
"""
Flatten a folder by moving all files from subfolders to the root folder.
Two phases:
1. dry_run=True: Scan and check for filename conflicts
2. dry_run=False: Actually move the files
"""
check_path_trust(req.folder_path)
folder_path = os.path.normpath(req.folder_path)
if not os.path.isdir(folder_path):
raise HTTPException(400, detail=f"Not a folder: {folder_path}")
# Collect all files recursively
all_files = [] # List of (full_path, filename)
for root, dirs, files in os.walk(folder_path):
# Skip the root folder itself
if root == folder_path:
continue
for f in files:
if is_media_file(f):
full_path = os.path.join(root, f)
all_files.append((full_path, f))
# Check for filename conflicts
filename_count = {}
for _, filename in all_files:
filename_count[filename] = filename_count.get(filename, 0) + 1
conflicts = [name for name, count in filename_count.items() if count > 1]
if req.dry_run:
return FlattenFolderResp(
success=len(conflicts) == 0,
total_files=len(all_files),
conflicts=conflicts
)
# If not dry_run, check for conflicts first
if conflicts:
raise HTTPException(
400,
detail=f"Cannot flatten: {len(conflicts)} filename conflicts found"
)
# Actually move files
conn = DataBase.get_conn()
moved_count = 0
errors = []
for full_path, filename in all_files:
try:
dest_path = os.path.join(folder_path, filename)
# Move the file
shutil.move(full_path, dest_path)
# Update database
img = DbImg.get(conn, full_path)
if img:
img.update_path(conn, dest_path, force=True)
# Move associated txt file if exists
txt_path = get_img_geninfo_txt_path(full_path)
if txt_path and os.path.exists(txt_path):
txt_dest = os.path.join(folder_path, os.path.basename(txt_path))
shutil.move(txt_path, txt_dest)
moved_count += 1
except Exception as e:
errors.append(f"{full_path}: {str(e)}")
# Clean up empty directories
for root, dirs, files in os.walk(folder_path, topdown=False):
if root != folder_path:
try:
if not os.listdir(root): # Directory is empty
os.rmdir(root)
except Exception:
pass
return FlattenFolderResp(
success=len(errors) == 0,
total_files=len(all_files),
conflicts=[],
moved_files=moved_count,
errors=errors
)
@app.post( @app.post(
api_base + "/batch_top_4_media_info", api_base + "/batch_top_4_media_info",
@ -1281,7 +1387,7 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
# ===== 主题聚类 / Embedding拆分到独立模块减少 api.py 体积)===== # ===== 主题聚类 / Embedding拆分到独立模块减少 api.py 体积)=====
mount_topic_cluster_routes( topic_cluster_funcs = mount_topic_cluster_routes(
app=app, app=app,
db_api_base=db_api_base, db_api_base=db_api_base,
verify_secret=verify_secret, verify_secret=verify_secret,
@ -1303,6 +1409,16 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
openai_api_key=OPENAI_API_KEY, openai_api_key=OPENAI_API_KEY,
) )
# ===== 智能文件整理 =====
mount_organize_routes(
app=app,
db_api_base=db_api_base,
verify_secret=verify_secret,
write_permission_required=write_permission_required,
start_cluster_job_func=topic_cluster_funcs["start_cluster_job"],
get_cluster_job_status_func=topic_cluster_funcs["get_cluster_job_status"],
)
class ExtraPathModel(BaseModel): class ExtraPathModel(BaseModel):
path: str path: str

View File

@ -0,0 +1,682 @@
"""
Smart file organization based on topic clustering.
Organizes scattered image files into folders based on semantic clustering.
"""
import os
import uuid
import asyncio
import threading
import time
import shutil
from typing import Dict, List, Optional, Callable, Any
from collections import defaultdict
from pydantic import BaseModel
from fastapi import FastAPI, Depends, HTTPException
from scripts.iib.logger import logger
from scripts.iib.db.datamodel import DataBase, Image as DbImg
from scripts.iib.tool import get_img_geninfo_txt_path, is_media_file
# ========== Job Storage ==========
_ORGANIZE_JOBS: Dict[str, Dict] = {}
_ORGANIZE_JOBS_LOCK = threading.Lock()
_ORGANIZE_JOBS_MAX = 16
def _job_now() -> float:
return time.time()
def _organize_job_get(job_id: str) -> Optional[Dict]:
with _ORGANIZE_JOBS_LOCK:
j = _ORGANIZE_JOBS.get(job_id)
return dict(j) if isinstance(j, dict) else None
def _organize_job_upsert(job_id: str, patch: Dict) -> None:
with _ORGANIZE_JOBS_LOCK:
cur = _ORGANIZE_JOBS.get(job_id)
if not isinstance(cur, dict):
cur = {"job_id": job_id}
cur.update(patch or {})
cur["updated_at"] = _job_now()
_ORGANIZE_JOBS[job_id] = cur
_organize_job_trim()
def _organize_job_trim() -> None:
with _ORGANIZE_JOBS_LOCK:
if len(_ORGANIZE_JOBS) <= _ORGANIZE_JOBS_MAX:
return
items = sorted(
_ORGANIZE_JOBS.items(),
key=lambda kv: kv[1].get("updated_at", 0),
reverse=True
)
keep = dict(items[:_ORGANIZE_JOBS_MAX])
_ORGANIZE_JOBS.clear()
_ORGANIZE_JOBS.update(keep)
# ========== Request/Response Models ==========
class OrganizeFilesReq(BaseModel):
folder_paths: List[str]
dest_folder: Optional[str] = None
threshold: float = 0.90
min_cluster_size: int = 2
lang: str = "en"
recursive: bool = False # If True, treat all files in subfolders as files to organize
# Folder naming options
folder_naming: str = "title" # "title" | "keywords" | "id"
max_folder_name_length: int = 50
# Operation options
action: str = "move" # "move" | "copy"
handle_noise: str = "unsorted" # "skip" | "unsorted" | "leave"
noise_folder_name: str = "未分类"
class OrganizeFolderEdit(BaseModel):
cluster_id: str
new_folder_name: str
class OrganizeFilesConfirmReq(BaseModel):
job_id: str
folder_edits: Optional[List[OrganizeFolderEdit]] = None
skip_cluster_ids: Optional[List[str]] = None
# ========== Utility Functions ==========
def _sanitize_folder_name(name: str, max_len: int = 50) -> str:
"""Sanitize folder name, remove illegal characters but keep spaces for readability."""
if not name:
return "cluster"
import re
# Remove illegal characters for Windows/Unix
illegal = r'<>:"/\|?*'
for c in illegal:
name = name.replace(c, ' ')
# Remove leading/trailing spaces and dots
name = name.strip(' .')
# Replace multiple consecutive spaces with single space (keep spaces, don't convert to underscore)
name = re.sub(r'\s+', ' ', name)
# Truncate
if len(name) > max_len:
name = name[:max_len].rstrip(' ._')
return name or "cluster"
def _generate_folder_name(
cluster: Dict,
naming: str,
existing_disk_names: set,
max_len: int
) -> str:
"""
Generate folder name for a cluster (without uniqueness check).
Args:
cluster: cluster info dict
naming: naming strategy ("title" | "keywords" | "id")
existing_disk_names: set of folder names already existing on disk (case-insensitive, for merging)
max_len: max folder name length
Returns:
folder name string
Note: This function may return the same name for different clusters.
The caller (_build_file_mappings) is responsible for merging clusters with the same name.
"""
if naming == "title":
base = cluster.get("title", "") or f"cluster_{cluster.get('id', '')}"
elif naming == "keywords":
kws = cluster.get("keywords", [])[:3]
base = "_".join(kws) if kws else f"cluster_{cluster.get('id', '')}"
else: # id
base = f"cluster_{cluster.get('id', '')}"
base = _sanitize_folder_name(base, max_len)
# Check if this name matches an existing folder on disk (case-insensitive)
# If so, use the disk name (preserving case) for merging
existing_disk_lower_map = {n.lower(): n for n in existing_disk_names}
if base.lower() in existing_disk_lower_map:
return existing_disk_lower_map[base.lower()]
return base
def _build_file_mappings(
cluster_result: Dict,
dest_folder: str,
folder_naming: str,
max_len: int,
handle_noise: str,
noise_folder_name: str
) -> Dict:
"""
Build file mappings from cluster result.
Returns: {clusters, noise, all_mappings}
Note: Clusters with the same generated folder name will be merged into one.
"""
# Get existing folder names on disk (for merge detection)
existing_disk_names = set()
if os.path.isdir(dest_folder):
for item in os.listdir(dest_folder):
item_path = os.path.join(dest_folder, item)
if os.path.isdir(item_path):
existing_disk_names.add(item)
all_mappings = []
# First pass: generate folder names and group clusters by folder name
# This merges clusters that would end up with the same folder name
folder_to_clusters: Dict[str, Dict] = {} # folder_name -> merged cluster info
for cluster in cluster_result.get("clusters", []):
folder_name = _generate_folder_name(cluster, folder_naming, existing_disk_names, max_len)
if folder_name in folder_to_clusters:
# Merge into existing
existing = folder_to_clusters[folder_name]
existing["paths"].extend(cluster.get("paths", []))
existing["cluster_ids"].append(cluster["id"])
# Merge keywords (dedupe)
for kw in cluster.get("keywords", []):
if kw not in existing["keywords"]:
existing["keywords"].append(kw)
else:
# New folder
folder_to_clusters[folder_name] = {
"folder_name": folder_name,
"title": cluster.get("title", ""),
"keywords": list(cluster.get("keywords", [])),
"paths": list(cluster.get("paths", [])),
"cluster_ids": [cluster["id"]],
}
# Second pass: build file mappings from merged clusters
clusters_preview = []
for folder_name, merged in folder_to_clusters.items():
file_mappings = []
# Use first cluster_id as the main id, or combine them
cluster_id = merged["cluster_ids"][0] if len(merged["cluster_ids"]) == 1 else "_".join(merged["cluster_ids"])
for path in merged["paths"]:
filename = os.path.basename(path)
dest_path = os.path.join(dest_folder, folder_name, filename)
mapping = {
"src_path": path,
"dest_folder_name": folder_name,
"dest_path": dest_path,
"cluster_id": cluster_id,
"is_noise": False
}
file_mappings.append(mapping)
all_mappings.append(mapping)
clusters_preview.append({
"cluster_id": cluster_id,
"suggested_folder_name": folder_name,
"title": merged["title"],
"keywords": merged["keywords"],
"size": len(file_mappings),
"file_mappings": file_mappings
})
# Process noise files
noise_mappings = []
noise_paths = cluster_result.get("noise", [])
noise_folder = ""
if handle_noise == "unsorted" and noise_paths:
# Put into "unsorted" folder
noise_folder_base = _sanitize_folder_name(noise_folder_name, max_len)
# Check if noise folder already exists on disk (merge into it)
existing_disk_lower_map = {n.lower(): n for n in existing_disk_names}
if noise_folder_base.lower() in existing_disk_lower_map:
# Use existing folder name (case-preserved)
noise_folder = existing_disk_lower_map[noise_folder_base.lower()]
else:
# Check if it conflicts with a cluster folder name
used_folder_names = set(folder_to_clusters.keys())
used_lower_map = {n.lower(): n for n in used_folder_names}
if noise_folder_base.lower() in used_lower_map:
# Merge into the cluster folder (or add suffix to avoid confusion)
# For noise, we add suffix to avoid mixing with clustered files
noise_folder = noise_folder_base
counter = 1
while noise_folder.lower() in used_lower_map or noise_folder.lower() in existing_disk_lower_map:
noise_folder = f"{noise_folder_base}_{counter}"
counter += 1
else:
noise_folder = noise_folder_base
for path in noise_paths:
filename = os.path.basename(path)
dest_path = os.path.join(dest_folder, noise_folder, filename)
mapping = {
"src_path": path,
"dest_folder_name": noise_folder,
"dest_path": dest_path,
"cluster_id": "__noise__",
"is_noise": True
}
noise_mappings.append(mapping)
all_mappings.append(mapping)
elif handle_noise == "leave":
# Keep in original location, don't move
for path in noise_paths:
mapping = {
"src_path": path,
"dest_folder_name": "",
"dest_path": path, # Keep original
"cluster_id": "__noise__",
"is_noise": True
}
noise_mappings.append(mapping)
# Don't add to all_mappings since no move needed
# "skip": completely ignore noise files
noise_cluster = {
"cluster_id": "__noise__",
"suggested_folder_name": noise_folder,
"title": "未分类" if noise_folder_name == "未分类" else "Unsorted",
"keywords": [],
"size": len(noise_paths),
"file_mappings": noise_mappings
}
return {
"clusters": clusters_preview,
"noise": noise_cluster,
"all_mappings": all_mappings
}
# ========== Route Mounting ==========
def mount_organize_routes(
app: FastAPI,
db_api_base: str,
verify_secret,
write_permission_required,
# Cluster job functions from topic_cluster
start_cluster_job_func: Callable,
get_cluster_job_status_func: Callable,
):
"""Mount file organization routes."""
async def _run_organize_job(job_id: str, req: OrganizeFilesReq):
"""
Background task for file organization.
Uses topic_cluster job API internally.
"""
try:
logger.info(f"[organize_files][{job_id}] Starting organize job")
_organize_job_upsert(job_id, {
"status": "running",
"progress": {
"stage": "clustering",
"embedded_done": 0,
"to_embed": 0,
"clusters_done": 0,
"clusters_total": 0,
"moved_done": 0,
"moved_total": 0,
"current_file": "",
"created_folders": [],
"errors": []
}
})
# 1. Start cluster job using topic_cluster API
logger.info(f"[organize_files][{job_id}] Starting cluster job (recursive={req.recursive})")
cluster_job_id = await start_cluster_job_func(
folder_paths=req.folder_paths,
threshold=req.threshold,
min_cluster_size=req.min_cluster_size,
lang=req.lang,
recursive=req.recursive,
)
# 2. Poll cluster job status until done
logger.info(f"[organize_files][{job_id}] Polling cluster job {cluster_job_id}")
max_wait = 3600 # 1 hour max
start_time = time.time()
poll_interval = 0.5
while True:
if time.time() - start_time > max_wait:
raise Exception("Cluster job timeout")
cluster_status = await get_cluster_job_status_func(cluster_job_id)
status = cluster_status.get("status", "")
stage = cluster_status.get("stage", "")
progress = cluster_status.get("progress", {})
# Update organize job progress based on cluster job
_organize_job_upsert(job_id, {
"progress": {
"stage": stage,
"embedded_done": progress.get("embedded_done", 0),
"to_embed": progress.get("to_embed", 0),
"clusters_done": progress.get("clusters_done", 0),
"clusters_total": progress.get("clusters_total", 0),
"moved_done": 0,
"moved_total": 0,
"current_file": "",
"created_folders": [],
"errors": []
}
})
if status == "done":
cluster_result = cluster_status.get("result", {})
break
elif status == "error":
raise Exception(f"Cluster job failed: {cluster_status.get('error', 'Unknown error')}")
await asyncio.sleep(poll_interval)
# Increase poll interval gradually
poll_interval = min(poll_interval * 1.2, 2.0)
# 3. Build file mappings for preview
logger.info(f"[organize_files][{job_id}] Building file mappings")
dest_folder = req.dest_folder or req.folder_paths[0]
dest_folder = os.path.abspath(dest_folder)
preview_data = _build_file_mappings(
cluster_result,
dest_folder,
req.folder_naming,
req.max_folder_name_length,
req.handle_noise,
req.noise_folder_name
)
preview = {
"job_id": job_id,
"dest_folder": dest_folder,
"total_files": len(preview_data["all_mappings"]),
"clusters": preview_data["clusters"],
"noise": preview_data["noise"],
"all_mappings": preview_data["all_mappings"]
}
_organize_job_upsert(job_id, {
"status": "preview_ready",
"preview": preview,
"progress": {
"stage": "preview_ready",
"embedded_done": 0,
"to_embed": 0,
"clusters_done": len(preview_data["clusters"]),
"clusters_total": len(preview_data["clusters"]),
"moved_done": 0,
"moved_total": 0,
"current_file": "",
"created_folders": [],
"errors": []
}
})
logger.info(f"[organize_files][{job_id}] Preview ready: {len(preview_data['all_mappings'])} files")
except Exception as e:
import traceback
logger.error(f"[organize_files][{job_id}] Error: {e}")
logger.error(traceback.format_exc())
_organize_job_upsert(job_id, {
"status": "error",
"error": str(e),
})
async def _execute_organize(job_id: str, req: OrganizeFilesConfirmReq):
"""
Execute file organization (move/copy files).
"""
try:
job = _organize_job_get(job_id)
if not job:
return
original_req = job.get("req", {})
action = original_req.get("action", "move")
preview = job.get("preview", {})
all_mappings = list(preview.get("all_mappings", []))
dest_folder = preview.get("dest_folder", "")
logger.info(f"[organize_files][{job_id}] Executing organize: action={action}, mappings={len(all_mappings)}")
# Apply user edits
folder_edits = {}
for edit in (req.folder_edits or []):
folder_edits[edit.cluster_id] = edit.new_folder_name
skip_ids = set(req.skip_cluster_ids or [])
# Filter and update mappings
final_mappings = []
for m in all_mappings:
# Skip if cluster is skipped
if m["cluster_id"] in skip_ids:
continue
# Apply folder name edits
if m["cluster_id"] in folder_edits:
new_name = _sanitize_folder_name(folder_edits[m["cluster_id"]])
m["dest_folder_name"] = new_name
m["dest_path"] = os.path.join(dest_folder, new_name, os.path.basename(m["src_path"]))
# Skip files that don't need moving (same src and dest)
if m["dest_path"] != m["src_path"]:
final_mappings.append(m)
total = len(final_mappings)
_organize_job_upsert(job_id, {
"status": "moving",
"progress": {
"stage": "moving",
"moved_total": total,
"moved_done": 0,
"current_file": "",
"created_folders": [],
"errors": []
}
})
# Group by destination folder
by_dest: Dict[str, List[Dict]] = defaultdict(list)
for m in final_mappings:
dest_dir = os.path.dirname(m["dest_path"])
by_dest[dest_dir].append(m)
created_folders = []
errors = []
moved_done = 0
conn = DataBase.get_conn()
# Execute moves/copies
for dest_dir, mappings in by_dest.items():
try:
# Update progress
current_folder = os.path.basename(dest_dir)
_organize_job_upsert(job_id, {
"progress": {
"stage": "moving",
"moved_done": moved_done,
"moved_total": total,
"current_file": f"-> {current_folder}/",
"created_folders": created_folders,
"errors": errors
}
})
# Create destination folder
if not os.path.exists(dest_dir):
os.makedirs(dest_dir, exist_ok=True)
created_folders.append(dest_dir)
# Move or copy each file
for m in mappings:
src_path = m["src_path"]
dest_path = m["dest_path"]
try:
txt_path = get_img_geninfo_txt_path(src_path)
if action == "move":
if txt_path:
shutil.move(txt_path, dest_dir)
img = DbImg.get(conn, src_path)
if img:
img.update_path(conn, dest_path, force=True)
shutil.move(src_path, dest_path)
else:
shutil.copy2(src_path, dest_path)
if txt_path:
shutil.copy2(txt_path, dest_dir)
moved_done += 1
except Exception as e:
errors.append({
"src": src_path,
"dest": dest_path,
"error": str(e)
})
logger.error(f"[organize_files][{job_id}] Error moving {src_path}: {e}")
conn.commit()
logger.info(f"[organize_files][{job_id}] Processed {len(mappings)} files to {dest_dir}")
except Exception as e:
conn.rollback()
error_info = {"dest": dest_dir, "count": len(mappings), "error": str(e)}
errors.append(error_info)
logger.error(f"[organize_files][{job_id}] Error with folder {dest_dir}: {e}")
# Yield control
await asyncio.sleep(0)
# Done
_organize_job_upsert(job_id, {
"status": "done",
"progress": {
"stage": "done",
"moved_done": moved_done,
"moved_total": total,
"current_file": "",
"created_folders": created_folders,
"errors": errors
},
"result": {
"moved_count": moved_done,
"created_folders": created_folders,
"errors": errors
}
})
logger.info(f"[organize_files][{job_id}] Completed: moved {moved_done} files to {len(created_folders)} folders")
except Exception as e:
import traceback
logger.error(f"[organize_files][{job_id}] Execute error: {e}")
logger.error(traceback.format_exc())
_organize_job_upsert(job_id, {
"status": "error",
"error": str(e),
})
@app.post(
f"{db_api_base}/organize_files_start",
dependencies=[Depends(verify_secret), Depends(write_permission_required)],
)
async def organize_files_start(req: OrganizeFilesReq):
"""
Start file organization task (background).
Returns job_id for progress polling.
"""
# Validate folders
folders = []
for p in req.folder_paths:
if isinstance(p, str) and p.strip():
folders.append(os.path.normpath(p.strip()))
if not folders:
raise HTTPException(400, "folder_paths is required")
for f in folders:
if not os.path.exists(f) or not os.path.isdir(f):
raise HTTPException(400, f"Folder not found: {f}")
# Update req with normalized folders
req.folder_paths = folders
job_id = uuid.uuid4().hex
_organize_job_upsert(job_id, {
"status": "queued",
"req": req.dict(),
"created_at": _job_now()
})
asyncio.create_task(_run_organize_job(job_id, req))
logger.info(f"[organize_files] Started job {job_id} for folders: {folders}")
return {"job_id": job_id}
@app.get(
f"{db_api_base}/organize_files_status",
dependencies=[Depends(verify_secret)],
)
async def organize_files_status(job_id: str):
"""
Query task status and progress.
Status flow: queued -> running -> preview_ready -> (confirm) -> moving -> done
"""
job = _organize_job_get(job_id)
if not job:
raise HTTPException(404, "Job not found")
return job
@app.post(
f"{db_api_base}/organize_files_confirm",
dependencies=[Depends(verify_secret), Depends(write_permission_required)],
)
async def organize_files_confirm(req: OrganizeFilesConfirmReq):
"""
Confirm and execute file organization.
- Can modify folder names via folder_edits
- Can skip clusters via skip_cluster_ids
"""
job = _organize_job_get(req.job_id)
if not job:
raise HTTPException(404, "Job not found")
if job.get("status") != "preview_ready":
raise HTTPException(400, f"Job not ready for confirmation, current status: {job.get('status')}")
asyncio.create_task(_execute_organize(req.job_id, req))
return {"ok": True, "job_id": req.job_id}

View File

@ -896,10 +896,11 @@ def accumulate_streaming_response(resp: requests.Response) -> str:
delta = (obj.get('choices') or [{}])[0].get('delta') or {} delta = (obj.get('choices') or [{}])[0].get('delta') or {}
chunk_text = delta.get('content') or '' chunk_text = delta.get('content') or ''
if chunk_text: if chunk_text:
# try: try:
# print(f"[streaming] chunk_received len={len(chunk_text)} snippet={chunk_text[:200]}") if is_dev:
# except Exception: print(f"[streaming] chunk_received len={len(chunk_text)} snippet={chunk_text[:200]}")
# pass except Exception:
pass
content_buffer += chunk_text content_buffer += chunk_text
return content_buffer.strip() return content_buffer.strip()

View File

@ -157,6 +157,12 @@ def _job_get(job_id: str) -> Optional[Dict]:
return dict(j) if isinstance(j, dict) else None return dict(j) if isinstance(j, dict) else None
# Export for use by other modules (e.g., organize_files)
def get_cluster_job_status(job_id: str) -> Optional[Dict]:
"""Public function to get cluster job status."""
return _job_get(job_id)
def _job_upsert(job_id: str, patch: Dict) -> None: def _job_upsert(job_id: str, patch: Dict) -> None:
with _TOPIC_CLUSTER_JOBS_LOCK: with _TOPIC_CLUSTER_JOBS_LOCK:
cur = _TOPIC_CLUSTER_JOBS.get(job_id) cur = _TOPIC_CLUSTER_JOBS.get(job_id)
@ -934,6 +940,7 @@ def mount_topic_cluster_routes(
force: bool, force: bool,
batch_size: int, batch_size: int,
max_chars: int, max_chars: int,
recursive: bool = True,
progress_cb: Optional[Callable[[Dict], None]] = None, progress_cb: Optional[Callable[[Dict], None]] = None,
) -> Dict: ) -> Dict:
""" """
@ -941,10 +948,13 @@ def mount_topic_cluster_routes(
Progress payload (best-effort): Progress payload (best-effort):
- stage: "embedding" - stage: "embedding"
- folder, scanned, to_embed, embedded_done, updated, skipped - folder, scanned, to_embed, embedded_done, updated, skipped
Args:
recursive: If True, include files in subfolders. Default True for backward compatibility.
""" """
logger.info("[build_embeddings] === _build_embeddings_one_folder START ===") logger.info("[build_embeddings] === _build_embeddings_one_folder START ===")
logger.info("[build_embeddings] folder=%s model=%s force=%s batch_size=%s max_chars=%s", logger.info("[build_embeddings] folder=%s model=%s force=%s batch_size=%s max_chars=%s recursive=%s",
folder, model, force, batch_size, max_chars) folder, model, force, batch_size, max_chars, recursive)
if not openai_api_key: if not openai_api_key:
logger.error("[build_embeddings] OpenAI API Key not configured") logger.error("[build_embeddings] OpenAI API Key not configured")
@ -970,6 +980,14 @@ def mount_topic_cluster_routes(
cur.execute("SELECT id, path, exif FROM image WHERE path LIKE ?", (like_prefix,)) cur.execute("SELECT id, path, exif FROM image WHERE path LIKE ?", (like_prefix,))
rows = cur.fetchall() rows = cur.fetchall()
# Filter to direct children only if not recursive
if not recursive:
def is_direct_child(path: str) -> bool:
parent_dir = os.path.dirname(path)
return os.path.normpath(parent_dir) == os.path.normpath(folder)
rows = [r for r in rows if is_direct_child(r[1])]
logger.info("[build_embeddings] Filtered to direct children: %d files", len(rows))
images = [] images = []
for image_id, path, exif in rows: for image_id, path, exif in rows:
if not isinstance(path, str) or not os.path.exists(path): if not isinstance(path, str) or not os.path.exists(path):
@ -1165,6 +1183,8 @@ def mount_topic_cluster_routes(
force: Optional[bool] = False force: Optional[bool] = False
batch_size: Optional[int] = 64 batch_size: Optional[int] = 64
max_chars: Optional[int] = 4000 max_chars: Optional[int] = 4000
# If True, recursively scan subfolders; default True for backward compatibility
recursive: Optional[bool] = True
@app.post( @app.post(
f"{db_api_base}/build_iib_output_embeddings", f"{db_api_base}/build_iib_output_embeddings",
@ -1182,12 +1202,14 @@ def mount_topic_cluster_routes(
batch_size = max(1, min(int(req.batch_size or 64), 256)) batch_size = max(1, min(int(req.batch_size or 64), 256))
max_chars = max(256, min(int(req.max_chars or 4000), 8000)) max_chars = max(256, min(int(req.max_chars or 4000), 8000))
force = bool(req.force) force = bool(req.force)
recursive = bool(req.recursive) if req.recursive is not None else True
return await _build_embeddings_one_folder( return await _build_embeddings_one_folder(
folder=folder, folder=folder,
model=model, model=model,
force=force, force=force,
batch_size=batch_size, batch_size=batch_size,
max_chars=max_chars, max_chars=max_chars,
recursive=recursive,
progress_cb=None, progress_cb=None,
) )
@ -1210,6 +1232,8 @@ def mount_topic_cluster_routes(
force_title: Optional[bool] = False force_title: Optional[bool] = False
# Output language for titles/keywords (from frontend globalStore.lang) # Output language for titles/keywords (from frontend globalStore.lang)
lang: Optional[str] = None lang: Optional[str] = None
# If True, recursively scan subfolders; default True for Topic Search (backward compatible)
recursive: Optional[bool] = True
def _scope_cache_stale_by_folders(conn: Connection, folders: List[str]) -> Dict: def _scope_cache_stale_by_folders(conn: Connection, folders: List[str]) -> Dict:
""" """
@ -1409,6 +1433,9 @@ def mount_topic_cluster_routes(
logger.info("[cluster_after] use_title_cache=%s force_title=%s", use_title_cache, force_title) logger.info("[cluster_after] use_title_cache=%s force_title=%s", use_title_cache, force_title)
recursive = bool(req.recursive) if req.recursive is not None else False
logger.info("[cluster_after] recursive=%s", recursive)
if progress_cb: if progress_cb:
logger.info("[cluster_after] Calling progress callback with clustering stage") logger.info("[cluster_after] Calling progress callback with clustering stage")
progress_cb({"stage": "clustering", "folder": folder, "folders": folders}) progress_cb({"stage": "clustering", "folder": folder, "folders": folders})
@ -1426,6 +1453,17 @@ def mount_topic_cluster_routes(
) )
rows = cur.fetchall() rows = cur.fetchall()
# Filter to direct children only if not recursive
if not recursive:
def is_direct_child(path: str, folders: List[str]) -> bool:
for f in folders:
parent_dir = os.path.dirname(path)
if os.path.normpath(parent_dir) == os.path.normpath(f):
return True
return False
rows = [r for r in rows if is_direct_child(r[1], folders)]
logger.info("[cluster_after] Filtered to direct children: %d files", len(rows))
items = [] items = []
for n, (image_id, path, exif, vec_blob) in enumerate(rows): for n, (image_id, path, exif, vec_blob) in enumerate(rows):
if not isinstance(path, str) or not os.path.exists(path): if not isinstance(path, str) or not os.path.exists(path):
@ -1608,21 +1646,18 @@ def mount_topic_cluster_routes(
sorted_keywords = sorted(keyword_frequency.items(), key=lambda x: x[1], reverse=True) sorted_keywords = sorted(keyword_frequency.items(), key=lambda x: x[1], reverse=True)
return [k for k, v in sorted_keywords[:100]] return [k for k, v in sorted_keywords[:100]]
# Separate clusters that need LLM title from those that are too small (noise)
clusters_to_title = []
for cidx, c in enumerate(clusters): for cidx, c in enumerate(clusters):
if len(c["members"]) < min_cluster_size: if len(c["members"]) < min_cluster_size:
for mi in c["members"]: for mi in c["members"]:
noise.append(items[mi]["path"]) noise.append(items[mi]["path"])
continue else:
member_items = [items[mi] for mi in c["members"]] member_items = [items[mi] for mi in c["members"]]
paths = [x["path"] for x in member_items] paths = [x["path"] for x in member_items]
texts = [x.get("text") or "" for x in member_items] texts = [x.get("text") or "" for x in member_items]
member_ids = [x["id"] for x in member_items] member_ids = [x["id"] for x in member_items]
# Representative prompt for LLM title generation
rep = (c.get("sample_text") or (texts[0] if texts else "")).strip() rep = (c.get("sample_text") or (texts[0] if texts else "")).strip()
cached = None
cluster_hash = _cluster_sig( cluster_hash = _cluster_sig(
member_ids=member_ids, member_ids=member_ids,
model=model, model=model,
@ -1631,8 +1666,30 @@ def mount_topic_cluster_routes(
title_model=title_model, title_model=title_model,
lang=output_lang, lang=output_lang,
) )
clusters_to_title.append({
"cidx": cidx,
"paths": paths,
"texts": texts,
"rep": rep,
"cluster_hash": cluster_hash,
})
# Process LLM titles concurrently in batches
LLM_CONCURRENCY = 5 # Number of concurrent LLM requests
completed_count = [0] # Use list for closure mutation
async def process_cluster_title(cluster_info: dict) -> dict:
cidx = cluster_info["cidx"]
paths = cluster_info["paths"]
texts = cluster_info["texts"]
rep = cluster_info["rep"]
cluster_hash = cluster_info["cluster_hash"]
# Check cache first
cached = None
if use_title_cache and (not force_title): if use_title_cache and (not force_title):
cached = TopicTitleCache.get(conn, cluster_hash) cached = TopicTitleCache.get(conn, cluster_hash)
if cached and isinstance(cached, dict) and cached.get("title"): if cached and isinstance(cached, dict) and cached.get("title"):
title = str(cached.get("title")) title = str(cached.get("title"))
keywords = cached.get("keywords") or [] keywords = cached.get("keywords") or []
@ -1657,11 +1714,12 @@ def mount_topic_cluster_routes(
except Exception: except Exception:
pass pass
for kw in keywords or []: # Update progress
keyword_frequency[kw] = keyword_frequency.get(kw, 0) + 1 completed_count[0] += 1
if progress_cb:
progress_cb({"stage": "titling", "clusters_total": len(clusters_to_title), "clusters_done": completed_count[0]})
out_clusters.append( return {
{
"id": f"topic_{cidx}", "id": f"topic_{cidx}",
"title": title, "title": title,
"keywords": keywords, "keywords": keywords,
@ -1669,11 +1727,24 @@ def mount_topic_cluster_routes(
"paths": paths, "paths": paths,
"sample_prompt": _clean_for_title(rep)[:200], "sample_prompt": _clean_for_title(rep)[:200],
} }
)
if (cidx + 1) % 6 == 0: # Use semaphore to limit concurrency
if progress_cb: semaphore = asyncio.Semaphore(LLM_CONCURRENCY)
progress_cb({"stage": "titling", "clusters_total": len(clusters), "clusters_done": cidx + 1})
await asyncio.sleep(0) async def process_with_semaphore(cluster_info: dict) -> dict:
async with semaphore:
return await process_cluster_title(cluster_info)
# Run all title generations concurrently (limited by semaphore)
if clusters_to_title:
logger.info(f"[cluster_after] Processing {len(clusters_to_title)} clusters with concurrency={LLM_CONCURRENCY}")
tasks = [process_with_semaphore(c) for c in clusters_to_title]
out_clusters = await asyncio.gather(*tasks)
# Update keyword frequency from results
for cluster in out_clusters:
for kw in cluster.get("keywords") or []:
keyword_frequency[kw] = keyword_frequency.get(kw, 0) + 1
out_clusters.sort(key=lambda x: x["size"], reverse=True) out_clusters.sort(key=lambda x: x["size"], reverse=True)
return { return {
@ -1862,4 +1933,44 @@ def mount_topic_cluster_routes(
# We intentionally do NOT keep the legacy synchronous `/cluster_iib_output` endpoint. # We intentionally do NOT keep the legacy synchronous `/cluster_iib_output` endpoint.
# The UI should use `/cluster_iib_output_job_start` + `/cluster_iib_output_job_status` only. # The UI should use `/cluster_iib_output_job_start` + `/cluster_iib_output_job_status` only.
# Return internal functions for use by other modules (e.g., organize_files)
async def start_cluster_job_for_organize(
folder_paths: List[str],
threshold: float = 0.90,
min_cluster_size: int = 2,
lang: str = "en",
recursive: bool = False,
) -> str:
"""
Start a cluster job and return job_id.
This is a wrapper for organize_files to use.
"""
_ensure_perf_deps()
req = ClusterIibOutputReq(
folder_paths=folder_paths,
threshold=threshold,
min_cluster_size=min_cluster_size,
lang=lang,
recursive=recursive,
)
job_id = uuid.uuid4().hex
_job_upsert(job_id, {"status": "queued", "stage": "queued", "created_at": _job_now()})
asyncio.create_task(_run_cluster_job(job_id, req))
return job_id
async def get_cluster_job_status_for_organize(job_id: str) -> Dict:
"""
Get cluster job status.
This is a wrapper for organize_files to use.
"""
j = _job_get(job_id)
if not j:
return {"status": "not_found", "error": "job not found"}
return j
return {
"start_cluster_job": start_cluster_job_for_organize,
"get_cluster_job_status": get_cluster_job_status_for_organize,
}

3
vue/components.d.ts vendored
View File

@ -33,6 +33,7 @@ declare module '@vue/runtime-core' {
AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
AProgress: typeof import('ant-design-vue/es')['Progress'] AProgress: typeof import('ant-design-vue/es')['Progress']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARow: typeof import('ant-design-vue/es')['Row'] ARow: typeof import('ant-design-vue/es')['Row']
@ -57,7 +58,9 @@ declare module '@vue/runtime-core' {
HistoryRecord: typeof import('./src/components/HistoryRecord.vue')['default'] HistoryRecord: typeof import('./src/components/HistoryRecord.vue')['default']
MultiSelectKeep: typeof import('./src/components/MultiSelectKeep.vue')['default'] MultiSelectKeep: typeof import('./src/components/MultiSelectKeep.vue')['default']
NumInput: typeof import('./src/components/numInput.vue')['default'] NumInput: typeof import('./src/components/numInput.vue')['default']
OrganizeJobsPanel: typeof import('./src/components/OrganizeJobsPanel.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SmartOrganizeConfigModal: typeof import('./src/components/SmartOrganizeConfigModal.vue')['default']
} }
} }

File diff suppressed because one or more lines are too long

1
vue/dist/assets/FileItem-353a3123.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
vue/dist/assets/FileItem-c62b10f3.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{d as a,o as t,k as s,c as n,cF as _,q as o}from"./index-2865012e.js";const c={class:"img-sli-container"},i=a({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(l){return(e,r)=>(t(),s("div",c,[n(_,{left:e.left,right:e.right},null,8,["left","right"])]))}});const d=o(i,[["__scopeId","data-v-ae3fb9a8"]]);export{d as default};

View File

@ -0,0 +1 @@
.img-sli-container[data-v-ec71de83]{position:relative;overflow-y:auto;height:calc(100vh - 40px)}

View File

@ -1 +0,0 @@
.img-sli-container[data-v-ae3fb9a8]{position:relative;overflow-y:auto;height:calc(100vh - 40px)}

View File

@ -0,0 +1 @@
import{d as t,o as a,j as n,c as s,b_ as _,n as o}from"./index-c24b5c8e.js";const c={class:"img-sli-container"},i=t({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(l){return(e,r)=>(a(),n("div",c,[s(_,{left:e.left,right:e.right},null,8,["left","right"])]))}});const p=o(i,[["__scopeId","data-v-ec71de83"]]);export{p as default};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.container[data-v-6e837a6f]{background:var(--zp-secondary-background);position:relative}.container .action-bar[data-v-6e837a6f]{display:flex;align-items:center;user-select:none;gap:4px;padding:4px}.container .action-bar>*[data-v-6e837a6f]{flex-wrap:wrap}.container .file-list[data-v-6e837a6f]{list-style:none;padding:8px;overflow:auto;height:calc(var(--pane-max-height) - 40px);width:100%}.container .no-res-hint[data-v-6e837a6f]{height:var(--pane-max-height);display:flex;align-items:center;flex-direction:column;justify-content:center}.container .no-res-hint .hint[data-v-6e837a6f]{font-size:1.6em;margin-bottom:2em;text-align:center}

View File

@ -0,0 +1 @@
.container[data-v-795e5ef4]{background:var(--zp-secondary-background);position:relative;height:var(--pane-max-height)}.action-bar[data-v-795e5ef4]{display:flex;align-items:center;user-select:none;gap:6px;padding:6px 8px}.title[data-v-795e5ef4]{font-weight:700;max-width:40vw}.file-list[data-v-795e5ef4]{list-style:none;padding:8px;overflow:auto;height:calc(var(--pane-max-height) - 44px);width:100%}.no-res-hint[data-v-795e5ef4]{height:calc(var(--pane-max-height) - 44px);display:flex;align-items:center;flex-direction:column;justify-content:center}.no-res-hint .hint[data-v-795e5ef4]{font-size:1.2em;opacity:.7}.preview-switch[data-v-795e5ef4]{position:fixed;bottom:24px;right:24px;display:flex;gap:8px;font-size:36px;user-select:none}.disable[data-v-795e5ef4]{opacity:.3;pointer-events:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.container[data-v-4815fec6]{background:var(--zp-secondary-background);position:relative}.container .action-bar[data-v-4815fec6]{display:flex;align-items:center;user-select:none;gap:4px;padding:4px}.container .action-bar>*[data-v-4815fec6]{flex-wrap:wrap}.container .file-list[data-v-4815fec6]{list-style:none;padding:8px;overflow:auto;height:calc(var(--pane-max-height) - 40px);width:100%}.container .no-res-hint[data-v-4815fec6]{height:var(--pane-max-height);display:flex;align-items:center;flex-direction:column;justify-content:center}.container .no-res-hint .hint[data-v-4815fec6]{font-size:1.6em;margin-bottom:2em;text-align:center}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.container[data-v-aea581a5]{background:var(--zp-secondary-background);position:relative;height:var(--pane-max-height)}.action-bar[data-v-aea581a5]{display:flex;align-items:center;user-select:none;gap:6px;padding:6px 8px}.title[data-v-aea581a5]{font-weight:700;max-width:40vw}.file-list[data-v-aea581a5]{list-style:none;padding:8px;overflow:auto;height:calc(var(--pane-max-height) - 44px);width:100%}.no-res-hint[data-v-aea581a5]{height:calc(var(--pane-max-height) - 44px);display:flex;align-items:center;flex-direction:column;justify-content:center}.no-res-hint .hint[data-v-aea581a5]{font-size:1.2em;opacity:.7}.preview-switch[data-v-aea581a5]{position:fixed;bottom:24px;right:24px;display:flex;gap:8px;font-size:36px;user-select:none}.disable[data-v-aea581a5]{opacity:.3;pointer-events:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
[data-v-9726dd9e] .float-panel{position:fixed}.regex-icon[data-v-9726dd9e]{user-select:none;padding:4px;margin:0 4px;cursor:pointer;border:1px solid var(--zp-border);border-radius:4px}.regex-icon img[data-v-9726dd9e]{height:1.5em}.regex-icon[data-v-9726dd9e]:hover{background:var(--zp-border)}.regex-icon.selected[data-v-9726dd9e]{background:var(--primary-color-1);border:1px solid var(--primary-color)}.search-bar[data-v-9726dd9e]{padding:8px 8px 0;display:flex}.search-bar.last[data-v-9726dd9e]{padding-bottom:8px}.search-bar .form-name[data-v-9726dd9e]{flex-shrink:0;padding:4px 8px}.search-bar .actions>*[data-v-9726dd9e]{margin-right:4px}.container[data-v-9726dd9e]{background:var(--zp-secondary-background);position:relative}.container .file-list[data-v-9726dd9e]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
[data-v-2c2d47fe] .float-panel{position:fixed}.regex-icon[data-v-2c2d47fe]{user-select:none;padding:4px;margin:0 4px;cursor:pointer;border:1px solid var(--zp-border);border-radius:4px}.regex-icon img[data-v-2c2d47fe]{height:1.5em}.regex-icon[data-v-2c2d47fe]:hover{background:var(--zp-border)}.regex-icon.selected[data-v-2c2d47fe]{background:var(--primary-color-1);border:1px solid var(--primary-color)}.search-bar[data-v-2c2d47fe]{padding:8px 8px 0;display:flex}.search-bar.last[data-v-2c2d47fe]{padding-bottom:8px}.search-bar .form-name[data-v-2c2d47fe]{flex-shrink:0;padding:4px 8px}.search-bar .actions>*[data-v-2c2d47fe]{margin-right:4px}.container[data-v-2c2d47fe]{background:var(--zp-secondary-background);position:relative}.container .file-list[data-v-2c2d47fe]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

15
vue/dist/assets/TagSearch-ec13e7fa.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.tag-hierarchy-graph[data-v-11299bfc]{width:100%;height:calc(100vh - 200px);min-height:600px;position:relative;background:linear-gradient(135deg,#1a1a2e 0%,#16213e 100%);border-radius:8px;overflow:hidden}.loading-container[data-v-11299bfc],.error-container[data-v-11299bfc]{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#fff}.loading-text[data-v-11299bfc]{margin-top:16px;font-size:14px;color:#aaa}.graph-container[data-v-11299bfc]{width:100%;height:100%;position:relative}.control-panel[data-v-11299bfc]{position:absolute;top:16px;left:16px;z-index:10;background:rgba(0,0,0,.7);padding:8px 12px;border-radius:6px;backdrop-filter:blur(10px)}.chart-container[data-v-11299bfc]{width:100%;height:100%}.topic-search[data-v-d9f73106]{height:var(--pane-max-height);overflow:auto;padding:12px}.toolbar[data-v-d9f73106]{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;background:var(--zp-primary-background);border-radius:12px}.left[data-v-d9f73106]{display:flex;align-items:center;gap:8px}.title[data-v-d9f73106]{display:flex;align-items:center;gap:8px;font-weight:700}.right[data-v-d9f73106]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.label[data-v-d9f73106]{color:#888}.guide[data-v-d9f73106]{display:flex;flex-direction:column;gap:8px;padding:10px 12px;background:var(--zp-primary-background);border-radius:12px;border:1px solid rgba(0,0,0,.06)}.guide-row[data-v-d9f73106]{display:flex;align-items:center;gap:10px}.guide-hint[data-v-d9f73106]{margin-top:4px;display:flex;align-items:center;gap:10px;opacity:.85}.guide-icon[data-v-d9f73106]{width:22px;text-align:center;flex:0 0 22px}.guide-text[data-v-d9f73106]{flex:1 1 auto;min-width:0;color:#000000bf}.grid[data-v-d9f73106]{margin-top:12px;display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}.card[data-v-d9f73106]{background:var(--zp-primary-background);border-radius:12px;padding:10px 12px;cursor:pointer;border:1px solid rgba(0,0,0,.06)}.card[data-v-d9f73106]:hover{border-color:#1890ff99}.card-top[data-v-d9f73106]{display:flex;align-items:center;justify-content:space-between;gap:8px}.card-title[data-v-d9f73106]{font-weight:700}.card-count[data-v-d9f73106]{min-width:28px;text-align:right;opacity:.75}.card-desc[data-v-d9f73106]{margin-top:6px;color:#666;font-size:12px}.empty[data-v-d9f73106]{height:calc(var(--pane-max-height) - 72px);display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px}.hint[data-v-d9f73106]{font-size:16px;opacity:.75}

File diff suppressed because one or more lines are too long

70
vue/dist/assets/TopicSearch-88c0bd9d.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{b0 as i,aM as t,dI as f,aQ as n}from"./index-c24b5c8e.js";function u(e,a,r){if(!i(r))return!1;var s=typeof a;return(s=="number"?t(r)&&f(a,r.length):s=="string"&&a in r)?n(r[a],e):!1}export{u as i};

View File

@ -1 +0,0 @@
import{bM as i,aT as t,ee as f,bD as n}from"./index-2865012e.js";function u(s,a,r){if(!i(r))return!1;var e=typeof a;return(e=="number"?t(r)&&f(a,r.length):e=="string"&&a in r)?n(r[a],s):!1}export{u as i};

View File

@ -0,0 +1 @@
import{d as F,p as z,b$ as B,bn as S,o as _,j as w,k as f,c as l,x as d,l as p,t as c,y as s,v as R,O as x,c0 as A,c1 as y,R as T,T as E,$ as V,a2 as N,n as U}from"./index-c24b5c8e.js";import{F as j,s as L}from"./FileItem-c62b10f3.js";import{u as O,b as H,j as q}from"./index-47a4d52c.js";import"./index-5130babf.js";import"./shortcut-4e374057.js";import"./_isIterateeCall-19da1ec8.js";const G={class:"actions-panel actions"},P={class:"item"},Q={key:0,class:"file-list"},W={class:"hint"},J=F({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(K){const{stackViewEl:b}=O().toRefs(),{itemSize:h,gridItems:D,cellWidth:g}=H(),i=z(),m=q(),{selectdFiles:a}=B(m),r=S(),v=async e=>{const t=A(e);t&&m.addFiles(t.nodes)},C=async()=>{r.pushAction(async()=>{const e=await y.value.post("/zip",{paths:a.value.map(u=>u.fullpath),compress:i.batchDownloadCompress,pack_only:!1},{responseType:"blob"}),t=window.URL.createObjectURL(new Blob([e.data])),o=document.createElement("a");o.href=t,o.setAttribute("download",`iib_${new Date().toLocaleString()}.zip`),document.body.appendChild(o),o.click()})},I=async()=>{r.pushAction(async()=>{await y.value.post("/zip",{paths:a.value.map(e=>e.fullpath),compress:i.batchDownloadCompress,pack_only:!0},{responseType:"blob"}),T.success(E("success"))})},$=e=>{a.value.splice(e,1)};return(e,t)=>{const o=V,u=N;return _(),w("div",{class:"container",ref_key:"stackViewEl",ref:b,onDrop:v},[f("div",G,[l(o,{onClick:t[0]||(t[0]=n=>s(m).selectdFiles=[])},{default:d(()=>[p(c(e.$t("clear")),1)]),_:1}),f("div",P,[p(c(e.$t("compressFile"))+": ",1),l(u,{checked:s(i).batchDownloadCompress,"onUpdate:checked":t[1]||(t[1]=n=>s(i).batchDownloadCompress=n)},null,8,["checked"])]),l(o,{onClick:I,type:"primary",loading:!s(r).isIdle},{default:d(()=>[p(c(e.$t("packOnlyNotDownload")),1)]),_:1},8,["loading"]),l(o,{onClick:C,type:"primary",loading:!s(r).isIdle},{default:d(()=>[p(c(e.$t("zipDownload")),1)]),_:1},8,["loading"])]),s(a).length?(_(),R(s(L),{key:1,ref:"scroller",class:"file-list",items:s(a).slice(),"item-size":s(h).first,"key-field":"fullpath","item-secondary-size":s(h).second,gridItems:s(D)},{default:d(({item:n,index:k})=>[l(j,{idx:k,file:n,"cell-width":s(g),"enable-close-icon":"",onCloseIconClick:M=>$(k),"full-screen-preview-image-url":s(x)(n),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","onCloseIconClick","full-screen-preview-image-url"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])):(_(),w("div",Q,[f("p",W,c(e.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const oe=U(J,[["__scopeId","data-v-3d7e6f2d"]]);export{oe as default};

View File

@ -1 +0,0 @@
.container[data-v-a2642a17]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-a2642a17]{padding:8px;background-color:var(--zp-primary-background)}.container .actions-panel.actions[data-v-a2642a17]{display:flex;align-items:center;gap:16px;z-index:333}.container .file-list[data-v-a2642a17]{flex:1;z-index:222;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-a2642a17]{text-align:center;font-size:2em;padding:30vh 128px 0}

View File

@ -0,0 +1 @@
.container[data-v-3d7e6f2d]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-3d7e6f2d]{padding:8px;background-color:var(--zp-primary-background)}.container .actions-panel.actions[data-v-3d7e6f2d]{display:flex;align-items:center;gap:16px;z-index:333}.container .file-list[data-v-3d7e6f2d]{flex:1;z-index:222;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-3d7e6f2d]{text-align:center;font-size:2em;padding:30vh 128px 0}

View File

@ -1 +0,0 @@
import{d as F,r as $,cG as B,c3 as S,o as _,k as w,l as f,c as l,y as p,m as d,t as c,z as s,x,O as A,cH as R,cI as y,ag as E,ae as T,W as V,q as N}from"./index-2865012e.js";import{_ as U}from"./index-85cde8a2.js";import{F as H,s as L}from"./FileItem-3bb21719.js";import{u as O,b as j,j as q}from"./index-9ff6b146.js";import"./index-cc5bdcee.js";import"./index-f200274c.js";import"./index-50dada2e.js";import"./shortcut-7cf3ed76.js";import"./index-dfe4c7be.js";import"./_isIterateeCall-3cdb292e.js";const G={class:"actions-panel actions"},W={class:"item"},P={key:0,class:"file-list"},Q={class:"hint"},J=F({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(K){const{stackViewEl:b}=O().toRefs(),{itemSize:h,gridItems:D,cellWidth:g}=j(),i=$(),m=q(),{selectdFiles:a}=B(m),r=S(),v=async e=>{const t=R(e);t&&m.addFiles(t.nodes)},C=async()=>{r.pushAction(async()=>{const e=await y.value.post("/zip",{paths:a.value.map(u=>u.fullpath),compress:i.batchDownloadCompress,pack_only:!1},{responseType:"blob"}),t=window.URL.createObjectURL(new Blob([e.data])),o=document.createElement("a");o.href=t,o.setAttribute("download",`iib_${new Date().toLocaleString()}.zip`),document.body.appendChild(o),o.click()})},I=async()=>{r.pushAction(async()=>{await y.value.post("/zip",{paths:a.value.map(e=>e.fullpath),compress:i.batchDownloadCompress,pack_only:!0},{responseType:"blob"}),E.success(T("success"))})},z=e=>{a.value.splice(e,1)};return(e,t)=>{const o=V,u=U;return _(),w("div",{class:"container",ref_key:"stackViewEl",ref:b,onDrop:v},[f("div",G,[l(o,{onClick:t[0]||(t[0]=n=>s(m).selectdFiles=[])},{default:p(()=>[d(c(e.$t("clear")),1)]),_:1}),f("div",W,[d(c(e.$t("compressFile"))+": ",1),l(u,{checked:s(i).batchDownloadCompress,"onUpdate:checked":t[1]||(t[1]=n=>s(i).batchDownloadCompress=n)},null,8,["checked"])]),l(o,{onClick:I,type:"primary",loading:!s(r).isIdle},{default:p(()=>[d(c(e.$t("packOnlyNotDownload")),1)]),_:1},8,["loading"]),l(o,{onClick:C,type:"primary",loading:!s(r).isIdle},{default:p(()=>[d(c(e.$t("zipDownload")),1)]),_:1},8,["loading"])]),s(a).length?(_(),x(s(L),{key:1,ref:"scroller",class:"file-list",items:s(a).slice(),"item-size":s(h).first,"key-field":"fullpath","item-secondary-size":s(h).second,gridItems:s(D)},{default:p(({item:n,index:k})=>[l(H,{idx:k,file:n,"cell-width":s(g),"enable-close-icon":"",onCloseIconClick:M=>z(k),"full-screen-preview-image-url":s(A)(n),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","onCloseIconClick","full-screen-preview-image-url"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])):(_(),w("div",P,[f("p",Q,c(e.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const ce=N(J,[["__scopeId","data-v-a2642a17"]]);export{ce as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{F as w,s as y}from"./FileItem-3bb21719.js";import{u as k,b}from"./index-9ff6b146.js";import{d as x,r as F,c0 as h,$ as D,b7 as I,bb as C,o as z,k as E,c,y as V,z as e,O as S,cH as B,cJ as R,q as $}from"./index-2865012e.js";import"./index-cc5bdcee.js";import"./index-f200274c.js";import"./index-50dada2e.js";import"./shortcut-7cf3ed76.js";import"./index-dfe4c7be.js";import"./_isIterateeCall-3cdb292e.js";const q=x({__name:"gridView",props:{tabIdx:{},paneIdx:{},id:{},removable:{type:Boolean},allowDragAndDrop:{type:Boolean},files:{},paneKey:{}},setup(p){const o=p,m=F(),{stackViewEl:d}=k().toRefs(),{itemSize:l,gridItems:u,cellWidth:f}=b(),g=h(),s=D(o.files??[]),_=async t=>{const i=B(t);o.allowDragAndDrop&&i&&(s.value=R([...s.value,...i.nodes]))},v=t=>{s.value.splice(t,1)};return I(()=>{m.pageFuncExportMap.set(o.paneKey,{getFiles:()=>C(s.value),setFiles:t=>s.value=t})}),(t,i)=>(z(),E("div",{class:"container",ref_key:"stackViewEl",ref:d,onDrop:_},[c(e(y),{ref:"scroller",class:"file-list",items:s.value.slice(),"item-size":e(l).first,"key-field":"fullpath","item-secondary-size":e(l).second,gridItems:e(u)},{default:V(({item:a,index:r})=>{var n;return[c(w,{idx:r,file:a,"cell-width":e(f),"enable-close-icon":o.removable,onCloseIconClick:A=>v(r),"full-screen-preview-image-url":e(S)(a),"extra-tags":(n=a==null?void 0:a.tags)==null?void 0:n.map(e(g).tagConvert),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","enable-close-icon","onCloseIconClick","full-screen-preview-image-url","extra-tags"])]}),_:1},8,["items","item-size","item-secondary-size","gridItems"])],544))}});const W=$(q,[["__scopeId","data-v-f35f4802"]]);export{W as default};

1
vue/dist/assets/gridView-7100baf1.css vendored Normal file
View File

@ -0,0 +1 @@
.container[data-v-0c31f6b2]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-0c31f6b2]{padding:8px;background-color:var(--zp-primary-background)}.container .file-list[data-v-0c31f6b2]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-0c31f6b2]{text-align:center;font-size:2em;padding:30vh 128px 0}

1
vue/dist/assets/gridView-d93867c4.js vendored Normal file
View File

@ -0,0 +1 @@
import{F as w,s as y}from"./FileItem-c62b10f3.js";import{u as k,b as x}from"./index-47a4d52c.js";import{d as b,p as F,bk as h,r as D,c2 as I,c3 as C,o as E,j as V,c as n,x as z,y as e,O as S,c0 as B,c4 as R,n as A}from"./index-c24b5c8e.js";import"./index-5130babf.js";import"./shortcut-4e374057.js";import"./_isIterateeCall-19da1ec8.js";const K=b({__name:"gridView",props:{tabIdx:{},paneIdx:{},id:{},removable:{type:Boolean},allowDragAndDrop:{type:Boolean},files:{},paneKey:{}},setup(p){const o=p,m=F(),{stackViewEl:d}=k().toRefs(),{itemSize:i,gridItems:u,cellWidth:f}=x(),g=h(),s=D(o.files??[]),_=async t=>{const l=B(t);o.allowDragAndDrop&&l&&(s.value=R([...s.value,...l.nodes]))},v=t=>{s.value.splice(t,1)};return I(()=>{m.pageFuncExportMap.set(o.paneKey,{getFiles:()=>C(s.value),setFiles:t=>s.value=t})}),(t,l)=>(E(),V("div",{class:"container",ref_key:"stackViewEl",ref:d,onDrop:_},[n(e(y),{ref:"scroller",class:"file-list",items:s.value.slice(),"item-size":e(i).first,"key-field":"fullpath","item-secondary-size":e(i).second,gridItems:e(u)},{default:z(({item:a,index:r})=>{var c;return[n(w,{idx:r,file:a,"cell-width":e(f),"enable-close-icon":o.removable,onCloseIconClick:T=>v(r),"full-screen-preview-image-url":e(S)(a),"extra-tags":(c=a==null?void 0:a.tags)==null?void 0:c.map(e(g).tagConvert),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","enable-close-icon","onCloseIconClick","full-screen-preview-image-url","extra-tags"])]}),_:1},8,["items","item-size","item-secondary-size","gridItems"])],544))}});const N=A(K,[["__scopeId","data-v-0c31f6b2"]]);export{N as default};

View File

@ -1 +0,0 @@
.container[data-v-f35f4802]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-f35f4802]{padding:8px;background-color:var(--zp-primary-background)}.container .file-list[data-v-f35f4802]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-f35f4802]{text-align:center;font-size:2em;padding:30vh 128px 0}

1
vue/dist/assets/hook-4227849e.js vendored Normal file
View File

@ -0,0 +1 @@
import{aJ as F,r as g,bD as P,bE as S,ao as A,af as R,bn as q,bF as z,bG as L}from"./index-c24b5c8e.js";import{u as O,b as Q,f as j,c as H,d as T,e as U,i as W,h as B}from"./index-47a4d52c.js";let K=0;const V=()=>++K,X=(n,i,{dataUpdateStrategy:l="replace"}={})=>{const a=F([""]),c=g(!1),t=g(),o=g(!1);let f=g(-1);const v=new Set,w=e=>{var s;l==="replace"?t.value=e:l==="merge"&&(A((Array.isArray(t.value)||typeof t.value>"u")&&Array.isArray(e),"数据更新策略为合并时仅可用于值为数组的情况"),t.value=[...(s=t==null?void 0:t.value)!==null&&s!==void 0?s:[],...e])},d=e=>S(void 0,void 0,void 0,function*(){if(o.value||c.value&&typeof e>"u")return!1;o.value=!0;const s=V();f.value=s;try{let r;if(typeof e=="number"){if(r=a[e],typeof r!="string")return!1}else r=a[a.length-1];const m=yield n(r);if(v.has(s))return v.delete(s),!1;w(i(m));const u=m.cursor;if((e===a.length-1||typeof e!="number")&&(c.value=!u.has_next,u.has_next)){const y=u.next_cursor||u.next;A(typeof y=="string"),a.push(y)}}finally{f.value===s&&(o.value=!1)}return!0}),p=()=>{v.add(f.value),o.value=!1},x=(e=!1)=>S(void 0,void 0,void 0,function*(){const{refetch:s,force:r}=typeof e=="object"?e:{refetch:e};r&&p(),A(!o.value),a.splice(0,a.length,""),o.value=!1,t.value=void 0,c.value=!1,s&&(yield d())}),I=()=>({next:()=>S(void 0,void 0,void 0,function*(){if(o.value)throw new Error("不允许同时迭代");return{done:!(yield d()),value:t.value}})});return P({abort:p,load:c,next:d,res:t,loading:o,cursorStack:a,reset:x,[Symbol.asyncIterator]:I,iter:{[Symbol.asyncIterator]:I}})},ee=n=>F(X(n,i=>i.files,{dataUpdateStrategy:"merge"})),te=n=>{const i=F(new Set),l=R(()=>(n.res??[]).filter(h=>!i.has(h.fullpath))),a=q(),{stackViewEl:c,multiSelectedIdxs:t,stack:o,scroller:f,props:v}=O({images:l}).toRefs(),{itemSize:w,gridItems:d,cellWidth:p,onScroll:x}=Q({fetchNext:()=>n.next()}),{showMenuIdx:I}=j(),{onFileDragStart:e,onFileDragEnd:s}=H(),{showGenInfo:r,imageGenInfo:m,q:u,onContextMenuClick:y,onFileItemClick:C}=T({openNext:z}),{previewIdx:E,previewing:_,onPreviewVisibleChange:D,previewImgMove:J,canPreview:M}=U({loadNext:()=>n.next()}),G=async(h,b,N)=>{o.value=[{curr:"",files:l.value}],await y(h,b,N)};W("removeFiles",async({paths:h})=>{h.forEach(b=>i.add(b))});const k=()=>{L(l.value)};return{images:l,scroller:f,queue:a,iter:n,onContextMenuClickU:G,stackViewEl:c,previewIdx:E,previewing:_,onPreviewVisibleChange:D,previewImgMove:J,canPreview:M,itemSize:w,gridItems:d,showGenInfo:r,imageGenInfo:m,q:u,onContextMenuClick:y,onFileItemClick:C,showMenuIdx:I,multiSelectedIdxs:t,onFileDragStart:e,onFileDragEnd:s,cellWidth:p,onScroll:x,saveLoadedFileAsJson:k,saveAllFileAsJson:async()=>{for(;!n.load;)await n.next();k()},props:v,...B()}};export{ee as c,te as u};

View File

@ -1 +0,0 @@
import{Z as b,$ as g,cl as R,cm as A,ax as F,Y as q,c3 as G,cg as z,cn as L}from"./index-2865012e.js";import{u as O,b as Q,f as j,c as H,d as T,e as U,i as W,h as Y}from"./index-9ff6b146.js";let Z=0;const $=()=>++Z,B=(n,i,{dataUpdateStrategy:l="replace"}={})=>{const a=b([""]),c=g(!1),t=g(),o=g(!1);let f=g(-1);const v=new Set,w=e=>{var s;l==="replace"?t.value=e:l==="merge"&&(F((Array.isArray(t.value)||typeof t.value>"u")&&Array.isArray(e),"数据更新策略为合并时仅可用于值为数组的情况"),t.value=[...(s=t==null?void 0:t.value)!==null&&s!==void 0?s:[],...e])},d=e=>A(void 0,void 0,void 0,function*(){if(o.value||c.value&&typeof e>"u")return!1;o.value=!0;const s=$();f.value=s;try{let r;if(typeof e=="number"){if(r=a[e],typeof r!="string")return!1}else r=a[a.length-1];const m=yield n(r);if(v.has(s))return v.delete(s),!1;w(i(m));const u=m.cursor;if((e===a.length-1||typeof e!="number")&&(c.value=!u.has_next,u.has_next)){const y=u.next_cursor||u.next;F(typeof y=="string"),a.push(y)}}finally{f.value===s&&(o.value=!1)}return!0}),p=()=>{v.add(f.value),o.value=!1},x=(e=!1)=>A(void 0,void 0,void 0,function*(){const{refetch:s,force:r}=typeof e=="object"?e:{refetch:e};r&&p(),F(!o.value),a.splice(0,a.length,""),o.value=!1,t.value=void 0,c.value=!1,s&&(yield d())}),I=()=>({next:()=>A(void 0,void 0,void 0,function*(){if(o.value)throw new Error("不允许同时迭代");return{done:!(yield d()),value:t.value}})});return R({abort:p,load:c,next:d,res:t,loading:o,cursorStack:a,reset:x,[Symbol.asyncIterator]:I,iter:{[Symbol.asyncIterator]:I}})},ee=n=>b(B(n,i=>i.files,{dataUpdateStrategy:"merge"})),te=n=>{const i=b(new Set),l=q(()=>(n.res??[]).filter(h=>!i.has(h.fullpath))),a=G(),{stackViewEl:c,multiSelectedIdxs:t,stack:o,scroller:f,props:v}=O({images:l}).toRefs(),{itemSize:w,gridItems:d,cellWidth:p,onScroll:x}=Q({fetchNext:()=>n.next()}),{showMenuIdx:I}=j(),{onFileDragStart:e,onFileDragEnd:s}=H(),{showGenInfo:r,imageGenInfo:m,q:u,onContextMenuClick:y,onFileItemClick:C}=T({openNext:z}),{previewIdx:_,previewing:E,onPreviewVisibleChange:M,previewImgMove:D,canPreview:J}=U({loadNext:()=>n.next()}),N=async(h,S,P)=>{o.value=[{curr:"",files:l.value}],await y(h,S,P)};W("removeFiles",async({paths:h})=>{h.forEach(S=>i.add(S))});const k=()=>{L(l.value)};return{images:l,scroller:f,queue:a,iter:n,onContextMenuClickU:N,stackViewEl:c,previewIdx:_,previewing:E,onPreviewVisibleChange:M,previewImgMove:D,canPreview:J,itemSize:w,gridItems:d,showGenInfo:r,imageGenInfo:m,q:u,onContextMenuClick:y,onFileItemClick:C,showMenuIdx:I,multiSelectedIdxs:t,onFileDragStart:e,onFileDragEnd:s,cellWidth:p,onScroll:x,saveLoadedFileAsJson:k,saveAllFileAsJson:async()=>{for(;!n.load;)await n.next();k()},props:v,...Y()}};export{ee as c,te as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.ant-switch{margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;box-sizing:border-box;min-width:44px;height:22px;line-height:22px;vertical-align:middle;background-color:#00000040;border:0;border-radius:100px;cursor:pointer;transition:all .2s;user-select:none}.ant-switch:focus{outline:0;box-shadow:0 0 0 2px #0000001a}.ant-switch-checked:focus{box-shadow:0 0 0 2px #fff1e6}.ant-switch:focus:hover{box-shadow:none}.ant-switch-checked{background-color:#d03f0a}.ant-switch-loading,.ant-switch-disabled{cursor:not-allowed;opacity:.4}.ant-switch-loading *,.ant-switch-disabled *{box-shadow:none;cursor:not-allowed}.ant-switch-inner{display:block;margin:0 7px 0 25px;color:#fff;font-size:12px;transition:margin .2s}.ant-switch-checked .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-handle{position:absolute;top:2px;left:2px;width:18px;height:18px;transition:all .2s ease-in-out}.ant-switch-handle:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:#fff;border-radius:9px;box-shadow:0 2px 4px #00230b33;transition:all .2s ease-in-out;content:""}.ant-switch-checked .ant-switch-handle{left:calc(100% - 20px)}.ant-switch:not(.ant-switch-disabled):active .ant-switch-handle:before{right:-30%;left:0}.ant-switch:not(.ant-switch-disabled):active.ant-switch-checked .ant-switch-handle:before{right:0;left:-30%}.ant-switch-loading-icon.anticon{position:relative;top:2px;color:#000000a6;vertical-align:top}.ant-switch-checked .ant-switch-loading-icon{color:#d03f0a}.ant-switch-small{min-width:28px;height:16px;line-height:16px}.ant-switch-small .ant-switch-inner{margin:0 5px 0 18px;font-size:12px}.ant-switch-small .ant-switch-handle{width:12px;height:12px}.ant-switch-small .ant-switch-loading-icon{top:1.5px;font-size:9px}.ant-switch-small.ant-switch-checked .ant-switch-inner{margin:0 18px 0 5px}.ant-switch-small.ant-switch-checked .ant-switch-handle{left:calc(100% - 14px)}.ant-switch-rtl{direction:rtl}.ant-switch-rtl .ant-switch-inner{margin:0 25px 0 7px}.ant-switch-rtl .ant-switch-handle{right:2px;left:auto}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active .ant-switch-handle:before{right:0;left:-30%}.ant-switch-rtl:not(.ant-switch-rtl-disabled):active.ant-switch-checked .ant-switch-handle:before{right:-30%;left:0}.ant-switch-rtl.ant-switch-checked .ant-switch-inner{margin:0 7px 0 25px}.ant-switch-rtl.ant-switch-checked .ant-switch-handle{right:calc(100% - 20px)}.ant-switch-rtl.ant-switch-small.ant-switch-checked .ant-switch-handle{right:calc(100% - 14px)}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.ant-spin{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;display:none;color:#d03f0a;text-align:center;vertical-align:middle;opacity:0;transition:transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #fff}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;transition:opacity .3s}.ant-spin-container:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:none \ ;width:100%;height:100%;background:#fff;opacity:0;transition:all .3s;content:"";pointer-events:none}.ant-spin-blur{clear:both;opacity:.5;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:#00000073}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#d03f0a;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.ant-spin-dot-item:nth-child(1){top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;animation-delay:1.2s}.ant-spin-dot-spin{transform:rotate(45deg);animation:antRotate 1.2s infinite linear}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.ant-spin-blur{background:#fff;opacity:.5}}@keyframes antSpinMove{to{opacity:1}}@keyframes antRotate{to{transform:rotate(405deg)}}.ant-spin-rtl{direction:rtl}.ant-spin-rtl .ant-spin-dot-spin{transform:rotate(-45deg);animation-name:antRotateRtl}@keyframes antRotateRtl{to{transform:rotate(-405deg)}}

3
vue/dist/assets/index-47a4d52c.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{d as $,bt as E,$ as f,v as M,_ as T,a as c,a1 as W,h as x,c as v,P as z}from"./index-2865012e.js";var G=["prefixCls","name","id","type","disabled","readonly","tabindex","autofocus","value","required"],H={prefixCls:String,name:String,id:String,type:String,defaultChecked:{type:[Boolean,Number],default:void 0},checked:{type:[Boolean,Number],default:void 0},disabled:Boolean,tabindex:{type:[Number,String]},readonly:Boolean,autofocus:Boolean,value:z.any,required:Boolean};const L=$({compatConfig:{MODE:3},name:"Checkbox",inheritAttrs:!1,props:E(H,{prefixCls:"rc-checkbox",type:"checkbox",defaultChecked:!1}),emits:["click","change"],setup:function(a,d){var t=d.attrs,h=d.emit,g=d.expose,o=f(a.checked===void 0?a.defaultChecked:a.checked),i=f();M(function(){return a.checked},function(){o.value=a.checked}),g({focus:function(){var e;(e=i.value)===null||e===void 0||e.focus()},blur:function(){var e;(e=i.value)===null||e===void 0||e.blur()}});var l=f(),m=function(e){if(!a.disabled){a.checked===void 0&&(o.value=e.target.checked),e.shiftKey=l.value;var r={target:c(c({},a),{},{checked:e.target.checked}),stopPropagation:function(){e.stopPropagation()},preventDefault:function(){e.preventDefault()},nativeEvent:e};a.checked!==void 0&&(i.value.checked=!!a.checked),h("change",r),l.value=!1}},C=function(e){h("click",e),l.value=e.shiftKey};return function(){var n,e=a.prefixCls,r=a.name,s=a.id,p=a.type,b=a.disabled,K=a.readonly,P=a.tabindex,B=a.autofocus,S=a.value,N=a.required,_=T(a,G),q=t.class,D=t.onFocus,j=t.onBlur,w=t.onKeydown,A=t.onKeypress,F=t.onKeyup,y=c(c({},_),t),O=Object.keys(y).reduce(function(k,u){return(u.substr(0,5)==="aria-"||u.substr(0,5)==="data-"||u==="role")&&(k[u]=y[u]),k},{}),R=W(e,q,(n={},x(n,"".concat(e,"-checked"),o.value),x(n,"".concat(e,"-disabled"),b),n)),V=c(c({name:r,id:s,type:p,readonly:K,disabled:b,tabindex:P,class:"".concat(e,"-input"),checked:!!o.value,autofocus:B,value:S},O),{},{onChange:m,onClick:C,onFocus:D,onBlur:j,onKeydown:w,onKeypress:A,onKeyup:F,required:N});return v("span",{class:R},[v("input",c({ref:i},V),null),v("span",{class:"".concat(e,"-inner")},null)])}}});export{L as V};

1
vue/dist/assets/index-5130babf.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{d as w,bt as z,a9 as A,d4 as j,ad as k,aD as V,d5 as B,d6 as y,e as $,c as s,_ as T,h as r,a as P,d7 as M,P as b}from"./index-2865012e.js";var O=["class","style"],W=function(){return{prefixCls:String,spinning:{type:Boolean,default:void 0},size:String,wrapperClassName:String,tip:b.any,delay:Number,indicator:b.any}},d=null;function q(t,n){return!!t&&!!n&&!isNaN(Number(n))}function G(t){var n=t.indicator;d=typeof n=="function"?n:function(){return s(n,null,null)}}const H=w({compatConfig:{MODE:3},name:"ASpin",inheritAttrs:!1,props:z(W(),{size:"default",spinning:!0,wrapperClassName:""}),setup:function(){return{originalUpdateSpinning:null,configProvider:A("configProvider",j)}},data:function(){var n=this.spinning,e=this.delay,i=q(n,e);return{sSpinning:n&&!i}},created:function(){this.originalUpdateSpinning=this.updateSpinning,this.debouncifyUpdateSpinning(this.$props)},mounted:function(){this.updateSpinning()},updated:function(){var n=this;k(function(){n.debouncifyUpdateSpinning(),n.updateSpinning()})},beforeUnmount:function(){this.cancelExistingSpin()},methods:{debouncifyUpdateSpinning:function(n){var e=n||this.$props,i=e.delay;i&&(this.cancelExistingSpin(),this.updateSpinning=V(this.originalUpdateSpinning,i))},updateSpinning:function(){var n=this.spinning,e=this.sSpinning;e!==n&&(this.sSpinning=n)},cancelExistingSpin:function(){var n=this.updateSpinning;n&&n.cancel&&n.cancel()},renderIndicator:function(n){var e="".concat(n,"-dot"),i=B(this,"indicator");return i===null?null:(Array.isArray(i)&&(i=i.length===1?i[0]:i),y(i)?$(i,{class:e}):d&&y(d())?$(d(),{class:e}):s("span",{class:"".concat(e," ").concat(n,"-dot-spin")},[s("i",{class:"".concat(n,"-dot-item")},null),s("i",{class:"".concat(n,"-dot-item")},null),s("i",{class:"".concat(n,"-dot-item")},null),s("i",{class:"".concat(n,"-dot-item")},null)]))}},render:function(){var n,e,i,o=this.$props,f=o.size,x=o.prefixCls,h=o.tip,p=h===void 0?(n=(e=this.$slots).tip)===null||n===void 0?void 0:n.call(e):h,N=o.wrapperClassName,l=this.$attrs,v=l.class,_=l.style,C=T(l,O),S=this.configProvider,U=S.getPrefixCls,D=S.direction,a=U("spin",x),u=this.sSpinning,E=(i={},r(i,a,!0),r(i,"".concat(a,"-sm"),f==="small"),r(i,"".concat(a,"-lg"),f==="large"),r(i,"".concat(a,"-spinning"),u),r(i,"".concat(a,"-show-text"),!!p),r(i,"".concat(a,"-rtl"),D==="rtl"),r(i,v,!!v),i),m=s("div",P(P({},C),{},{style:_,class:E}),[this.renderIndicator(a),p?s("div",{class:"".concat(a,"-text")},[p]):null]),g=M(this);if(g&&g.length){var c,I=(c={},r(c,"".concat(a,"-container"),!0),r(c,"".concat(a,"-blur"),u),c);return s("div",{class:["".concat(a,"-nested-loading"),N]},[u&&s("div",{key:"loading"},[m]),s("div",{class:I,key:"container"},[g])])}return m}});export{H as S,G as s};

View File

@ -1 +0,0 @@
.ant-tag{box-sizing:border-box;margin:0 8px 0 0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";display:inline-block;height:auto;padding:0 7px;font-size:12px;line-height:20px;white-space:nowrap;background:#fafafa;border:1px solid #d9d9d9;border-radius:2px;opacity:1;transition:all .3s}.ant-tag,.ant-tag a,.ant-tag a:hover{color:#000000d9}.ant-tag>a:first-child:last-child{display:inline-block;margin:0 -8px;padding:0 8px}.ant-tag-close-icon{margin-left:3px;color:#00000073;font-size:10px;cursor:pointer;transition:all .3s}.ant-tag-close-icon:hover{color:#000000d9}.ant-tag-has-color{border-color:transparent}.ant-tag-has-color,.ant-tag-has-color a,.ant-tag-has-color a:hover,.ant-tag-has-color .anticon-close,.ant-tag-has-color .anticon-close:hover{color:#fff}.ant-tag-checkable{background-color:transparent;border-color:transparent;cursor:pointer}.ant-tag-checkable:not(.ant-tag-checkable-checked):hover{color:#d03f0a}.ant-tag-checkable:active,.ant-tag-checkable-checked{color:#fff}.ant-tag-checkable-checked{background-color:#d03f0a}.ant-tag-checkable:active{background-color:#ab2800}.ant-tag-hidden{display:none}.ant-tag-pink{color:#c41d7f;background:#fff0f6;border-color:#ffadd2}.ant-tag-pink-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-magenta{color:#c41d7f;background:#fff0f6;border-color:#ffadd2}.ant-tag-magenta-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-red{color:#cf1322;background:#fff1f0;border-color:#ffa39e}.ant-tag-red-inverse{color:#fff;background:#f5222d;border-color:#f5222d}.ant-tag-volcano{color:#d4380d;background:#fff2e8;border-color:#ffbb96}.ant-tag-volcano-inverse{color:#fff;background:#fa541c;border-color:#fa541c}.ant-tag-orange{color:#d46b08;background:#fff7e6;border-color:#ffd591}.ant-tag-orange-inverse{color:#fff;background:#fa8c16;border-color:#fa8c16}.ant-tag-yellow{color:#d4b106;background:#feffe6;border-color:#fffb8f}.ant-tag-yellow-inverse{color:#fff;background:#fadb14;border-color:#fadb14}.ant-tag-gold{color:#d48806;background:#fffbe6;border-color:#ffe58f}.ant-tag-gold-inverse{color:#fff;background:#faad14;border-color:#faad14}.ant-tag-cyan{color:#08979c;background:#e6fffb;border-color:#87e8de}.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}.ant-tag-lime{color:#7cb305;background:#fcffe6;border-color:#eaff8f}.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}.ant-tag-green{color:#389e0d;background:#f6ffed;border-color:#b7eb8f}.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}.ant-tag-blue{color:#096dd9;background:#e6f7ff;border-color:#91d5ff}.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}.ant-tag-geekblue{color:#1d39c4;background:#f0f5ff;border-color:#adc6ff}.ant-tag-geekblue-inverse{color:#fff;background:#2f54eb;border-color:#2f54eb}.ant-tag-purple{color:#531dab;background:#f9f0ff;border-color:#d3adf7}.ant-tag-purple-inverse{color:#fff;background:#722ed1;border-color:#722ed1}.ant-tag-success{color:#52c41a;background:#f6ffed;border-color:#b7eb8f}.ant-tag-processing{color:#d03f0a;background:#fff1e6;border-color:#f7ae83}.ant-tag-error{color:#ff4d4f;background:#fff2f0;border-color:#ffccc7}.ant-tag-warning{color:#faad14;background:#fffbe6;border-color:#ffe58f}.ant-tag>.anticon+span,.ant-tag>span+.anticon{margin-left:7px}.ant-tag.ant-tag-rtl{margin-right:0;margin-left:8px;direction:rtl;text-align:right}.ant-tag-rtl .ant-tag-close-icon{margin-right:3px;margin-left:0}.ant-tag-rtl.ant-tag>.anticon+span,.ant-tag-rtl.ant-tag>span+.anticon{margin-right:7px;margin-left:0}

View File

@ -1 +0,0 @@
import{d as $,r as x,as as g,cK as b,$ as w,o as p,k as i,l as a,c as r,y as d,m as u,t as n,n as B,H as I,z as m,ay as V,ag as _,ae as v,V as W,W as D,cL as L,q as N}from"./index-2865012e.js";/* empty css */const R={class:"container"},q={class:"actions"},F={class:"uni-desc"},K={class:"snapshot"},z=$({__name:"index",props:{tabIdx:{},paneIdx:{},id:{},paneKey:{}},setup(A){const h=x(),t=g(),f=e=>{h.tabList=V(e.tabs)},k=b(async e=>{await L(`workspace_snapshot_${e.id}`),t.snapshots=t.snapshots.filter(c=>c.id!==e.id),_.success(v("deleteSuccess"))}),o=w(""),y=async()=>{if(!o.value){_.error(v("nameRequired"));return}const e=t.createSnapshot(o.value);await t.addSnapshot(e),_.success(v("saveCompleted"))};return(e,c)=>{const C=W,l=D;return p(),i("div",R,[a("div",q,[r(C,{value:o.value,"onUpdate:value":c[0]||(c[0]=s=>o.value=s),placeholder:e.$t("name"),style:{"max-width":"300px"}},null,8,["value","placeholder"]),r(l,{type:"primary",onClick:y},{default:d(()=>[u(n(e.$t("saveWorkspaceSnapshot")),1)]),_:1})]),a("p",F,n(e.$t("WorkspaceSnapshotDesc")),1),a("ul",K,[(p(!0),i(B,null,I(m(t).snapshots,s=>(p(),i("li",{key:s.id},[a("div",null,[a("span",null,n(s.name),1)]),a("div",null,[r(l,{onClick:S=>f(s)},{default:d(()=>[u(n(e.$t("restore")),1)]),_:2},1032,["onClick"]),r(l,{onClick:S=>m(k)(s)},{default:d(()=>[u(n(e.$t("remove")),1)]),_:2},1032,["onClick"])])]))),128))])])}}});const H=N(z,[["__scopeId","data-v-2c44013c"]]);export{H as default};

1
vue/dist/assets/index-6d9e7c10.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{a9 as M,Y as l,ab as D,ac as P,d as F,u as I,$ as K,at as L,cp as _,b as y,ba as T,cq as A,a1 as $,h as o,c as B,a as G}from"./index-2865012e.js";import{u as V}from"./index-de079109.js";var E=Symbol("rowContextKey"),W=function(r){D(E,r)},q=function(){return M(E,{gutter:l(function(){}),wrap:l(function(){}),supportFlexGap:l(function(){})})};P("top","middle","bottom","stretch");P("start","end","center","space-around","space-between");var U=function(){return{align:String,justify:String,prefixCls:String,gutter:{type:[Number,Array,Object],default:0},wrap:{type:Boolean,default:void 0}}},Y=F({compatConfig:{MODE:3},name:"ARow",props:U(),setup:function(r,N){var g=N.slots,v=I("row",r),d=v.prefixCls,h=v.direction,j,b=K({xs:!0,sm:!0,md:!0,lg:!0,xl:!0,xxl:!0,xxxl:!0}),w=V();L(function(){j=_.subscribe(function(e){var t=r.gutter||0;(!Array.isArray(t)&&y(t)==="object"||Array.isArray(t)&&(y(t[0])==="object"||y(t[1])==="object"))&&(b.value=e)})}),T(function(){_.unsubscribe(j)});var S=l(function(){var e=[0,0],t=r.gutter,n=t===void 0?0:t,s=Array.isArray(n)?n:[n,0];return s.forEach(function(i,x){if(y(i)==="object")for(var a=0;a<A.length;a++){var p=A[a];if(b.value[p]&&i[p]!==void 0){e[x]=i[p];break}}else e[x]=i||0}),e});W({gutter:S,supportFlexGap:w,wrap:l(function(){return r.wrap})});var R=l(function(){var e;return $(d.value,(e={},o(e,"".concat(d.value,"-no-wrap"),r.wrap===!1),o(e,"".concat(d.value,"-").concat(r.justify),r.justify),o(e,"".concat(d.value,"-").concat(r.align),r.align),o(e,"".concat(d.value,"-rtl"),h.value==="rtl"),e))}),O=l(function(){var e=S.value,t={},n=e[0]>0?"".concat(e[0]/-2,"px"):void 0,s=e[1]>0?"".concat(e[1]/-2,"px"):void 0;return n&&(t.marginLeft=n,t.marginRight=n),w.value?t.rowGap="".concat(e[1],"px"):s&&(t.marginTop=s,t.marginBottom=s),t});return function(){var e;return B("div",{class:R.value,style:O.value},[(e=g.default)===null||e===void 0?void 0:e.call(g)])}}});const X=Y;function k(c){return typeof c=="number"?"".concat(c," ").concat(c," auto"):/^\d+(\.\d+)?(px|em|rem|%)$/.test(c)?"0 0 ".concat(c):c}var H=function(){return{span:[String,Number],order:[String,Number],offset:[String,Number],push:[String,Number],pull:[String,Number],xs:{type:[String,Number,Object],default:void 0},sm:{type:[String,Number,Object],default:void 0},md:{type:[String,Number,Object],default:void 0},lg:{type:[String,Number,Object],default:void 0},xl:{type:[String,Number,Object],default:void 0},xxl:{type:[String,Number,Object],default:void 0},xxxl:{type:[String,Number,Object],default:void 0},prefixCls:String,flex:[String,Number]}};const Z=F({compatConfig:{MODE:3},name:"ACol",props:H(),setup:function(r,N){var g=N.slots,v=q(),d=v.gutter,h=v.supportFlexGap,j=v.wrap,b=I("col",r),w=b.prefixCls,S=b.direction,R=l(function(){var e,t=r.span,n=r.order,s=r.offset,i=r.push,x=r.pull,a=w.value,p={};return["xs","sm","md","lg","xl","xxl","xxxl"].forEach(function(m){var f,u={},C=r[m];typeof C=="number"?u.span=C:y(C)==="object"&&(u=C||{}),p=G(G({},p),{},(f={},o(f,"".concat(a,"-").concat(m,"-").concat(u.span),u.span!==void 0),o(f,"".concat(a,"-").concat(m,"-order-").concat(u.order),u.order||u.order===0),o(f,"".concat(a,"-").concat(m,"-offset-").concat(u.offset),u.offset||u.offset===0),o(f,"".concat(a,"-").concat(m,"-push-").concat(u.push),u.push||u.push===0),o(f,"".concat(a,"-").concat(m,"-pull-").concat(u.pull),u.pull||u.pull===0),o(f,"".concat(a,"-rtl"),S.value==="rtl"),f))}),$(a,(e={},o(e,"".concat(a,"-").concat(t),t!==void 0),o(e,"".concat(a,"-order-").concat(n),n),o(e,"".concat(a,"-offset-").concat(s),s),o(e,"".concat(a,"-push-").concat(i),i),o(e,"".concat(a,"-pull-").concat(x),x),e),p)}),O=l(function(){var e=r.flex,t=d.value,n={};if(t&&t[0]>0){var s="".concat(t[0]/2,"px");n.paddingLeft=s,n.paddingRight=s}if(t&&t[1]>0&&!h.value){var i="".concat(t[1]/2,"px");n.paddingTop=i,n.paddingBottom=i}return e&&(n.flex=k(e),j.value===!1&&!n.minWidth&&(n.minWidth=0)),n});return function(){var e;return B("div",{class:R.value,style:O.value},[(e=g.default)===null||e===void 0?void 0:e.call(g)])}}});export{Z as C,X as R};

1
vue/dist/assets/index-7e587e4d.css vendored Normal file
View File

@ -0,0 +1 @@
.container[data-v-e55e3025]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column;padding:16px}.container .actions[data-v-e55e3025]{margin-bottom:16px}.container .actions *[data-v-e55e3025]{margin-right:10px}.snapshot[data-v-e55e3025]{list-style:none;padding:0;margin:0;width:512px}.snapshot li[data-v-e55e3025]{display:flex;justify-content:space-between;align-items:center;padding:10px;background-color:var(--zp-secondary-variant-background);border-radius:4px;margin-bottom:10px;transition:all .3s ease;border-bottom:2px solid var(--zp-luminous-deep)}.snapshot li[data-v-e55e3025]:hover{border-bottom:2px solid var(--zp-luminous)}.snapshot li div[data-v-e55e3025]:first-child{flex-grow:1;font-weight:700}.snapshot li div[data-v-e55e3025]:last-child{display:flex;gap:10px}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{co as j,ac as K,d as $,j as z,d8 as U,w,$ as b,Y as S,v as A,u as D,at as E,ad as H,h as d,c as s,a as C,aa as L,b5 as W,g as _,d9 as G,P as c,da as x}from"./index-2865012e.js";var R=K("small","default"),Y=function(){return{id:String,prefixCls:String,size:c.oneOf(R),disabled:{type:Boolean,default:void 0},checkedChildren:c.any,unCheckedChildren:c.any,tabindex:c.oneOfType([c.string,c.number]),autofocus:{type:Boolean,default:void 0},loading:{type:Boolean,default:void 0},checked:c.oneOfType([c.string,c.number,c.looseBool]),checkedValue:c.oneOfType([c.string,c.number,c.looseBool]).def(!0),unCheckedValue:c.oneOfType([c.string,c.number,c.looseBool]).def(!1),onChange:{type:Function},onClick:{type:Function},onKeydown:{type:Function},onMouseup:{type:Function},"onUpdate:checked":{type:Function},onBlur:Function,onFocus:Function}},q=$({compatConfig:{MODE:3},name:"ASwitch",__ANT_SWITCH:!0,inheritAttrs:!1,props:Y(),slots:["checkedChildren","unCheckedChildren"],setup:function(n,r){var o=r.attrs,y=r.slots,B=r.expose,l=r.emit,m=z();U(function(){w(!("defaultChecked"in o),"Switch","'defaultChecked' is deprecated, please use 'v-model:checked'"),w(!("value"in o),"Switch","`value` is not validate prop, do you mean `checked`?")});var h=b(n.checked!==void 0?n.checked:o.defaultChecked),f=S(function(){return h.value===n.checkedValue});A(function(){return n.checked},function(){h.value=n.checked});var v=D("switch",n),u=v.prefixCls,F=v.direction,T=v.size,i=b(),g=function(){var e;(e=i.value)===null||e===void 0||e.focus()},V=function(){var e;(e=i.value)===null||e===void 0||e.blur()};B({focus:g,blur:V}),E(function(){H(function(){n.autofocus&&!n.disabled&&i.value.focus()})});var k=function(e,t){n.disabled||(l("update:checked",e),l("change",e,t),m.onFieldChange())},I=function(e){l("blur",e)},N=function(e){g();var t=f.value?n.unCheckedValue:n.checkedValue;k(t,e),l("click",t,e)},M=function(e){e.keyCode===x.LEFT?k(n.unCheckedValue,e):e.keyCode===x.RIGHT&&k(n.checkedValue,e),l("keydown",e)},O=function(e){var t;(t=i.value)===null||t===void 0||t.blur(),l("mouseup",e)},P=S(function(){var a;return a={},d(a,"".concat(u.value,"-small"),T.value==="small"),d(a,"".concat(u.value,"-loading"),n.loading),d(a,"".concat(u.value,"-checked"),f.value),d(a,"".concat(u.value,"-disabled"),n.disabled),d(a,u.value,!0),d(a,"".concat(u.value,"-rtl"),F.value==="rtl"),a});return function(){var a;return s(G,{insertExtraNode:!0},{default:function(){return[s("button",C(C(C({},L(n,["prefixCls","checkedChildren","unCheckedChildren","checked","autofocus","checkedValue","unCheckedValue","id","onChange","onUpdate:checked"])),o),{},{id:(a=n.id)!==null&&a!==void 0?a:m.id.value,onKeydown:M,onClick:N,onBlur:I,onMouseup:O,type:"button",role:"switch","aria-checked":h.value,disabled:n.disabled||n.loading,class:[o.class,P.value],ref:i}),[s("div",{class:"".concat(u.value,"-handle")},[n.loading?s(W,{class:"".concat(u.value,"-loading-icon")},null):null]),s("span",{class:"".concat(u.value,"-inner")},[f.value?_(y,n,"checkedChildren"):_(y,n,"unCheckedChildren")])])]}})}}});const Q=j(q);export{Q as _};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.container[data-v-2c44013c]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column;padding:16px}.container .actions[data-v-2c44013c]{margin-bottom:16px}.container .actions *[data-v-2c44013c]{margin-right:10px}.snapshot[data-v-2c44013c]{list-style:none;padding:0;margin:0;width:512px}.snapshot li[data-v-2c44013c]{display:flex;justify-content:space-between;align-items:center;padding:10px;background-color:var(--zp-secondary-variant-background);border-radius:4px;margin-bottom:10px;transition:all .3s ease;border-bottom:2px solid var(--zp-luminous-deep)}.snapshot li[data-v-2c44013c]:hover{border-bottom:2px solid var(--zp-luminous)}.snapshot li div[data-v-2c44013c]:first-child{flex-grow:1;font-weight:700}.snapshot li div[data-v-2c44013c]:last-child{display:flex;gap:10px}

232
vue/dist/assets/index-c24b5c8e.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{d as F,u as S,Y as h,a1 as j,h as d,c as s,a4 as U,dx as W,$ as V,b7 as Y,n as q,d9 as z,P as N,c2 as G}from"./index-2865012e.js";var H=function(){return{prefixCls:String,checked:{type:Boolean,default:void 0},onChange:{type:Function},onClick:{type:Function},"onUpdate:checked":Function}},J=F({compatConfig:{MODE:3},name:"ACheckableTag",props:H(),setup:function(e,i){var l=i.slots,r=i.emit,g=S("tag",e),u=g.prefixCls,o=function(C){var v=e.checked;r("update:checked",!v),r("change",!v),r("click",C)},p=h(function(){var a;return j(u.value,(a={},d(a,"".concat(u.value,"-checkable"),!0),d(a,"".concat(u.value,"-checkable-checked"),e.checked),a))});return function(){var a;return s("span",{class:p.value,onClick:o},[(a=l.default)===null||a===void 0?void 0:a.call(l)])}}});const m=J;var K=new RegExp("^(".concat(U.join("|"),")(-inverse)?$")),L=new RegExp("^(".concat(W.join("|"),")$")),Q=function(){return{prefixCls:String,color:{type:String},closable:{type:Boolean,default:!1},closeIcon:N.any,visible:{type:Boolean,default:void 0},onClose:{type:Function},"onUpdate:visible":Function,icon:N.any}},f=F({compatConfig:{MODE:3},name:"ATag",props:Q(),slots:["closeIcon","icon"],setup:function(e,i){var l=i.slots,r=i.emit,g=i.attrs,u=S("tag",e),o=u.prefixCls,p=u.direction,a=V(!0);Y(function(){e.visible!==void 0&&(a.value=e.visible)});var C=function(t){t.stopPropagation(),r("update:visible",!1),r("close",t),!t.defaultPrevented&&e.visible===void 0&&(a.value=!1)},v=h(function(){var n=e.color;return n?K.test(n)||L.test(n):!1}),E=h(function(){var n;return j(o.value,(n={},d(n,"".concat(o.value,"-").concat(e.color),v.value),d(n,"".concat(o.value,"-has-color"),e.color&&!v.value),d(n,"".concat(o.value,"-hidden"),!a.value),d(n,"".concat(o.value,"-rtl"),p.value==="rtl"),n))});return function(){var n,t,k,b=e.icon,R=b===void 0?(n=l.icon)===null||n===void 0?void 0:n.call(l):b,y=e.color,_=e.closeIcon,P=_===void 0?(t=l.closeIcon)===null||t===void 0?void 0:t.call(l):_,x=e.closable,w=x===void 0?!1:x,B=function(){return w?P?s("span",{class:"".concat(o.value,"-close-icon"),onClick:C},[P]):s(G,{class:"".concat(o.value,"-close-icon"),onClick:C},null):null},O={backgroundColor:y&&!v.value?y:void 0},I=R||null,T=(k=l.default)===null||k===void 0?void 0:k.call(l),A=I?s(q,null,[I,s("span",null,[T])]):T,D="onClick"in g,$=s("span",{class:E.value,style:O},[A,B()]);return D?s(z,null,{default:function(){return[$]}}):$}}});f.CheckableTag=m;f.install=function(c){return c.component(f.name,f),c.component(m.name,m),c};const Z=f;export{Z as _};

View File

@ -1 +0,0 @@
import{$ as t,at as o,cE as a}from"./index-2865012e.js";const r=function(){var e=t(!1);return o(function(){e.value=a()}),e};export{r as u};

File diff suppressed because one or more lines are too long

1
vue/dist/assets/index-e9cfa018.js vendored Normal file
View File

@ -0,0 +1 @@
import{d as S,p as $,aj as g,c5 as b,r as w,o as p,j as d,k as a,c as l,x as i,l as u,t as n,F as B,G as I,y as m,ap as R,R as _,T as v,Z as D,$ as F,c6 as N,n as V}from"./index-c24b5c8e.js";const W={class:"container"},j={class:"actions"},G={class:"uni-desc"},L={class:"snapshot"},T=S({__name:"index",props:{tabIdx:{},paneIdx:{},id:{},paneKey:{}},setup(q){const h=$(),t=g(),f=e=>{h.tabList=R(e.tabs)},k=b(async e=>{await N(`workspace_snapshot_${e.id}`),t.snapshots=t.snapshots.filter(c=>c.id!==e.id),_.success(v("deleteSuccess"))}),o=w(""),y=async()=>{if(!o.value){_.error(v("nameRequired"));return}const e=t.createSnapshot(o.value);await t.addSnapshot(e),_.success(v("saveCompleted"))};return(e,c)=>{const C=D,r=F;return p(),d("div",W,[a("div",j,[l(C,{value:o.value,"onUpdate:value":c[0]||(c[0]=s=>o.value=s),placeholder:e.$t("name"),style:{"max-width":"300px"}},null,8,["value","placeholder"]),l(r,{type:"primary",onClick:y},{default:i(()=>[u(n(e.$t("saveWorkspaceSnapshot")),1)]),_:1})]),a("p",G,n(e.$t("WorkspaceSnapshotDesc")),1),a("ul",L,[(p(!0),d(B,null,I(m(t).snapshots,s=>(p(),d("li",{key:s.id},[a("div",null,[a("span",null,n(s.name),1)]),a("div",null,[l(r,{onClick:x=>f(s)},{default:i(()=>[u(n(e.$t("restore")),1)]),_:2},1032,["onClick"]),l(r,{onClick:x=>m(k)(s)},{default:i(()=>[u(n(e.$t("remove")),1)]),_:2},1032,["onClick"])])]))),128))])])}}});const E=V(T,[["__scopeId","data-v-e55e3025"]]);export{E as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{ce as t,cf as i,cg as r,ch as a,aT as c}from"./index-2865012e.js";function o(e,s){return t(i(e,s,r),e+"")}function b(e){return a(e)&&c(e)}export{o as b,b as i};

View File

Before

Width:  |  Height:  |  Size: 195 B

After

Width:  |  Height:  |  Size: 195 B

View File

@ -1 +0,0 @@
import{d as Z,r as ee,$ as N,aH as te,aI as le,at as ie,cM as se,o as v,k as F,c as i,z as e,l as g,y as n,m as k,t as u,B as R,E as oe,C as ae,O as ne,Q as $,p as A,x as re,ag as w,ae as ce,cN as de,W as ue,T as me,U as fe,q as pe}from"./index-2865012e.js";import{F as ve,s as ge}from"./FileItem-3bb21719.js";import{u as ke,g as we,c as he,b as Ce,d as Se,e as _e,o as z}from"./index-9ff6b146.js";import{M as Ie,L as ye,R as xe,f as be}from"./MultiSelectKeep-d96596db.js";import"./index-cc5bdcee.js";import"./index-f200274c.js";import"./index-50dada2e.js";import"./shortcut-7cf3ed76.js";import"./index-dfe4c7be.js";import"./_isIterateeCall-3cdb292e.js";import"./index-5aea14c7.js";/* empty css */import"./index-85cde8a2.js";const Me={class:"refresh-button"},Te={class:"hint"},Ve={key:0,class:"preview-switch"},Ne=Z({__name:"randomImage",props:{tabIdx:{},paneIdx:{},id:{},paneKey:{}},setup(Fe){const B=ee(),m=N(!1),l=N([]),r=l,h=te(`${le}randomImageSettingNotificationShown`,!1),P=()=>{h.value||(w.info({content:ce("randomImageSettingNotification"),duration:6,key:"randomImageSetting"}),h.value=!0)},f=async()=>{try{m.value=!0;const s=await de();s.length===0&&w.warn("No data, please generate index in image search page first"),l.value=s}finally{m.value=!1,_()}},C=()=>{if(l.value.length===0){w.warn("没有图片可以浏览");return}z(l.value,a.value||0)};ie(()=>{f(),setTimeout(()=>{P()},2e3)});const{stackViewEl:E,multiSelectedIdxs:p,stack:O,scroller:U}=ke({images:l}).toRefs(),{onClearAllSelected:D,onSelectAll:G,onReverseSelect:K}=we();he();const{itemSize:S,gridItems:L,cellWidth:W,onScroll:_}=Ce(),{showGenInfo:c,imageGenInfo:I,q,onContextMenuClick:H,onFileItemClick:Q}=Se({openNext:se}),{previewIdx:a,previewing:y,onPreviewVisibleChange:j,previewImgMove:x,canPreview:b}=_e(),M=async(s,t,d)=>{O.value=[{curr:"",files:l.value}],await H(s,t,d)};return(s,t)=>{var T;const d=ue,J=me,X=fe;return v(),F("div",{class:"container",ref_key:"stackViewEl",ref:E},[i(Ie,{show:!!e(p).length||e(B).keepMultiSelect,onClearAllSelected:e(D),onSelectAll:e(G),onReverseSelect:e(K)},null,8,["show","onClearAllSelected","onSelectAll","onReverseSelect"]),g("div",Me,[i(d,{onClick:f,onTouchstart:R(f,["prevent"]),type:"primary",loading:m.value,shape:"round"},{default:n(()=>[k(u(s.$t("shuffle")),1)]),_:1},8,["onTouchstart","loading"]),i(d,{onClick:C,onTouchstart:R(C,["prevent"]),type:"default",disabled:!((T=l.value)!=null&&T.length),shape:"round"},{default:n(()=>[k(u(s.$t("tiktokView")),1)]),_:1},8,["onTouchstart","disabled"])]),i(X,{visible:e(c),"onUpdate:visible":t[1]||(t[1]=o=>ae(c)?c.value=o:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=o=>c.value=!1)},{cancelText:n(()=>[]),default:n(()=>[i(J,{active:"",loading:!e(q).isIdle},{default:n(()=>[g("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=o=>e(oe)(e(I)))},[g("div",Te,u(s.$t("doubleClickToCopy")),1),k(" "+u(e(I)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),i(e(ge),{ref_key:"scroller",ref:U,class:"file-list",items:l.value.slice(),"item-size":e(S).first,"key-field":"fullpath","item-secondary-size":e(S).second,gridItems:e(L),onScroll:e(_)},{default:n(({item:o,index:V})=>[i(ve,{idx:V,file:o,"cell-width":e(W),"full-screen-preview-image-url":e(r)[e(a)]?e(ne)(e(r)[e(a)]):"",onContextMenuClick:M,onPreviewVisibleChange:e(j),"is-selected-mutil-files":e(p).length>1,selected:e(p).includes(V),onFileItemClick:e(Q),onTiktokView:(Re,Y)=>e(z)(l.value,Y)},null,8,["idx","file","cell-width","full-screen-preview-image-url","onPreviewVisibleChange","is-selected-mutil-files","selected","onFileItemClick","onTiktokView"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"]),e(y)?(v(),F("div",Ve,[i(e(ye),{onClick:t[3]||(t[3]=o=>e(x)("prev")),class:$({disable:!e(b)("prev")})},null,8,["class"]),i(e(xe),{onClick:t[4]||(t[4]=o=>e(x)("next")),class:$({disable:!e(b)("next")})},null,8,["class"])])):A("",!0),e(y)&&e(r)&&e(r)[e(a)]?(v(),re(be,{key:1,file:e(r)[e(a)],idx:e(a),onContextMenuClick:M},null,8,["file","idx"])):A("",!0)],512)}}});const qe=pe(Ne,[["__scopeId","data-v-49082269"]]);export{qe as default};

View File

@ -0,0 +1 @@
import{d as Z,p as ee,r as R,az as te,aA as le,ak as se,c7 as ie,o as v,j as A,c as s,y as e,k as g,x as a,l as k,t as u,z as F,C as oe,B as ne,O as ae,Q as N,m as $,v as re,R as w,T as ce,c8 as de,$ as ue,Y as me,V as fe,n as pe}from"./index-c24b5c8e.js";import{F as ve,s as ge}from"./FileItem-c62b10f3.js";import{u as ke,g as we,c as he,b as Ce,d as Se,e as _e,o as z}from"./index-47a4d52c.js";import{M as Ie,L as ye,R as xe,f as be}from"./MultiSelectKeep-ea31915e.js";import"./index-5130babf.js";import"./shortcut-4e374057.js";import"./_isIterateeCall-19da1ec8.js";/* empty css */const Ve={class:"refresh-button"},Me={class:"hint"},Te={key:0,class:"preview-switch"},Re=Z({__name:"randomImage",props:{tabIdx:{},paneIdx:{},id:{},paneKey:{}},setup(Ae){const B=ee(),m=R(!1),l=R([]),r=l,h=te(`${le}randomImageSettingNotificationShown`,!1),P=()=>{h.value||(w.info({content:ce("randomImageSettingNotification"),duration:6,key:"randomImageSetting"}),h.value=!0)},f=async()=>{try{m.value=!0;const i=await de();i.length===0&&w.warn("No data, please generate index in image search page first"),l.value=i}finally{m.value=!1,_()}},C=()=>{if(l.value.length===0){w.warn("没有图片可以浏览");return}z(l.value,n.value||0)};se(()=>{f(),setTimeout(()=>{P()},2e3)});const{stackViewEl:O,multiSelectedIdxs:p,stack:D,scroller:E}=ke({images:l}).toRefs(),{onClearAllSelected:G,onSelectAll:K,onReverseSelect:L}=we();he();const{itemSize:S,gridItems:U,cellWidth:Q,onScroll:_}=Ce(),{showGenInfo:c,imageGenInfo:I,q:W,onContextMenuClick:j,onFileItemClick:q}=Se({openNext:ie}),{previewIdx:n,previewing:y,onPreviewVisibleChange:H,previewImgMove:x,canPreview:b}=_e(),V=async(i,t,d)=>{D.value=[{curr:"",files:l.value}],await j(i,t,d)};return(i,t)=>{var M;const d=ue,Y=me,J=fe;return v(),A("div",{class:"container",ref_key:"stackViewEl",ref:O},[s(Ie,{show:!!e(p).length||e(B).keepMultiSelect,onClearAllSelected:e(G),onSelectAll:e(K),onReverseSelect:e(L)},null,8,["show","onClearAllSelected","onSelectAll","onReverseSelect"]),g("div",Ve,[s(d,{onClick:f,onTouchstart:F(f,["prevent"]),type:"primary",loading:m.value,shape:"round"},{default:a(()=>[k(u(i.$t("shuffle")),1)]),_:1},8,["onTouchstart","loading"]),s(d,{onClick:C,onTouchstart:F(C,["prevent"]),type:"default",disabled:!((M=l.value)!=null&&M.length),shape:"round"},{default:a(()=>[k(u(i.$t("tiktokView")),1)]),_:1},8,["onTouchstart","disabled"])]),s(J,{visible:e(c),"onUpdate:visible":t[1]||(t[1]=o=>ne(c)?c.value=o:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=o=>c.value=!1)},{cancelText:a(()=>[]),default:a(()=>[s(Y,{active:"",loading:!e(W).isIdle},{default:a(()=>[g("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=o=>e(oe)(e(I)))},[g("div",Me,u(i.$t("doubleClickToCopy")),1),k(" "+u(e(I)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),s(e(ge),{ref_key:"scroller",ref:E,class:"file-list",items:l.value.slice(),"item-size":e(S).first,"key-field":"fullpath","item-secondary-size":e(S).second,gridItems:e(U),onScroll:e(_)},{default:a(({item:o,index:T})=>[s(ve,{idx:T,file:o,"cell-width":e(Q),"full-screen-preview-image-url":e(r)[e(n)]?e(ae)(e(r)[e(n)]):"",onContextMenuClick:V,onPreviewVisibleChange:e(H),"is-selected-mutil-files":e(p).length>1,selected:e(p).includes(T),onFileItemClick:e(q),onTiktokView:(Fe,X)=>e(z)(l.value,X)},null,8,["idx","file","cell-width","full-screen-preview-image-url","onPreviewVisibleChange","is-selected-mutil-files","selected","onFileItemClick","onTiktokView"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"]),e(y)?(v(),A("div",Te,[s(e(ye),{onClick:t[3]||(t[3]=o=>e(x)("prev")),class:N({disable:!e(b)("prev")})},null,8,["class"]),s(e(xe),{onClick:t[4]||(t[4]=o=>e(x)("next")),class:N({disable:!e(b)("next")})},null,8,["class"])])):$("",!0),e(y)&&e(r)&&e(r)[e(n)]?(v(),re(be,{key:1,file:e(r)[e(n)],idx:e(n),onContextMenuClick:V},null,8,["file","idx"])):$("",!0)],512)}}});const Ge=pe(Re,[["__scopeId","data-v-e1531e89"]]);export{Ge as default};

View File

@ -1 +1 @@
.container[data-v-49082269]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-49082269]{padding:8px;background-color:var(--zp-primary-background)}.container .refresh-button[data-v-49082269]{position:absolute;top:90%;left:50%;transform:translate(-50%,-50%);z-index:99;background:white;border-radius:9999px;box-shadow:0 0 20px var(--zp-secondary);padding:4px;display:flex;align-items:center;gap:8px}.container .file-list[data-v-49082269]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-49082269]{text-align:center;font-size:2em;padding:30vh 128px 0} .container[data-v-e1531e89]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-e1531e89]{padding:8px;background-color:var(--zp-primary-background)}.container .refresh-button[data-v-e1531e89]{position:absolute;top:90%;left:50%;transform:translate(-50%,-50%);z-index:99;background:white;border-radius:9999px;box-shadow:0 0 20px var(--zp-secondary);padding:4px;display:flex;align-items:center;gap:8px}.container .file-list[data-v-e1531e89]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-e1531e89]{text-align:center;font-size:2em;padding:30vh 128px 0}

View File

@ -1 +0,0 @@
import{R as y,C as v}from"./index-6db1bae6.js";import{co as f,c as d,A as w,d as P,o as a,k as c,l as r,n as S,H as O,an as V,y as b,m as u,t as p,z as R,W as $,q as H,aw as x,aH as _,aI as m}from"./index-2865012e.js";const A=f(y),q=f(v);var L={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M878.3 392.1L631.9 145.7c-6.5-6.5-15-9.7-23.5-9.7s-17 3.2-23.5 9.7L423.8 306.9c-12.2-1.4-24.5-2-36.8-2-73.2 0-146.4 24.1-206.5 72.3-15.4 12.3-16.6 35.4-2.7 49.4l181.7 181.7-215.4 215.2a15.8 15.8 0 00-4.6 9.8l-3.4 37.2c-.9 9.4 6.6 17.4 15.9 17.4.5 0 1 0 1.5-.1l37.2-3.4c3.7-.3 7.2-2 9.8-4.6l215.4-215.4 181.7 181.7c6.5 6.5 15 9.7 23.5 9.7 9.7 0 19.3-4.2 25.9-12.4 56.3-70.3 79.7-158.3 70.2-243.4l161.1-161.1c12.9-12.8 12.9-33.8 0-46.8z"}}]},name:"pushpin",theme:"filled"};const z=L;function h(t){for(var e=1;e<arguments.length;e++){var s=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(s);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(s).filter(function(i){return Object.getOwnPropertyDescriptor(s,i).enumerable}))),n.forEach(function(i){C(t,i,s[i])})}return t}function C(t,e,s){return e in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}var l=function(e,s){var n=h({},e,s.attrs);return d(w,h({},n,{icon:z}),null)};l.displayName="PushpinFilled";l.inheritAttrs=!1;const N=l,F={class:"record-container"},I={style:{flex:"1"}},k={class:"rec-actions"},B=["onClick"],j=P({__name:"HistoryRecord",props:{records:{}},emits:["reuseRecord"],setup(t){return(e,s)=>{const n=$;return a(),c("div",null,[r("ul",F,[(a(!0),c(S,null,O(e.records.getRecords(),i=>(a(),c("li",{key:i.id,class:"record"},[r("div",I,[V(e.$slots,"default",{record:i},void 0,!0)]),r("div",k,[d(n,{onClick:g=>e.$emit("reuseRecord",i),type:"primary"},{default:b(()=>[u(p(e.$t("restore")),1)]),_:2},1032,["onClick"]),r("div",{class:"pin",onClick:g=>e.records.switchPin(i)},[d(R(N)),u(" "+p(e.records.isPinned(i)?e.$t("unpin"):e.$t("pin")),1)],8,B)])]))),128))])])}}});const E=H(j,[["__scopeId","data-v-834a248f"]]);class o{constructor(e=128,s=[],n=[]){this.maxLength=e,this.records=s,this.pinnedValues=n}isPinned(e){return this.pinnedValues.some(s=>s.id===e.id)}add(e){this.records.length>=this.maxLength&&this.records.pop(),this.records.unshift({...e,id:x()+Date.now(),time:new Date().toLocaleString()})}pin(e){const s=this.records.findIndex(n=>n.id===e.id);s!==-1&&this.records.splice(s,1),this.pinnedValues.push(e)}unpin(e){const s=this.pinnedValues.findIndex(n=>n.id===e.id);s!==-1&&this.pinnedValues.splice(s,1),this.records.unshift(e)}switchPin(e){this.isPinned(e)?this.unpin(e):this.pin(e)}getRecords(){return[...this.pinnedValues,...this.records]}getPinnedValues(){return this.pinnedValues}}const M=_(`${m}fuzzy-search-HistoryRecord`,new o,{serializer:{read:t=>{const e=JSON.parse(t);return new o(e.maxLength,e.records,e.pinnedValues)},write:JSON.stringify}}),T=_(`${m}tag-search-HistoryRecord`,new o,{serializer:{read:t=>{const e=JSON.parse(t);return new o(e.maxLength,e.records,e.pinnedValues)},write:JSON.stringify}});export{E as H,q as _,A as a,M as f,T as t};

View File

@ -0,0 +1 @@
[data-v-fff181dd] .ant-row .ant-col:nth-child(1){font-weight:700}.record-container[data-v-fff181dd]{list-style:none;padding:8px;margin:16px;max-height:50vh;overflow:auto}.record[data-v-fff181dd]{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:10px;border-bottom:1px solid var(--zp-tertiary);position:relative;flex-wrap:nowrap;transition:all .3s ease}.record[data-v-fff181dd]:hover{background:var(--zp-secondary-background)}.record .rec-actions[data-v-fff181dd]{user-select:none;display:flex;gap:8px}.record .pin[data-v-fff181dd]{cursor:pointer;padding:4px 8px;border-radius:4px;transition:all .3s ease}.record .pin[data-v-fff181dd]:hover{background:var(--zp-primary-background)}

View File

@ -1 +0,0 @@
[data-v-834a248f] .ant-row .ant-col:nth-child(1){font-weight:700}.record-container[data-v-834a248f]{list-style:none;padding:8px;margin:16px;max-height:50vh;overflow:auto}.record[data-v-834a248f]{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding:10px;border-bottom:1px solid var(--zp-tertiary);position:relative;flex-wrap:nowrap;transition:all .3s ease}.record[data-v-834a248f]:hover{background:var(--zp-secondary-background)}.record .rec-actions[data-v-834a248f]{user-select:none;display:flex;gap:8px}.record .pin[data-v-834a248f]{cursor:pointer;padding:4px 8px;border-radius:4px;transition:all .3s ease}.record .pin[data-v-834a248f]:hover{background:var(--zp-primary-background)}

View File

@ -0,0 +1 @@
import{bH as f,bI as y,bJ as v,c as d,A as P,d as w,o as a,j as c,k as r,F as b,G as S,ad as O,x as V,l as u,t as p,y as $,$ as R,n as x,an as H,az as _,aA as g}from"./index-c24b5c8e.js";const A=f(y),D=f(v);var L={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M878.3 392.1L631.9 145.7c-6.5-6.5-15-9.7-23.5-9.7s-17 3.2-23.5 9.7L423.8 306.9c-12.2-1.4-24.5-2-36.8-2-73.2 0-146.4 24.1-206.5 72.3-15.4 12.3-16.6 35.4-2.7 49.4l181.7 181.7-215.4 215.2a15.8 15.8 0 00-4.6 9.8l-3.4 37.2c-.9 9.4 6.6 17.4 15.9 17.4.5 0 1 0 1.5-.1l37.2-3.4c3.7-.3 7.2-2 9.8-4.6l215.4-215.4 181.7 181.7c6.5 6.5 15 9.7 23.5 9.7 9.7 0 19.3-4.2 25.9-12.4 56.3-70.3 79.7-158.3 70.2-243.4l161.1-161.1c12.9-12.8 12.9-33.8 0-46.8z"}}]},name:"pushpin",theme:"filled"};const z=L;function h(t){for(var e=1;e<arguments.length;e++){var s=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(s);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(s).filter(function(i){return Object.getOwnPropertyDescriptor(s,i).enumerable}))),n.forEach(function(i){F(t,i,s[i])})}return t}function F(t,e,s){return e in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}var l=function(e,s){var n=h({},e,s.attrs);return d(P,h({},n,{icon:z}),null)};l.displayName="PushpinFilled";l.inheritAttrs=!1;const N=l,C={class:"record-container"},I={style:{flex:"1"}},k={class:"rec-actions"},j=["onClick"],B=w({__name:"HistoryRecord",props:{records:{}},emits:["reuseRecord"],setup(t){return(e,s)=>{const n=R;return a(),c("div",null,[r("ul",C,[(a(!0),c(b,null,S(e.records.getRecords(),i=>(a(),c("li",{key:i.id,class:"record"},[r("div",I,[O(e.$slots,"default",{record:i},void 0,!0)]),r("div",k,[d(n,{onClick:m=>e.$emit("reuseRecord",i),type:"primary"},{default:V(()=>[u(p(e.$t("restore")),1)]),_:2},1032,["onClick"]),r("div",{class:"pin",onClick:m=>e.records.switchPin(i)},[d($(N)),u(" "+p(e.records.isPinned(i)?e.$t("unpin"):e.$t("pin")),1)],8,j)])]))),128))])])}}});const E=x(B,[["__scopeId","data-v-fff181dd"]]);class o{constructor(e=128,s=[],n=[]){this.maxLength=e,this.records=s,this.pinnedValues=n}isPinned(e){return this.pinnedValues.some(s=>s.id===e.id)}add(e){this.records.length>=this.maxLength&&this.records.pop(),this.records.unshift({...e,id:H()+Date.now(),time:new Date().toLocaleString()})}pin(e){const s=this.records.findIndex(n=>n.id===e.id);s!==-1&&this.records.splice(s,1),this.pinnedValues.push(e)}unpin(e){const s=this.pinnedValues.findIndex(n=>n.id===e.id);s!==-1&&this.pinnedValues.splice(s,1),this.records.unshift(e)}switchPin(e){this.isPinned(e)?this.unpin(e):this.pin(e)}getRecords(){return[...this.pinnedValues,...this.records]}getPinnedValues(){return this.pinnedValues}}const q=_(`${g}fuzzy-search-HistoryRecord`,new o,{serializer:{read:t=>{const e=JSON.parse(t);return new o(e.maxLength,e.records,e.pinnedValues)},write:JSON.stringify}}),G=_(`${g}tag-search-HistoryRecord`,new o,{serializer:{read:t=>{const e=JSON.parse(t);return new o(e.maxLength,e.records,e.pinnedValues)},write:JSON.stringify}});export{E as H,D as _,A as a,q as f,G as t};

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