Merge pull request #917 from zanllp/feat/smart-organize
feat: add smart organize feature with AI-powered file organizationpull/918/head
commit
f6b185e8a8
37
README-zh.md
37
README-zh.md
|
|
@ -1,6 +1,6 @@
|
|||
> 🌐 在线体验: http://39.105.110.128:0721 , 这是我一个空闲的2c2g3m的云主机没有cdn
|
||||
>
|
||||
# Stable-Diffusion-WebUI无边图像浏览
|
||||
# 无边图像浏览
|
||||
|
||||
|
||||
[查看近期更新](https://github.com/zanllp/sd-webui-infinite-image-browsing/wiki/Change-log)
|
||||
|
|
@ -69,6 +69,27 @@
|
|||
### 🧠 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) 并提交相关的代码。
|
||||
|
|
@ -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">
|
||||
|
||||
## 智能整理
|
||||
|
||||
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 的召回阶段)。
|
||||
|
|
|
|||
37
README.md
37
README.md
|
|
@ -7,7 +7,7 @@
|
|||
[Installation / Running](#installation--running)
|
||||
|
||||
|
||||
# Stable Diffusion webui Infinite Image Browsing
|
||||
# Infinite Image Browsing (IIB)
|
||||
|
||||
### Software Support and Development Progress Overview
|
||||
| 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
|
||||
- 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
|
||||
- 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.
|
||||
|
|
@ -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">
|
||||
|
||||
## 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)
|
||||
|
||||
This feature groups images by **semantic similarity of prompts** and supports **natural-language retrieval** (similar to the retrieval stage in RAG).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,43 @@
|
|||
[跳到中文](#中文)
|
||||
# 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
|
||||
### Drag-and-drop into folders and safer move/copy
|
||||
- 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
|
||||
### 支持拖拽到文件夹与更安全的移动/复制
|
||||
- 支持拖拽文件到文件夹,并修复右侧打开文件夹与界面细节。
|
||||
|
|
|
|||
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 |
|
|
@ -13,8 +13,8 @@ Promise.resolve().then(async () => {
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Infinite Image Browsing</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-2865012e.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-1293e61d.css">
|
||||
<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-6d9e7c10.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -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.topic_cluster import mount_topic_cluster_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.seq import seq
|
||||
import urllib.parse
|
||||
|
|
@ -942,6 +943,111 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
check_path_trust(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(
|
||||
api_base + "/batch_top_4_media_info",
|
||||
|
|
@ -1281,7 +1387,7 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
|
||||
|
||||
# ===== 主题聚类 / Embedding(拆分到独立模块,减少 api.py 体积)=====
|
||||
mount_topic_cluster_routes(
|
||||
topic_cluster_funcs = mount_topic_cluster_routes(
|
||||
app=app,
|
||||
db_api_base=db_api_base,
|
||||
verify_secret=verify_secret,
|
||||
|
|
@ -1303,6 +1409,16 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
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):
|
||||
path: str
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -896,10 +896,11 @@ def accumulate_streaming_response(resp: requests.Response) -> str:
|
|||
delta = (obj.get('choices') or [{}])[0].get('delta') or {}
|
||||
chunk_text = delta.get('content') or ''
|
||||
if chunk_text:
|
||||
# try:
|
||||
# print(f"[streaming] chunk_received len={len(chunk_text)} snippet={chunk_text[:200]}")
|
||||
# except Exception:
|
||||
# pass
|
||||
try:
|
||||
if is_dev:
|
||||
print(f"[streaming] chunk_received len={len(chunk_text)} snippet={chunk_text[:200]}")
|
||||
except Exception:
|
||||
pass
|
||||
content_buffer += chunk_text
|
||||
|
||||
return content_buffer.strip()
|
||||
|
|
@ -157,6 +157,12 @@ def _job_get(job_id: str) -> Optional[Dict]:
|
|||
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:
|
||||
with _TOPIC_CLUSTER_JOBS_LOCK:
|
||||
cur = _TOPIC_CLUSTER_JOBS.get(job_id)
|
||||
|
|
@ -934,6 +940,7 @@ def mount_topic_cluster_routes(
|
|||
force: bool,
|
||||
batch_size: int,
|
||||
max_chars: int,
|
||||
recursive: bool = True,
|
||||
progress_cb: Optional[Callable[[Dict], None]] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
|
|
@ -941,10 +948,13 @@ def mount_topic_cluster_routes(
|
|||
Progress payload (best-effort):
|
||||
- stage: "embedding"
|
||||
- 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] folder=%s model=%s force=%s batch_size=%s max_chars=%s",
|
||||
folder, model, force, batch_size, max_chars)
|
||||
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, recursive)
|
||||
|
||||
if not openai_api_key:
|
||||
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,))
|
||||
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 = []
|
||||
for image_id, path, exif in rows:
|
||||
if not isinstance(path, str) or not os.path.exists(path):
|
||||
|
|
@ -1165,6 +1183,8 @@ def mount_topic_cluster_routes(
|
|||
force: Optional[bool] = False
|
||||
batch_size: Optional[int] = 64
|
||||
max_chars: Optional[int] = 4000
|
||||
# If True, recursively scan subfolders; default True for backward compatibility
|
||||
recursive: Optional[bool] = True
|
||||
|
||||
@app.post(
|
||||
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))
|
||||
max_chars = max(256, min(int(req.max_chars or 4000), 8000))
|
||||
force = bool(req.force)
|
||||
recursive = bool(req.recursive) if req.recursive is not None else True
|
||||
return await _build_embeddings_one_folder(
|
||||
folder=folder,
|
||||
model=model,
|
||||
force=force,
|
||||
batch_size=batch_size,
|
||||
max_chars=max_chars,
|
||||
recursive=recursive,
|
||||
progress_cb=None,
|
||||
)
|
||||
|
||||
|
|
@ -1210,6 +1232,8 @@ def mount_topic_cluster_routes(
|
|||
force_title: Optional[bool] = False
|
||||
# Output language for titles/keywords (from frontend globalStore.lang)
|
||||
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:
|
||||
"""
|
||||
|
|
@ -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)
|
||||
|
||||
recursive = bool(req.recursive) if req.recursive is not None else False
|
||||
logger.info("[cluster_after] recursive=%s", recursive)
|
||||
|
||||
if progress_cb:
|
||||
logger.info("[cluster_after] Calling progress callback with clustering stage")
|
||||
progress_cb({"stage": "clustering", "folder": folder, "folders": folders})
|
||||
|
|
@ -1426,6 +1453,17 @@ def mount_topic_cluster_routes(
|
|||
)
|
||||
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 = []
|
||||
for n, (image_id, path, exif, vec_blob) in enumerate(rows):
|
||||
if not isinstance(path, str) or not os.path.exists(path):
|
||||
|
|
@ -1608,31 +1646,50 @@ def mount_topic_cluster_routes(
|
|||
sorted_keywords = sorted(keyword_frequency.items(), key=lambda x: x[1], reverse=True)
|
||||
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):
|
||||
if len(c["members"]) < min_cluster_size:
|
||||
for mi in c["members"]:
|
||||
noise.append(items[mi]["path"])
|
||||
continue
|
||||
else:
|
||||
member_items = [items[mi] for mi in c["members"]]
|
||||
paths = [x["path"] for x in member_items]
|
||||
texts = [x.get("text") or "" for x in member_items]
|
||||
member_ids = [x["id"] for x in member_items]
|
||||
rep = (c.get("sample_text") or (texts[0] if texts else "")).strip()
|
||||
cluster_hash = _cluster_sig(
|
||||
member_ids=member_ids,
|
||||
model=model,
|
||||
threshold=threshold,
|
||||
min_cluster_size=min_cluster_size,
|
||||
title_model=title_model,
|
||||
lang=output_lang,
|
||||
)
|
||||
clusters_to_title.append({
|
||||
"cidx": cidx,
|
||||
"paths": paths,
|
||||
"texts": texts,
|
||||
"rep": rep,
|
||||
"cluster_hash": cluster_hash,
|
||||
})
|
||||
|
||||
member_items = [items[mi] for mi in c["members"]]
|
||||
paths = [x["path"] for x in member_items]
|
||||
texts = [x.get("text") or "" for x in member_items]
|
||||
member_ids = [x["id"] for x in member_items]
|
||||
# Process LLM titles concurrently in batches
|
||||
LLM_CONCURRENCY = 5 # Number of concurrent LLM requests
|
||||
completed_count = [0] # Use list for closure mutation
|
||||
|
||||
# Representative prompt for LLM title generation
|
||||
rep = (c.get("sample_text") or (texts[0] if texts else "")).strip()
|
||||
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
|
||||
cluster_hash = _cluster_sig(
|
||||
member_ids=member_ids,
|
||||
model=model,
|
||||
threshold=threshold,
|
||||
min_cluster_size=min_cluster_size,
|
||||
title_model=title_model,
|
||||
lang=output_lang,
|
||||
)
|
||||
if use_title_cache and (not force_title):
|
||||
cached = TopicTitleCache.get(conn, cluster_hash)
|
||||
|
||||
if cached and isinstance(cached, dict) and cached.get("title"):
|
||||
title = str(cached.get("title"))
|
||||
keywords = cached.get("keywords") or []
|
||||
|
|
@ -1657,23 +1714,37 @@ def mount_topic_cluster_routes(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
for kw in keywords or []:
|
||||
keyword_frequency[kw] = keyword_frequency.get(kw, 0) + 1
|
||||
# Update progress
|
||||
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(
|
||||
{
|
||||
"id": f"topic_{cidx}",
|
||||
"title": title,
|
||||
"keywords": keywords,
|
||||
"size": len(paths),
|
||||
"paths": paths,
|
||||
"sample_prompt": _clean_for_title(rep)[:200],
|
||||
}
|
||||
)
|
||||
if (cidx + 1) % 6 == 0:
|
||||
if progress_cb:
|
||||
progress_cb({"stage": "titling", "clusters_total": len(clusters), "clusters_done": cidx + 1})
|
||||
await asyncio.sleep(0)
|
||||
return {
|
||||
"id": f"topic_{cidx}",
|
||||
"title": title,
|
||||
"keywords": keywords,
|
||||
"size": len(paths),
|
||||
"paths": paths,
|
||||
"sample_prompt": _clean_for_title(rep)[:200],
|
||||
}
|
||||
|
||||
# Use semaphore to limit concurrency
|
||||
semaphore = asyncio.Semaphore(LLM_CONCURRENCY)
|
||||
|
||||
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)
|
||||
return {
|
||||
|
|
@ -1862,4 +1933,44 @@ def mount_topic_cluster_routes(
|
|||
# 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.
|
||||
|
||||
# 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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ declare module '@vue/runtime-core' {
|
|||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
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']
|
||||
MultiSelectKeep: typeof import('./src/components/MultiSelectKeep.vue')['default']
|
||||
NumInput: typeof import('./src/components/numInput.vue')['default']
|
||||
OrganizeJobsPanel: typeof import('./src/components/OrganizeJobsPanel.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
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
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
|
|
@ -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};
|
||||
|
|
@ -0,0 +1 @@
|
|||
.img-sli-container[data-v-ec71de83]{position:relative;overflow-y:auto;height:calc(100vh - 40px)}
|
||||
|
|
@ -1 +0,0 @@
|
|||
.img-sli-container[data-v-ae3fb9a8]{position:relative;overflow-y:auto;height:calc(100vh - 40px)}
|
||||
|
|
@ -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
|
|
@ -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}
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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};
|
||||
|
|
@ -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};
|
||||
|
|
@ -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};
|
||||
|
|
@ -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}
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
|
@ -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};
|
||||
|
|
@ -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}
|
||||
|
|
@ -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};
|
||||
|
|
@ -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}
|
||||
|
|
@ -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};
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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)}}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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};
|
||||
|
|
@ -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}
|
||||
|
|
@ -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};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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};
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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 _};
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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};
|
||||
|
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 195 B |
|
|
@ -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};
|
||||
|
|
@ -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};
|
||||
|
|
@ -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}
|
||||
|
|
@ -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};
|
||||
|
|
@ -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)}
|
||||
|
|
@ -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)}
|
||||
|
|
@ -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
Loading…
Reference in New Issue