百度云独立出去
parent
f9867e5a64
commit
21668a1dfe
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
# Stable-Diffusion-WebUI无边图像浏览
|
||||
|
||||
高性能的图片(文件)浏览器😋。它适合在所有地方使用,针对云端还做了优化,你可以使用缩略图进行更快的预览和使用自带百度云进行文件传输。
|
||||
高性能的图片(文件)浏览器😋。它适合在所有地方使用,针对云端还做了优化,你可以使用缩略图进行更快的预览。
|
||||
|
||||
如果您对该项目有任何疑问或建议,请在GitHub上提交issue,或者看下最下面的FAQ部分。
|
||||
|
||||
> 百度云部分已独立,如果你有需要请[点此单独安装](https://github.com/zanllp/sd-webui-baidu-netdisk)
|
||||
## 主要特性
|
||||
|
||||
- 类chrome,vscode的多标签页多窗格。自由拖拽创建,同时预览多个文件夹,在多窗格之间移动文件
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-6eb64f08.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-9ee28aec.css">
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-8bb7713e.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-92aac6a6.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
238
scripts/api.py
238
scripts/api.py
|
|
@ -1,22 +1,19 @@
|
|||
from datetime import datetime, timedelta
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from scripts.tool import (
|
||||
human_readable_size,
|
||||
is_valid_image_path,
|
||||
temp_path,
|
||||
read_info_from_image,
|
||||
get_modified_date
|
||||
get_modified_date,
|
||||
is_win,
|
||||
cwd
|
||||
)
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import re
|
||||
import subprocess
|
||||
import asyncio
|
||||
import subprocess
|
||||
from typing import Any, List, Literal, Optional, Union
|
||||
from scripts.baiduyun_task import BaiduyunTask
|
||||
from typing import Any, List, Literal, Optional
|
||||
from pydantic import BaseModel
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from PIL import Image
|
||||
|
|
@ -25,118 +22,10 @@ import hashlib
|
|||
from urllib.parse import urlencode
|
||||
from scripts.db.datamodel import DataBase, Image as DbImg, Tag, Floder, ImageTag
|
||||
from scripts.db.update_image_data import update_image_data
|
||||
|
||||
from scripts.bin import (
|
||||
bin_file_name,
|
||||
get_matched_summary,
|
||||
check_bin_exists,
|
||||
download_bin_file,
|
||||
)
|
||||
|
||||
from scripts.bin import (
|
||||
check_bin_exists,
|
||||
cwd,
|
||||
bin_file_path,
|
||||
is_win,
|
||||
)
|
||||
from scripts.tool import get_windows_drives, convert_to_bytes
|
||||
import functools
|
||||
from scripts.tool import get_windows_drives
|
||||
from scripts.logger import logger
|
||||
|
||||
|
||||
class AutoUpload:
|
||||
# 已成等待发送图像的队列
|
||||
files = []
|
||||
task_id: Union[None, str] = None
|
||||
|
||||
|
||||
def exec_ops(args: Union[List[str], str]):
|
||||
args = [args] if isinstance(args, str) else args
|
||||
res = ""
|
||||
if check_bin_exists():
|
||||
result = subprocess.run([bin_file_path, *args], capture_output=True)
|
||||
try:
|
||||
res = result.stdout.decode().strip()
|
||||
except UnicodeDecodeError:
|
||||
res = result.stdout.decode("gbk", errors="ignore").strip()
|
||||
if args[0] != "ls":
|
||||
logger.info(res)
|
||||
return res
|
||||
|
||||
|
||||
def login_by_bduss(bduss: str):
|
||||
output = exec_ops(["login", f"-bduss={bduss}"])
|
||||
match = re.search("百度帐号登录成功: (.+)$", output)
|
||||
if match:
|
||||
return {"status": "ok", "msg": match.group(1).strip()}
|
||||
else:
|
||||
return {"status": "error", "msg": output}
|
||||
|
||||
|
||||
def get_curr_working_dir():
|
||||
return exec_ops("pwd")
|
||||
|
||||
|
||||
def list_file(cwd="/"):
|
||||
output = exec_ops(["ls", cwd])
|
||||
pattern = re.compile(
|
||||
r"\s+(\d+)\s+([\w\-.]+)\s+(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\s+(.*)"
|
||||
)
|
||||
if output.find("获取目录下的文件列表: 网络错误") != -1:
|
||||
raise Exception("获取目录下的文件列表: 网络错误")
|
||||
files = []
|
||||
for line in output.split("\n"):
|
||||
match = re.match(pattern, line)
|
||||
if match:
|
||||
name = match.group(4).strip()
|
||||
f_type = "dir" if name.endswith("/") else "file"
|
||||
size = match.group(2)
|
||||
name = name.strip("/")
|
||||
file_info = {
|
||||
"size": size,
|
||||
"date": match.group(3),
|
||||
"name": name,
|
||||
"type": f_type,
|
||||
"bytes": convert_to_bytes(size) if size != "-" else size,
|
||||
"fullpath": f"{cwd}/{name}",
|
||||
}
|
||||
files.append(file_info)
|
||||
return files
|
||||
|
||||
|
||||
def get_curr_user():
|
||||
match = re.search(
|
||||
r"uid:\s*(\d+), 用户名:\s*(\w+),",
|
||||
exec_ops("who"),
|
||||
)
|
||||
if not match:
|
||||
return
|
||||
uid = match.group(1)
|
||||
if int(uid) == 0:
|
||||
return
|
||||
username = match.group(2)
|
||||
return {"uid": uid, "username": username}
|
||||
|
||||
|
||||
def logout():
|
||||
match = re.search("退出用户成功", exec_ops(["logout", "-y"]))
|
||||
return bool(match)
|
||||
|
||||
|
||||
def singleton_async(fn):
|
||||
@functools.wraps(fn)
|
||||
async def wrapper(*args, **kwargs):
|
||||
key = args[0] if len(args) > 0 else None
|
||||
if key in wrapper.busy:
|
||||
raise Exception("Function is busy, please try again later.")
|
||||
wrapper.busy.append(key)
|
||||
try:
|
||||
return await fn(*args, **kwargs)
|
||||
finally:
|
||||
wrapper.busy.remove(key)
|
||||
|
||||
wrapper.busy = []
|
||||
return wrapper
|
||||
|
||||
|
||||
send_img_path = {"value": ""}
|
||||
|
|
@ -150,24 +39,6 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
name="infinite_image_browsing-fe-static",
|
||||
)
|
||||
|
||||
@app.get(f"{pre}/user")
|
||||
async def user():
|
||||
return get_curr_user()
|
||||
|
||||
@app.post(f"{pre}/user/logout")
|
||||
async def user_logout():
|
||||
return logout()
|
||||
|
||||
class BaiduyunUserLoginReq(BaseModel):
|
||||
bduss: str
|
||||
|
||||
@app.post(f"{pre}/user/login")
|
||||
async def user_login(req: BaiduyunUserLoginReq):
|
||||
res = login_by_bduss(req.bduss)
|
||||
if res["status"] != "ok":
|
||||
raise HTTPException(status_code=401, detail=res["msg"])
|
||||
return get_curr_user()
|
||||
|
||||
@app.get(f"{pre}/hello")
|
||||
async def greeting():
|
||||
return "hello"
|
||||
|
|
@ -193,63 +64,6 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
send_dirs: List[str]
|
||||
recv_dir: str
|
||||
|
||||
@app.post(f"{pre}/task")
|
||||
async def upload(req: BaiduyunUploadDownloadReq):
|
||||
task = await BaiduyunTask.create(**req.dict())
|
||||
return {"id": task.id}
|
||||
|
||||
@app.get(f"{pre}/tasks")
|
||||
async def upload_tasks():
|
||||
tasks = []
|
||||
for key in BaiduyunTask.get_cache():
|
||||
task = BaiduyunTask.get_by_id(key)
|
||||
task.update_state()
|
||||
tasks.append(task.get_summary())
|
||||
return {"tasks": list(reversed(tasks))}
|
||||
|
||||
@app.delete(pre + "/task/{id}")
|
||||
async def remove_task_cache(id: str):
|
||||
c = BaiduyunTask.get_cache()
|
||||
if id in c:
|
||||
c.pop(id)
|
||||
|
||||
@app.get(pre + "/task/{id}/files_state")
|
||||
async def task_files_state(id):
|
||||
p = BaiduyunTask.get_by_id(id)
|
||||
if not p:
|
||||
raise HTTPException(status_code=404, detail="找不到该上传任务")
|
||||
return {"files_state": p.files_state}
|
||||
|
||||
@app.post(pre + "/task/{id}/cancel")
|
||||
async def cancel_task(id):
|
||||
p = BaiduyunTask.get_by_id(id)
|
||||
if not p:
|
||||
raise HTTPException(status_code=404, detail="找不到该上传任务")
|
||||
last_tick = await p.cancel()
|
||||
return {"last_tick": last_tick}
|
||||
|
||||
upload_poll_promise_dict = {}
|
||||
|
||||
@app.get(pre + "/task/{id}/tick")
|
||||
async def upload_poll(id):
|
||||
async def get_tick_sync_wait_wrapper():
|
||||
task = BaiduyunTask.get_by_id(id)
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="找不到该上传任务")
|
||||
return await task.get_tick()
|
||||
|
||||
res = upload_poll_promise_dict.get(id)
|
||||
if res:
|
||||
res = await res
|
||||
else:
|
||||
upload_poll_promise_dict[id] = asyncio.create_task(
|
||||
get_tick_sync_wait_wrapper()
|
||||
)
|
||||
res = await upload_poll_promise_dict[id]
|
||||
upload_poll_promise_dict.pop(id)
|
||||
|
||||
return res
|
||||
|
||||
class DeleteFilesReq(BaseModel):
|
||||
file_paths: List[str]
|
||||
|
||||
|
|
@ -266,7 +80,7 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
# 处理删除失败的情况
|
||||
raise HTTPException(400, detail=f"删除文件{path}时出错:{e}")
|
||||
else:
|
||||
exec_ops(["rm", *req.file_paths]) # 没检查是否失败,暂时先这样
|
||||
pass
|
||||
|
||||
class MoveFilesReq(BaseModel):
|
||||
file_paths: List[str]
|
||||
|
|
@ -281,7 +95,7 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
except OSError as e:
|
||||
raise HTTPException(400, detail=f"移动文件{path}到{req.dest}时出错:{e}")
|
||||
else:
|
||||
exec_ops(["mv", *req.file_paths, req.dest]) # 没检查是否失败,暂时先这样
|
||||
pass
|
||||
|
||||
@app.get(pre + "/files/{target}")
|
||||
async def get_target_floder_files(
|
||||
|
|
@ -329,7 +143,7 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
}
|
||||
)
|
||||
else:
|
||||
files = list_file(folder_path)
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
|
@ -457,6 +271,8 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
for _ in range(600): # 等待60s
|
||||
if send_img_path["value"] == "": # 等待setup里面生成完成
|
||||
return True
|
||||
v = send_img_path["value"]
|
||||
logger.info("gen_info_completed %s %s",_,v )
|
||||
await asyncio.sleep(0.1)
|
||||
return send_img_path["value"] == ""
|
||||
|
||||
|
|
@ -465,26 +281,6 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
with Image.open(path) as img:
|
||||
return read_info_from_image(img)
|
||||
|
||||
class AutoUploadParams(BaseModel):
|
||||
recv_dir: str
|
||||
|
||||
@app.post(pre + "/auto_upload")
|
||||
async def auto_upload(req: AutoUploadParams):
|
||||
tick_info = None
|
||||
if AutoUpload.task_id:
|
||||
task = BaiduyunTask.get_by_id(AutoUpload.task_id)
|
||||
tick_info = await task.get_tick()
|
||||
if not task.running:
|
||||
AutoUpload.task_id = None
|
||||
else:
|
||||
recived_file = AutoUpload.files
|
||||
AutoUpload.files = []
|
||||
if len(recived_file):
|
||||
logger.info(f"创建上传任务 {recived_file} ----> {req.recv_dir}")
|
||||
task = await BaiduyunTask.create("upload", recived_file, req.recv_dir)
|
||||
AutoUpload.task_id = task.id
|
||||
return {"tick_info": tick_info, "pending_files": AutoUpload.files}
|
||||
|
||||
class CheckPathExistsReq(BaseModel):
|
||||
paths: List[str]
|
||||
|
||||
|
|
@ -495,24 +291,10 @@ def infinite_image_browsing_api(_: Any, app: FastAPI):
|
|||
res[path] = os.path.exists(path)
|
||||
return res
|
||||
|
||||
@app.get(pre + "/baiduyun_exists")
|
||||
async def baiduyun_exists():
|
||||
return check_bin_exists()
|
||||
|
||||
@app.get(pre)
|
||||
def index_bd():
|
||||
return FileResponse(os.path.join(cwd, "vue/dist/index.html"))
|
||||
|
||||
@app.post(pre + "/download_baiduyun")
|
||||
async def download_baiduyun():
|
||||
if not check_bin_exists():
|
||||
try:
|
||||
download_bin_file()
|
||||
except:
|
||||
raise HTTPException(
|
||||
500,
|
||||
detail=f"安装失败,找不到{bin_file_name},尝试手动从 {get_matched_summary()[1]} 或者 {get_matched_summary()[2]} 下载,下载后放到 {cwd} 文件夹下,重启界面",
|
||||
)
|
||||
|
||||
db_pre = pre + "/db"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,170 +0,0 @@
|
|||
import asyncio
|
||||
import datetime
|
||||
import os
|
||||
from typing import Dict, List, Literal
|
||||
import uuid
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from scripts.log_parser import parse_log_line
|
||||
from scripts.bin import bin_file_path
|
||||
from scripts.logger import logger
|
||||
from scripts.tool import is_dev
|
||||
|
||||
|
||||
class BaiduyunTask:
|
||||
def __init__(
|
||||
self,
|
||||
subprocess: asyncio.subprocess.Process,
|
||||
type: Literal["upload", "download"],
|
||||
send_dirs: List[str],
|
||||
recv_dir: str,
|
||||
):
|
||||
self.subprocess = subprocess
|
||||
self.id = str(uuid.uuid4())
|
||||
self.start_time = datetime.datetime.now()
|
||||
self.running = True
|
||||
self.logs = []
|
||||
self.raw_logs = []
|
||||
self.files_state = {}
|
||||
self.type = type
|
||||
self.send_dirs = send_dirs
|
||||
self.recv_dir = recv_dir
|
||||
self.n_files = 0
|
||||
self.n_success_files = 0
|
||||
self.n_failed_files = 0
|
||||
self.canceled = False
|
||||
|
||||
def start_time_human_readable(self):
|
||||
return self.start_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def update_state(self):
|
||||
self.n_files = 0
|
||||
self.n_success_files = 0
|
||||
self.n_failed_files = 0
|
||||
for key in self.files_state:
|
||||
status = self.files_state[key]["status"]
|
||||
self.n_files += 1
|
||||
if status == "upload-success" or status == "file-skipped" or status == "download-success":
|
||||
self.n_success_files += 1
|
||||
elif status == "upload-failed":
|
||||
self.n_failed_files += 1
|
||||
self.running = False if self.canceled else not isinstance(self.subprocess.returncode, int)
|
||||
|
||||
def append_log(self, parsed_log, raw_log):
|
||||
self.raw_logs.append(raw_log)
|
||||
self.logs.append(parsed_log)
|
||||
if isinstance(parsed_log, dict) and "id" in parsed_log:
|
||||
self.files_state[parsed_log["id"]] = parsed_log
|
||||
|
||||
def get_summary(task):
|
||||
return {
|
||||
"type": task.type,
|
||||
"id": task.id,
|
||||
"running": task.running,
|
||||
"start_time": task.start_time_human_readable(),
|
||||
"recv_dir": task.recv_dir,
|
||||
"send_dirs": task.send_dirs,
|
||||
"n_files": task.n_files,
|
||||
"n_failed_files": task.n_failed_files,
|
||||
"n_success_files": task.n_success_files,
|
||||
"canceled": task.canceled
|
||||
}
|
||||
|
||||
#
|
||||
async def get_tick(self):
|
||||
tasks = []
|
||||
while True:
|
||||
try:
|
||||
line = await asyncio.wait_for(
|
||||
self.subprocess.stdout.readline(), timeout=0.1
|
||||
)
|
||||
line = line.decode()
|
||||
logger.info(line)
|
||||
if not line:
|
||||
break
|
||||
if line.isspace():
|
||||
continue
|
||||
info = parse_log_line(line, self.type == "upload")
|
||||
if is_dev and line.find("md5") != -1:
|
||||
line = ''
|
||||
tasks.append({"info": info, "log": line})
|
||||
self.append_log(info, line)
|
||||
except asyncio.TimeoutError:
|
||||
break
|
||||
self.update_state()
|
||||
return {"tasks": tasks, "task_summary": self.get_summary()}
|
||||
|
||||
async def cancel(self):
|
||||
self.subprocess.terminate()
|
||||
self.canceled = True
|
||||
last_tick = await self.get_tick()
|
||||
return last_tick
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def create(
|
||||
type: Literal["upload", "download"], send_dirs: List[str], recv_dir: str
|
||||
):
|
||||
if type == "upload" :
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
bin_file_path,
|
||||
type,
|
||||
*process_path_arr(send_dirs),
|
||||
parse_and_replace_time(recv_dir),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
elif type == "download":
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
bin_file_path,
|
||||
type,
|
||||
*process_path_arr(send_dirs),
|
||||
"--saveto",
|
||||
parse_and_replace_time(recv_dir),
|
||||
"-p",
|
||||
"4",
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
else:
|
||||
raise Exception("unknown task type")
|
||||
task = BaiduyunTask(process, type, send_dirs, recv_dir)
|
||||
task.update_state()
|
||||
baiduyun_task_cache[task.id] = task
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(id: str):
|
||||
return baiduyun_task_cache.get(id)
|
||||
|
||||
@staticmethod
|
||||
def get_cache():
|
||||
return baiduyun_task_cache
|
||||
|
||||
|
||||
baiduyun_task_cache: Dict[str, BaiduyunTask] = {}
|
||||
|
||||
def process_path_arr(path_arr):
|
||||
"""
|
||||
处理路径,顺便替换模板
|
||||
如果是绝对路径直接返回,
|
||||
如果是相对路径则与当前工作目录拼接返回。
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
result = []
|
||||
for path in path_arr:
|
||||
if os.path.isabs(path):
|
||||
result.append(path)
|
||||
else:
|
||||
result.append(os.path.join(cwd, path))
|
||||
return list(map(parse_and_replace_time, result))
|
||||
|
||||
def parse_and_replace_time(s):
|
||||
pattern = r'<#(.+?)#>'
|
||||
matches = re.findall(pattern, s)
|
||||
for match in matches:
|
||||
formatted_time = datetime.datetime.now().strftime(match)
|
||||
s = s.replace(f'<#{match}#>', formatted_time)
|
||||
return s
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import urllib.request
|
||||
import zipfile
|
||||
import os
|
||||
import platform
|
||||
from scripts.tool import cwd,is_win
|
||||
bin_file_name = "BaiduPCS-Go.exe" if is_win else "BaiduPCS-Go"
|
||||
bin_file_path = os.path.join(cwd, bin_file_name)
|
||||
|
||||
|
||||
def check_bin_exists():
|
||||
return os.path.exists(bin_file_path)
|
||||
|
||||
|
||||
def get_matched_summary():
|
||||
system = platform.system()
|
||||
machine = platform.machine()
|
||||
if system == "Darwin":
|
||||
if machine == "x86_64":
|
||||
file_name = "BaiduPCS-Go-v3.9.0-darwin-osx-amd64"
|
||||
elif machine == "arm64":
|
||||
file_name = "BaiduPCS-Go-v3.9.0-darwin-osx-arm64"
|
||||
elif system == "Linux":
|
||||
if machine == "i386":
|
||||
file_name = "BaiduPCS-Go-v3.9.0-linux-386"
|
||||
elif machine == "x86_64":
|
||||
file_name = "BaiduPCS-Go-v3.9.0-linux-amd64"
|
||||
elif system == "Windows":
|
||||
if machine == "AMD64":
|
||||
file_name = "BaiduPCS-Go-v3.9.0-windows-x64"
|
||||
elif machine == "x86":
|
||||
file_name = "BaiduPCS-Go-v3.9.0-windows-x86"
|
||||
if not file_name:
|
||||
raise Exception(f"找不到对应的文件,请携带此信息找开发者 machine:{machine} system:{system}")
|
||||
return file_name, f"https://github.com/qjfoidnh/BaiduPCS-Go/releases/download/v3.9.0/{file_name}.zip", f"http://static.zanllp.cn/{file_name}.zip"
|
||||
|
||||
|
||||
def download_bin_file():
|
||||
summary, url, fallback_url = get_matched_summary()
|
||||
|
||||
# 下载文件保存路径
|
||||
download_path = "BaiduPCS-Go.zip"
|
||||
|
||||
try:
|
||||
# 下载文件并保存
|
||||
urllib.request.urlretrieve(url, download_path)
|
||||
except:
|
||||
urllib.request.urlretrieve(fallback_url, download_path)
|
||||
|
||||
# 解压缩
|
||||
with zipfile.ZipFile(download_path, "r") as zip_ref:
|
||||
zip_ref.extractall()
|
||||
|
||||
# 移动文件夹到当前目录下
|
||||
os.rename(os.path.join(summary, bin_file_name), bin_file_path)
|
||||
try:
|
||||
os.chmod(bin_file_path, 0o755) # unix only
|
||||
except Exception:
|
||||
pass
|
||||
# 删除下载的压缩包和空的文件夹
|
||||
os.remove(download_path)
|
||||
os.remove(os.path.join(summary, "README.md"))
|
||||
os.rmdir(summary)
|
||||
|
||||
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import re
|
||||
|
||||
upload_regex = re.compile(
|
||||
r"\[(\d+)\] (上传文件失败|秒传失败|上传文件成功|准备上传|目标文件|加入上传队列)(?:\:|,)?(.*)"
|
||||
)
|
||||
upload_success_extra_re = re.compile(r"保存到网盘路径: (.*)$")
|
||||
upload_concurrent_re = re.compile(r"上传单个文件最大并发量为: (\d+), 最大同时上传文件数为: (\d+)")
|
||||
download_regex = re.compile(r"\[(\d+)\] (加入下载队列|准备下载|下载完成, 保存位置|文件已经存在)(?:\:|,)?(.*)")
|
||||
|
||||
|
||||
def parse_log_line(line_str: str, is_upload=True):
|
||||
match = upload_regex.match(line_str) if is_upload else download_regex.match(line_str)
|
||||
if not match:
|
||||
if line_str.startswith("上传结束") or line_str.startswith("下载结束"):
|
||||
return {"status": "done", "extra_info": line_str}
|
||||
concurrent_match = upload_concurrent_re.search(line_str)
|
||||
if concurrent_match:
|
||||
return {"status": "start", "concurrent": int(concurrent_match.group(1))}
|
||||
return
|
||||
line = {
|
||||
"id": match.group(1),
|
||||
"status": {
|
||||
"秒传失败": "fast-upload-failed",
|
||||
"上传文件成功": "upload-success",
|
||||
"准备上传": "upload-preparing",
|
||||
"目标文件": "file-skipped",
|
||||
"加入上传队列": "queued",
|
||||
"上传文件失败": "upload-failed",
|
||||
"准备下载": "download-preparing",
|
||||
"文件已经存在": "file-skipped",
|
||||
"下载完成, 保存位置": "download-success",
|
||||
"加入下载队列": "queued",
|
||||
}.get(match.group(2)),
|
||||
}
|
||||
extra_info = match.group(3).strip()
|
||||
if is_upload:
|
||||
if line["status"] == "upload-success":
|
||||
line["remote_path"] = upload_success_extra_re.match(
|
||||
extra_info
|
||||
).group(1)
|
||||
elif line["status"] == "upload-preparing":
|
||||
line["local_path"] = extra_info
|
||||
elif line["status"] == "upload-failed":
|
||||
line["extra_info"] = extra_info
|
||||
else:
|
||||
if line["status"] == "download-success":
|
||||
line["local_path"] = extra_info
|
||||
elif line["status"] == "download-preparing":
|
||||
line["remote_path"] = extra_info
|
||||
return line
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
from scripts.api import infinite_image_browsing_api, send_img_path, AutoUpload
|
||||
from modules import script_callbacks, generation_parameters_copypaste as send, extras
|
||||
from scripts.api import infinite_image_browsing_api, send_img_path
|
||||
from modules import script_callbacks, generation_parameters_copypaste as send
|
||||
from scripts.tool import locale
|
||||
from scripts.tool import read_info_from_image
|
||||
from PIL import Image
|
||||
from scripts.logger import logger
|
||||
|
||||
|
||||
"""
|
||||
|
|
@ -9,56 +11,62 @@ api函数声明和启动分离方便另外一边被外部调用
|
|||
"""
|
||||
|
||||
|
||||
|
||||
def on_ui_tabs():
|
||||
import gradio as gr
|
||||
|
||||
with gr.Blocks(analytics_enabled=False) as view:
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
gr.HTML(
|
||||
"如果你看到这个那说明此项那说明出现了问题", elem_id="infinite_image_browsing_container_wrapper"
|
||||
)
|
||||
gr.HTML("error", elem_id="infinite_image_browsing_container_wrapper")
|
||||
# 以下是使用2个组件模拟粘贴过程
|
||||
img = gr.Image(
|
||||
type="pil",
|
||||
elem_id="bd_hidden_img",
|
||||
elem_id="iib_hidden_img",
|
||||
)
|
||||
|
||||
def on_img_change():
|
||||
send_img_path["value"] = '' # 真正收到图片改变才允许放行
|
||||
send_img_path["value"] = "" # 真正收到图片改变才允许放行
|
||||
|
||||
img.change(on_img_change)
|
||||
|
||||
img_update_trigger = gr.Button("button", elem_id="bd_hidden_img_update_trigger")
|
||||
img_update_trigger = gr.Button(
|
||||
"button", elem_id="iib_hidden_img_update_trigger"
|
||||
)
|
||||
|
||||
# 修改文本和图像,等待修改完成后前端触发粘贴按钮
|
||||
# 有时在触发后收不到回调,可能是在解析params。txt时除了问题删除掉就行了
|
||||
def img_update_func():
|
||||
try:
|
||||
path = send_img_path.get("value")
|
||||
geninfo,_ = extras.images.read_info_from_image(Image.open(path))
|
||||
return path, geninfo
|
||||
logger.info("img_update_func %s", path)
|
||||
img = Image.open(path)
|
||||
info = read_info_from_image(img)
|
||||
return img, info
|
||||
except Exception as e:
|
||||
logger.error("img_update_func % c",e)
|
||||
|
||||
img_file_info = gr.Textbox(elem_id="bd_hidden_img_file_info")
|
||||
img_file_info = gr.Textbox(elem_id="iib_hidden_img_file_info")
|
||||
img_update_trigger.click(img_update_func, outputs=[img, img_file_info])
|
||||
for tab in ["txt2img", "img2img", "inpaint", "extras"]:
|
||||
btn = gr.Button(f"Send to {tab}", elem_id=f"bd_hidden_tab_{tab}")
|
||||
btn = gr.Button(f"Send to {tab}", elem_id=f"iib_hidden_tab_{tab}")
|
||||
# 注册粘贴
|
||||
send.register_paste_params_button(
|
||||
send.ParamBinding(
|
||||
paste_button=btn,
|
||||
tabname=tab,
|
||||
source_image_component=img,
|
||||
source_text_component=img_file_info
|
||||
source_text_component=img_file_info,
|
||||
)
|
||||
)
|
||||
|
||||
return ((view, "无边图像浏览" if locale == "zh" else "Infinite image browsing", "infinite-image-browsing"),)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def on_img_saved(params: script_callbacks.ImageSaveParams):
|
||||
AutoUpload.files.append(params.filename)
|
||||
return (
|
||||
(
|
||||
view,
|
||||
"无边图像浏览" if locale == "zh" else "Infinite image browsing",
|
||||
"infinite-image-browsing",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
script_callbacks.on_ui_tabs(on_ui_tabs)
|
||||
script_callbacks.on_app_started(infinite_image_browsing_api)
|
||||
script_callbacks.on_image_saved(on_img_saved)
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
[id^="bd_hidden_"] {
|
||||
[id^="iib_hidden_"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
@ -9,13 +9,9 @@ export {}
|
|||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
ACollapse: typeof import('ant-design-vue/es')['Collapse']
|
||||
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
|
|
@ -25,19 +21,13 @@ declare module '@vue/runtime-core' {
|
|||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
import{B as s}from"./button-3e5cfde3.js";import{d as g,u as O,G as C,_ as u,a as m,a3 as y,D as h}from"./index-6eb64f08.js";import{_ as E,a as _}from"./styleChecker-572b0d40.js";var z=E(function e(t){_(this,e),this.error=new Error("unreachable case: ".concat(JSON.stringify(t)))}),P=function(){return{prefixCls:String,size:{type:String}}};const l=g({compatConfig:{MODE:3},name:"AButtonGroup",props:P(),setup:function(t,n){var a=n.slots,o=O("btn-group",t),i=o.prefixCls,v=o.direction,b=C(function(){var r,p=t.size,c="";switch(p){case"large":c="lg";break;case"small":c="sm";break;case"middle":case void 0:break;default:console.warn(new z(p).error)}return r={},u(r,"".concat(i.value),!0),u(r,"".concat(i.value,"-").concat(c),c),u(r,"".concat(i.value,"-rtl"),v.value==="rtl"),r});return function(){var r;return m("div",{class:b.value},[y((r=a.default)===null||r===void 0?void 0:r.call(a))])}}});s.Group=l;s.install=function(e){return e.component(s.name,s),e.component(l.name,l),e};var w={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"}}]},name:"eye",theme:"outlined"};const x=w;function d(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]!=null?Object(arguments[t]):{},a=Object.keys(n);typeof Object.getOwnPropertySymbols=="function"&&(a=a.concat(Object.getOwnPropertySymbols(n).filter(function(o){return Object.getOwnPropertyDescriptor(n,o).enumerable}))),a.forEach(function(o){S(e,o,n[o])})}return e}function S(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var f=function(t,n){var a=d({},t,n.attrs);return m(h,d({},a,{icon:x}),null)};f.displayName="EyeOutlined";f.inheritAttrs=!1;const $=f;export{$ as E};
|
||||
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 z,r as v,J as V,b0 as A,ad as D,K as G,L as N,a,N as o,M as e,ac as _,V as x,U as k,R as B,br as F,S as R,a0 as U}from"./index-6eb64f08.js";import{u as $,b as E,f as q,d as O,i as Q,j,t as H,M as J,S as K}from"./FileItem-ba9b9206.js";import{g as L}from"./db-2f654f91.js";import{c as P}from"./copy2clipboard-b3ef2142.js";import"./index-8b6f769c.js";import"./styleChecker-572b0d40.js";import"./useTaskListStore-db67ad53.js";import"./EyeOutlined-652d774a.js";import"./button-3e5cfde3.js";const W={class:"hint"},X=z({__name:"MatchedImageGrid",props:{tabIdx:null,paneIdx:null,selectedTagIds:null,id:null},setup(w){const c=w,p=v(),u=V(new A);D(()=>c.selectedTagIds,async()=>{var t;const{res:l}=u.pushAction(()=>L(c.selectedTagIds));p.value=(await l).sort((r,d)=>Date.parse(d.date)-Date.parse(r.date)),(t=m.value)==null||t.scrollToItem(0)},{immediate:!0});const m=v(),f={tabIdx:-1,target:"local",paneIdx:-1},{stackViewEl:b}=$().toRefs(),{itemSize:g,gridItems:h}=E(f),{showMenuIdx:n}=q(),{showGenInfo:i,imageGenInfo:I,q:y,onContextMenuClick:M}=O(f,{openNext:F});return(l,t)=>{const r=R,d=J,C=K;return G(),N("div",{class:"container",ref_key:"stackViewEl",ref:b},[a(C,{size:"large",spinning:!u.isIdle},{default:o(()=>[a(d,{visible:e(i),"onUpdate:visible":t[1]||(t[1]=s=>_(i)?i.value=s:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=s=>i.value=!1)},{cancelText:o(()=>[]),default:o(()=>[a(r,{active:"",loading:!e(y).isIdle},{default:o(()=>[x("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=s=>e(P)(e(I),"copied"))},[x("div",W,k(l.$t("doubleClickToCopy")),1),B(" "+k(e(I)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),a(e(Q),{ref_key:"scroller",ref:m,class:"file-list",items:p.value||[],"item-size":e(g).first,"key-field":"fullpath","item-secondary-size":e(g).second,gridItems:e(h)},{default:o(({item:s,index:S})=>[a(j,{idx:S,file:s,"show-menu-idx":e(n),"onUpdate:showMenuIdx":t[3]||(t[3]=T=>_(n)?n.value=T:null),"full-screen-preview-image-url":e(H)(s),onContextMenuClick:e(M)},null,8,["idx","file","show-menu-idx","full-screen-preview-image-url","onContextMenuClick"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])]),_:1},8,["spinning"])],512)}}});const le=U(X,[["__scopeId","data-v-b2de5e08"]]);export{le as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{d as z,r as _,aV as V,a_ as A,n as D,K as G,L as N,c as a,N as o,O as e,Q as v,T as k,U as x,Y as B,aQ as F,a6 as Q,a8 as U}from"./index-8bb7713e.js";import{u as $,b as E,f as O,d as R,h as q,j,k as H,t as K,M as L,S as Y}from"./FileItem-c8b1e15c.js";import{g as J}from"./db-94066177.js";import"./index-f11b4f57.js";import"./button-37307691.js";const P={class:"hint"},W=z({__name:"MatchedImageGrid",props:{tabIdx:null,paneIdx:null,selectedTagIds:null,id:null},setup(w){const c=w,u=_(),p=V(new A);D(()=>c.selectedTagIds,async()=>{var s;const{res:l}=p.pushAction(()=>J(c.selectedTagIds));u.value=(await l).sort((d,r)=>Date.parse(r.date)-Date.parse(d.date)),(s=m.value)==null||s.scrollToItem(0)},{immediate:!0});const m=_(),f={tabIdx:-1,target:"local",paneIdx:-1},{stackViewEl:h}=$().toRefs(),{itemSize:g,gridItems:b}=E(f),{showMenuIdx:n}=O(),{showGenInfo:i,imageGenInfo:I,q:y,onContextMenuClick:M}=R(f,{openNext:F});return(l,s)=>{const d=Q,r=L,C=Y;return G(),N("div",{class:"container",ref_key:"stackViewEl",ref:h},[a(C,{size:"large",spinning:!p.isIdle},{default:o(()=>[a(r,{visible:e(i),"onUpdate:visible":s[1]||(s[1]=t=>v(i)?i.value=t:null),width:"70vw","mask-closable":"",onOk:s[2]||(s[2]=t=>i.value=!1)},{cancelText:o(()=>[]),default:o(()=>[a(d,{active:"",loading:!e(y).isIdle},{default:o(()=>[k("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:s[0]||(s[0]=t=>e(q)(e(I),"copied"))},[k("div",P,x(l.$t("doubleClickToCopy")),1),B(" "+x(e(I)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),a(e(j),{ref_key:"scroller",ref:m,class:"file-list",items:u.value||[],"item-size":e(g).first,"key-field":"fullpath","item-secondary-size":e(g).second,gridItems:e(b)},{default:o(({item:t,index:S})=>[a(H,{idx:S,file:t,"show-menu-idx":e(n),"onUpdate:showMenuIdx":s[3]||(s[3]=T=>v(n)?n.value=T:null),"full-screen-preview-image-url":e(K)(t),onContextMenuClick:e(M)},null,8,["idx","file","show-menu-idx","full-screen-preview-image-url","onContextMenuClick"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])]),_:1},8,["spinning"])],512)}}});const ae=U(W,[["__scopeId","data-v-b2de5e08"]]);export{ae as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{d as b,F as N,J as T,b0 as V,r as h,G as q,aV as A,y as D,K as o,L as l,Q as r,W as y,V as d,a as G,M as c,ah as U,O as u,N as f,R as p,U as g,X as F,Y as M,aq as $,a0 as L}from"./index-6eb64f08.js";/* empty css */import{a as k,u as O}from"./db-2f654f91.js";import{B as Q}from"./button-3e5cfde3.js";const R={class:"container"},z={class:"search-bar"},E={class:"tag-list"},J=["onClick"],K=b({__name:"TagSearch",props:{tabIdx:null,paneIdx:null},setup(x){const I=x,S=N(),i=T(new V),a=h(),t=h(new Set),m=q(()=>a.value?a.value.tags.slice().sort((s,n)=>n.count-s.count):[]),C=A();D(async()=>{a.value=await k()});const B=async()=>{i.pushAction(async()=>{await O(),a.value=await k()})},w=()=>{S.openTagSearchMatchedImageGridInRight(I.tabIdx,C,Array.from(t.value))},_=s=>s.display_name?`${s.display_name} : ${s.name}`:s.name;return(s,n)=>{const v=Q;return o(),l("div",R,[r("",!0),a.value?(o(),l(y,{key:1},[d("div",null,[d("div",z,[G(c(U),{conv:{value:e=>e.id,text:_},mode:"multiple",style:{width:"100%"},options:c(m),value:Array.from(t.value),placeholder:"Select tags to match images","onUpdate:value":n[0]||(n[0]=e=>t.value=new Set(e))},null,8,["conv","options","value"]),a.value.expired||!a.value.img_count?(o(),u(v,{key:0,onClick:B,loading:!i.isIdle,type:"primary"},{default:f(()=>[p(g(a.value.img_count===0?"Generate index for search image":"Update index"),1)]),_:1},8,["loading"])):(o(),u(v,{key:1,type:"primary",onClick:w,loading:!i.isIdle},{default:f(()=>[p("Search")]),_:1},8,["loading"]))])]),d("ul",E,[(o(!0),l(y,null,F(c(m),e=>(o(),l("li",{key:e.id,class:M(["tag",{selected:t.value.has(e.id)}]),onClick:W=>t.value.has(e.id)?t.value.delete(e.id):t.value.add(e.id)},[t.value.has(e.id)?(o(),u(c($),{key:0})):r("",!0),p(" "+g(_(e)),1)],10,J))),128))])],64)):r("",!0)])}}});const P=L(K,[["__scopeId","data-v-ffc944e7"]]);export{P as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{d as b,V as T,aV as N,a_ as V,r as h,A,ab as D,p as U,K as o,L as l,$ as r,x as y,T as d,c as $,O as c,a2 as q,W as u,N as f,Y as p,U as g,Z as G,a4 as F,cj as L,a8 as M}from"./index-8bb7713e.js";/* empty css */import{a as k,u as O}from"./db-94066177.js";import{B as j}from"./button-37307691.js";const z={class:"container"},E={class:"search-bar"},K={class:"tag-list"},Q=["onClick"],R=b({__name:"TagSearch",props:{tabIdx:null,paneIdx:null},setup(x){const I=x,S=T(),i=N(new V),a=h(),t=h(new Set),m=A(()=>a.value?a.value.tags.slice().sort((s,n)=>n.count-s.count):[]),C=D();U(async()=>{a.value=await k()});const B=async()=>{i.pushAction(async()=>{await O(),a.value=await k()})},w=()=>{S.openTagSearchMatchedImageGridInRight(I.tabIdx,C,Array.from(t.value))},_=s=>s.display_name?`${s.display_name} : ${s.name}`:s.name;return(s,n)=>{const v=j;return o(),l("div",z,[r("",!0),a.value?(o(),l(y,{key:1},[d("div",null,[d("div",E,[$(c(q),{conv:{value:e=>e.id,text:_},mode:"multiple",style:{width:"100%"},options:c(m),value:Array.from(t.value),placeholder:"Select tags to match images","onUpdate:value":n[0]||(n[0]=e=>t.value=new Set(e))},null,8,["conv","options","value"]),a.value.expired||!a.value.img_count?(o(),u(v,{key:0,onClick:B,loading:!i.isIdle,type:"primary"},{default:f(()=>[p(g(a.value.img_count===0?"Generate index for search image":"Update index"),1)]),_:1},8,["loading"])):(o(),u(v,{key:1,type:"primary",onClick:w,loading:!i.isIdle},{default:f(()=>[p("Search")]),_:1},8,["loading"]))])]),d("ul",K,[(o(!0),l(y,null,G(c(m),e=>(o(),l("li",{key:e.id,class:F(["tag",{selected:t.value.has(e.id)}]),onClick:W=>t.value.has(e.id)?t.value.delete(e.id):t.value.add(e.id)},[t.value.has(e.id)?(o(),u(c(L),{key:0})):r("",!0),p(" "+g(_(e)),1)],10,Q))),128))])],64)):r("",!0)])}}});const P=M(R,[["__scopeId","data-v-ffc944e7"]]);export{P as default};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
.ant-alert{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:2px}.ant-alert-content{flex:1;min-width:0}.ant-alert-icon{margin-right:8px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.ant-alert-success .ant-alert-icon{color:#52c41a}.ant-alert-info{background-color:#fff1e6;border:1px solid #f7ae83}.ant-alert-info .ant-alert-icon{color:#d03f0a}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff2f0;border:1px solid #ffccc7}.ant-alert-error .ant-alert-icon{color:#ff4d4f}.ant-alert-error .ant-alert-description>pre{margin:0;padding:0}.ant-alert-action{margin-left:8px}.ant-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:#00000073;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:#000000bf}.ant-alert-close-text{color:#00000073;transition:color .3s}.ant-alert-close-text:hover{color:#000000bf}.ant-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{margin-right:15px;font-size:24px}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:#000000d9;font-size:16px}.ant-alert-message{color:#000000d9}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert.ant-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}.ant-alert.ant-alert-rtl{direction:rtl}.ant-alert-rtl .ant-alert-icon{margin-right:auto;margin-left:8px}.ant-alert-rtl .ant-alert-action,.ant-alert-rtl .ant-alert-close-icon{margin-right:8px;margin-left:auto}.ant-alert-rtl.ant-alert-with-description{padding-right:24px;padding-left:15px}.ant-alert-rtl.ant-alert-with-description .ant-alert-icon{margin-right:auto;margin-left:15px}.ant-statistic{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"}.ant-statistic-title{margin-bottom:4px;color:#00000073;font-size:14px}.ant-statistic-content{color:#000000d9;font-size:24px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.ant-statistic-content-value{display:inline-block;direction:ltr}.ant-statistic-content-prefix,.ant-statistic-content-suffix{display:inline-block}.ant-statistic-content-prefix{margin-right:4px}.ant-statistic-content-suffix{margin-left:4px}.ant-statistic-rtl{direction:rtl}.ant-statistic-rtl .ant-statistic-content-prefix{margin-right:0;margin-left:4px}.ant-statistic-rtl .ant-statistic-content-suffix{margin-right:4px;margin-left:0}.container[data-v-e8df9a1a]{margin:16px}.container>*[data-v-e8df9a1a]{margin:8px}.log-container[data-v-e8df9a1a]{margin-top:24px}.log-container .scroll-container[data-v-e8df9a1a]{max-height:512px;overflow:auto}.log-container ul[data-v-e8df9a1a]{list-style:none;padding:0}.log-container ul li[data-v-e8df9a1a]{display:inline-block;padding:8px;margin:8px;background:var(--zp-secondary-background);border-radius:4px}.log-container ul li.err[data-v-e8df9a1a]{color:red}
|
||||
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{b as s}from"./index-8b6f769c.js";import{cq as t,at as a}from"./index-6eb64f08.js";function r(e,o){return e&&e.length?t(e,s(o)):[]}const i=(e,o)=>(a.success({content:o??`已复制内容 "${e}" 到粘贴板`}),navigator.clipboard.writeText(e));export{i as c,r as u};
|
||||
|
|
@ -1 +1 @@
|
|||
import{c1 as a}from"./index-6eb64f08.js";const n=async()=>(await a.get("/db/basic_info")).data,i=async()=>{await a.post("/db/update_image_data",{},{timeout:1/0})},o=async t=>(await a.get("/db/match_images_by_tags",{params:{tag_ids:t.join()}})).data;export{n as a,o as g,i as u};
|
||||
import{bC as a}from"./index-8bb7713e.js";const n=async()=>(await a.get("/db/basic_info")).data,i=async()=>{await a.post("/db/update_image_data",{},{timeout:1/0})},o=async t=>(await a.get("/db/match_images_by_tags",{params:{tag_ids:t.join()}})).data;export{n as a,o as g,i as u};
|
||||
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-collapse{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";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:2px}.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 2px 2px}.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;display:flex;flex-wrap:nowrap;align-items:flex-start;padding:12px 16px;color:#000000d9;line-height:1.5715;cursor:pointer;transition:all .3s,visibility 0s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{display:inline-block;margin-right:12px;font-size:12px;vertical-align:-1px}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transition:transform .24s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-left:auto}.ant-collapse>.ant-collapse-item>.ant-collapse-header:focus{outline:none}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only{cursor:default}.ant-collapse>.ant-collapse-item .ant-collapse-header-collapsible-only .ant-collapse-header-text{cursor:pointer}.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-left:12px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 40px 12px 16px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{position:absolute;top:50%;right:16px;left:auto;margin:0;transform:translateY(-50%)}.ant-collapse-content{color:#000000d9;background-color:#fff;border-top:1px solid #d9d9d9}.ant-collapse-content>.ant-collapse-content-box{padding:16px}.ant-collapse-content-hidden{display:none}.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 2px 2px}.ant-collapse-borderless{background-color:#fafafa;border:0}.ant-collapse-borderless>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse-borderless>.ant-collapse-item:last-child,.ant-collapse-borderless>.ant-collapse-item:last-child .ant-collapse-header{border-radius:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:4px}.ant-collapse-ghost{background-color:transparent;border:0}.ant-collapse-ghost>.ant-collapse-item{border-bottom:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-ghost>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:12px;padding-bottom:12px}.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#00000040;cursor:not-allowed}.ant-collapse-rtl{direction:rtl}.ant-collapse-rtl .ant-collapse>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{margin-right:0;margin-left:12px}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transform:rotate(180deg)}.ant-collapse-rtl.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{margin-right:auto;margin-left:0}.ant-collapse-rtl.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-right:12px;padding-left:0}.container[data-v-e0979915]{padding:20px;background-color:var(--zp-secondary-background);height:100%;overflow:auto}.header[data-v-e0979915]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h1[data-v-e0979915]{font-size:28px;font-weight:700;color:var(--zp-primary)}.last-record[data-v-e0979915]{margin-left:8px;font-size:14px;color:var(--zp-tertiary)}.last-record a[data-v-e0979915]{text-decoration:none;color:var(--zp-tertiary)}.last-record a[data-v-e0979915]:hover{color:var(--zp-primary)}.content[data-v-e0979915]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));grid-gap:20px}.quick-start[data-v-e0979915]{background-color:var(--zp-primary-background);border-radius:8px;box-shadow:0 1px 2px #0000001a;padding:20px}.quick-start ul[data-v-e0979915]{list-style:none;padding:4px}.quick-start h2[data-v-e0979915]{margin-top:0;margin-bottom:20px;font-size:20px;font-weight:700;color:var(--zp-primary)}.quick-start__item[data-v-e0979915]{margin-bottom:10px;padding:4px 8px;display:flex;align-items:center}.quick-start__item[data-v-e0979915]:hover{background:var(--zp-secondary-background);border-radius:4px;color:var(--primary-color);cursor:pointer}.quick-start__text[data-v-e0979915]{flex:1;font-size:16px}.quick-start__icon[data-v-e0979915]{margin-right:8px}
|
||||
|
|
@ -0,0 +1 @@
|
|||
.container[data-v-8c6e0bce]{padding:20px;background-color:var(--zp-secondary-background);height:100%;overflow:auto}.header[data-v-8c6e0bce]{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.header h1[data-v-8c6e0bce]{font-size:28px;font-weight:700;color:var(--zp-primary)}.last-record[data-v-8c6e0bce]{margin-left:8px;font-size:14px;color:var(--zp-tertiary)}.last-record a[data-v-8c6e0bce]{text-decoration:none;color:var(--zp-tertiary)}.last-record a[data-v-8c6e0bce]:hover{color:var(--zp-primary)}.content[data-v-8c6e0bce]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));grid-gap:20px}.quick-start[data-v-8c6e0bce]{background-color:var(--zp-primary-background);border-radius:8px;box-shadow:0 1px 2px #0000001a;padding:20px}.quick-start ul[data-v-8c6e0bce]{list-style:none;padding:4px}.quick-start h2[data-v-8c6e0bce]{margin-top:0;margin-bottom:20px;font-size:20px;font-weight:700;color:var(--zp-primary)}.quick-start__item[data-v-8c6e0bce]{margin-bottom:10px;padding:4px 8px;display:flex;align-items:center}.quick-start__item[data-v-8c6e0bce]:hover{background:var(--zp-secondary-background);border-radius:4px;color:var(--primary-color);cursor:pointer}.quick-start__text[data-v-8c6e0bce]{flex:1;font-size:16px}.quick-start__icon[data-v-8c6e0bce]{margin-right:8px}
|
||||
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
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{bs as D,bA as K,r as G,y as F,b4 as V,G as v,bb as W,t as I,d as B,u as T,bB as P,a5 as j,A as k,bC as R,c as $,_ as s,a as E,h as _}from"./index-6eb64f08.js";import{g as U,t as q,d as H}from"./styleChecker-572b0d40.js";var J="[object Object]",Q=Function.prototype,X=Object.prototype,L=Q.toString,Y=X.hasOwnProperty,Z=L.call(Object);function ie(n){if(!D(n)||K(n)!=J)return!1;var e=U(n);if(e===null)return!0;var a=Y.call(e,"constructor")&&e.constructor;return typeof a=="function"&&a instanceof a&&L.call(a)==Z}function se(n,e,a){var p=-1,i=n.length;e<0&&(e=-e>i?0:i+e),a=a>i?i:a,a<0&&(a+=i),i=e>a?0:a-e>>>0,e>>>=0;for(var f=Array(i);++p<i;)f[p]=n[p+e];return f}function le(n){var e=q(n),a=e%1;return e===e?a?e-a:e:0}const z=function(){var n=G(!1);return F(function(){n.value=H()}),n};var M=Symbol("rowContextKey"),ee=function(e){W(M,e)},te=function(){return V(M,{gutter:v(function(){}),wrap:v(function(){}),supportFlexGap:v(function(){})})};I("top","middle","bottom","stretch");I("start","end","center","space-around","space-between");var re=function(){return{align:String,justify:String,prefixCls:String,gutter:{type:[Number,Array,Object],default:0},wrap:{type:Boolean,default:void 0}}},ne=B({compatConfig:{MODE:3},name:"ARow",props:re(),setup:function(e,a){var p=a.slots,i=T("row",e),f=i.prefixCls,O=i.direction,S,x=G({xs:!0,sm:!0,md:!0,lg:!0,xl:!0,xxl:!0,xxxl:!0}),w=z();F(function(){S=P.subscribe(function(t){var r=e.gutter||0;(!Array.isArray(r)&&j(r)==="object"||Array.isArray(r)&&(j(r[0])==="object"||j(r[1])==="object"))&&(x.value=t)})}),k(function(){P.unsubscribe(S)});var h=v(function(){var t=[0,0],r=e.gutter,o=r===void 0?0:r,l=Array.isArray(o)?o:[o,0];return l.forEach(function(d,y){if(j(d)==="object")for(var u=0;u<R.length;u++){var g=R[u];if(x.value[g]&&d[g]!==void 0){t[y]=d[g];break}}else t[y]=d||0}),t});ee({gutter:h,supportFlexGap:w,wrap:v(function(){return e.wrap})});var N=v(function(){var t;return $(f.value,(t={},s(t,"".concat(f.value,"-no-wrap"),e.wrap===!1),s(t,"".concat(f.value,"-").concat(e.justify),e.justify),s(t,"".concat(f.value,"-").concat(e.align),e.align),s(t,"".concat(f.value,"-rtl"),O.value==="rtl"),t))}),A=v(function(){var t=h.value,r={},o=t[0]>0?"".concat(t[0]/-2,"px"):void 0,l=t[1]>0?"".concat(t[1]/-2,"px"):void 0;return o&&(r.marginLeft=o,r.marginRight=o),w.value?r.rowGap="".concat(t[1],"px"):l&&(r.marginTop=l,r.marginBottom=l),r});return function(){var t;return E("div",{class:N.value,style:A.value},[(t=p.default)===null||t===void 0?void 0:t.call(p)])}}});const fe=ne;function ae(n){return typeof n=="number"?"".concat(n," ").concat(n," auto"):/^\d+(\.\d+)?(px|em|rem|%)$/.test(n)?"0 0 ".concat(n):n}var oe=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 de=B({compatConfig:{MODE:3},name:"ACol",props:oe(),setup:function(e,a){var p=a.slots,i=te(),f=i.gutter,O=i.supportFlexGap,S=i.wrap,x=T("col",e),w=x.prefixCls,h=x.direction,N=v(function(){var t,r=e.span,o=e.order,l=e.offset,d=e.push,y=e.pull,u=w.value,g={};return["xs","sm","md","lg","xl","xxl","xxxl"].forEach(function(m){var b,c={},C=e[m];typeof C=="number"?c.span=C:j(C)==="object"&&(c=C||{}),g=_(_({},g),{},(b={},s(b,"".concat(u,"-").concat(m,"-").concat(c.span),c.span!==void 0),s(b,"".concat(u,"-").concat(m,"-order-").concat(c.order),c.order||c.order===0),s(b,"".concat(u,"-").concat(m,"-offset-").concat(c.offset),c.offset||c.offset===0),s(b,"".concat(u,"-").concat(m,"-push-").concat(c.push),c.push||c.push===0),s(b,"".concat(u,"-").concat(m,"-pull-").concat(c.pull),c.pull||c.pull===0),s(b,"".concat(u,"-rtl"),h.value==="rtl"),b))}),$(u,(t={},s(t,"".concat(u,"-").concat(r),r!==void 0),s(t,"".concat(u,"-order-").concat(o),o),s(t,"".concat(u,"-offset-").concat(l),l),s(t,"".concat(u,"-push-").concat(d),d),s(t,"".concat(u,"-pull-").concat(y),y),t),g)}),A=v(function(){var t=e.flex,r=f.value,o={};if(r&&r[0]>0){var l="".concat(r[0]/2,"px");o.paddingLeft=l,o.paddingRight=l}if(r&&r[1]>0&&!O.value){var d="".concat(r[1]/2,"px");o.paddingTop=d,o.paddingBottom=d}return t&&(o.flex=ae(t),S.value===!1&&!o.minWidth&&(o.minWidth=0)),o});return function(){var t;return E("div",{class:N.value,style:A.value},[(t=p.default)===null||t===void 0?void 0:t.call(p)])}}});export{de as C,fe as R,se as b,ie as i,le as t};
|
||||
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 @@
|
|||
.container[data-v-59148842]{height:100%;position:relative}.container .close-btn[data-v-59148842]{position:absolute;right:20px;top:20px}.list[data-v-59148842]{font-family:Consolas,Menlo,monospace;height:90vh;overflow:auto;padding:16px;border-radius:16px;background-color:var(--zp-secondary-background);color:var(--zp-primary);margin:16px;list-style:none;font-size:12px}.list pre[data-v-59148842]{margin:2px;white-space:normal}.list[data-v-59148842]::-webkit-scrollbar{display:none}
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{u as p}from"./useTaskListStore-db67ad53.js";import{d as u,r as d,G as g,ad as f,aH as m,K as t,L as s,V as l,W as k,X as L,U as y,M as D,a0 as v}from"./index-6eb64f08.js";const x={class:"container"},h=u({__name:"logDetail",props:{logDetailId:null},setup(r){const n=r,c=p(),a=d(),o=g(()=>c.taskLogMap.get(n.logDetailId));return f(o,async()=>{await m();const e=a.value;e&&(e.scrollTop=e.scrollHeight)},{deep:!0}),(e,B)=>(t(),s("div",x,[l("ul",{class:"list",ref_key:"logListEl",ref:a},[(t(!0),s(k,null,L(D(o),(i,_)=>(t(),s("li",{key:_},[l("pre",null,y(i.log),1)]))),128))],512)]))}});const T=v(h,[["__scopeId","data-v-59148842"]]);export{T as default};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
.ant-breadcrumb{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";color:#00000073;font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb a{color:#00000073;transition:color .3s}.ant-breadcrumb a:hover{color:#de632f}.ant-breadcrumb>span:last-child{color:#000000d9}.ant-breadcrumb>span:last-child a{color:#000000d9}.ant-breadcrumb>span:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:#00000073}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-link>.anticon+a{margin-left:4px}.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-breadcrumb-rtl{direction:rtl}.ant-breadcrumb-rtl:before{display:table;content:""}.ant-breadcrumb-rtl:after{display:table;clear:both;content:""}.ant-breadcrumb-rtl>span{float:right}.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+a{margin-right:4px;margin-left:0}.ant-breadcrumb-rtl .ant-breadcrumb-overlay-link>.anticon{margin-right:4px;margin-left:0}.nprogress{pointer-events:none}.nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}.nprogress .peg{display:block;position:absolute;right:0px;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translateY(-4px)}.nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}.nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent .nprogress .spinner,.nprogress-custom-parent .nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.uninstalled-hint[data-v-70134b8c]{margin:256px auto;display:flex;flex-flow:column;justify-content:center;align-items:center}.uninstalled-hint>*[data-v-70134b8c]{margin:16px;text-align:center}.preview-switch[data-v-70134b8c]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-70134b8c]{margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-70134b8c]{opacity:0;pointer-events:none;cursor:none}.container[data-v-70134b8c]{height:100%;background:var(--zp-secondary-background)}.location-bar[data-v-70134b8c]{padding:4px 16px;background:var(--zp-primary-background);border-bottom:1px solid var(--zp-border);display:flex;align-items:center;justify-content:space-between}.location-bar .actions[data-v-70134b8c]{display:flex;align-items:center;flex-shrink:0}.location-bar a.opt[data-v-70134b8c]{margin-left:8px}.view[data-v-70134b8c]{padding:8px;height:calc(100vh - 96px)}.view .file-list[data-v-70134b8c]{list-style:none;padding:8px;height:100%;overflow:auto}.hint[data-v-70134b8c]{padding:4px;border:4px;background:var(--zp-secondary-background);border:1px solid var(--zp-border)}
|
||||
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{cL as f,cM as i,cN as l,bH as d}from"./index-6eb64f08.js";var u=f(Object.getPrototypeOf,Object);const y=u;function o(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,i(n.key),n)}}function b(e,t,r){return t&&o(e.prototype,t),r&&o(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}function E(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function g(e){return function(t){return t==null?void 0:t[e]}}var c=1/0,p=17976931348623157e292;function h(e){if(!e)return e===0?e:0;if(e=l(e),e===c||e===-c){var t=e<0?-1:1;return t*p}return e===e?e:0}var m=function(){return d()&&window.document.documentElement},a,x=function(){if(!m())return!1;if(a!==void 0)return a;var t=document.createElement("div");return t.style.display="flex",t.style.flexDirection="column",t.style.rowGap="1px",t.appendChild(document.createElement("div")),t.appendChild(document.createElement("div")),document.body.appendChild(t),a=t.scrollHeight===1,document.body.removeChild(t),a};export{b as _,E as a,g as b,m as c,x as d,y as g,h as t};
|
||||
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{cO as r,r as e,J as t,b0 as i,cP as d}from"./index-6eb64f08.js";const v=r("useTaskListStore",()=>{const a=e(new Map),n=t(new i),u=e(3),o=e([]),c=t([]),l=e(-1),s=e(null);return{checkBaiduyunInstalled:async()=>(s.value===null&&(s.value=d()),s.value),baiduyunInstalled:s,pollInterval:u,taskLogMap:a,queue:n,tasks:o,showDirAutoCompletedIdx:l,pendingBaiduyunTaskQueue:c}},{persist:{paths:["pollInterval","tasks"],key:"useTaskListStore-v0.0.1"}});export{v as u};
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-6eb64f08.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-9ee28aec.css">
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-8bb7713e.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-92aac6a6.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { FetchQueue } from 'vue3-ts-util'
|
||||
import { getUserInfo } from './api/user'
|
||||
import { getGlobalSetting } from './api'
|
||||
import { useGlobalStore } from './store/useGlobalStore'
|
||||
import { getAutoCompletedTagList } from '@/page/taskRecord/autoComplete'
|
||||
|
|
@ -16,7 +15,6 @@ onMounted(async () => {
|
|||
const r = await getAutoCompletedTagList(resp)
|
||||
globalStore.autoCompletedDirList = r.filter(v => v?.dir?.trim?.())
|
||||
})
|
||||
globalStore.user = await queue.pushAction(getUserInfo).res
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -17,118 +17,6 @@ export const greeting = async () => {
|
|||
const resp = await axiosInst.get('hello')
|
||||
return resp.data as string
|
||||
}
|
||||
interface BaiduYunTaskCreateReq {
|
||||
type: 'upload' | 'download'
|
||||
send_dirs: string[]
|
||||
recv_dir: string
|
||||
}
|
||||
export const createBaiduYunTask = async (req: BaiduYunTaskCreateReq) => {
|
||||
const resp = await axiosInst.post('task', req)
|
||||
return resp.data as {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
interface UploadTaskStart {
|
||||
status: 'start'
|
||||
concurrent: number
|
||||
}
|
||||
|
||||
interface UploadTaskFileSkipped {
|
||||
status: 'file-skipped'
|
||||
id: string
|
||||
}
|
||||
|
||||
interface UploadTaskPreparing {
|
||||
id: string
|
||||
status: 'upload-preparing'
|
||||
local_path: string
|
||||
}
|
||||
|
||||
interface UploadTaskSuccess {
|
||||
id: string
|
||||
status: 'upload-success'
|
||||
remote_path: string
|
||||
}
|
||||
|
||||
interface UploadTaskFastuploadFailed {
|
||||
id: string
|
||||
status: 'fast-upload-failed'
|
||||
}
|
||||
|
||||
interface UploadTaskFailed {
|
||||
id: string
|
||||
status: 'upload-failed'
|
||||
extra_info: string
|
||||
}
|
||||
|
||||
interface UploadTaskDone {
|
||||
status: 'done'
|
||||
}
|
||||
|
||||
interface UploadTaskQueued {
|
||||
id: string
|
||||
status: 'queued'
|
||||
local_file_path: string
|
||||
}
|
||||
|
||||
export type UploadTaskFileStatus =
|
||||
| UploadTaskFastuploadFailed
|
||||
| UploadTaskFileSkipped
|
||||
| UploadTaskPreparing
|
||||
| UploadTaskQueued
|
||||
| UploadTaskSuccess
|
||||
| UploadTaskFailed
|
||||
|
||||
export interface UploadTaskTickStatus {
|
||||
log: string
|
||||
info: UploadTaskDone | UploadTaskStart | UploadTaskFileStatus
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时刻的记录,包含日志输出,文件状态变化
|
||||
*/
|
||||
export const getUploadTaskTickStatus = async (id: string) => {
|
||||
const resp = await axiosInst.get(`/task/${id}/tick`)
|
||||
return resp.data as {
|
||||
tasks: UploadTaskTickStatus[],
|
||||
task_summary: UploadTaskSummary
|
||||
}
|
||||
}
|
||||
|
||||
export interface UploadTaskSummary {
|
||||
id: string
|
||||
running: boolean
|
||||
start_time: string
|
||||
send_dirs: string[]
|
||||
recv_dir: string
|
||||
type: 'upload' | 'download'
|
||||
n_files: number
|
||||
n_failed_files: number
|
||||
canceled: boolean
|
||||
n_success_files: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定任务所有上传文件的状态
|
||||
* @param id
|
||||
*/
|
||||
export const getUploadTaskFilesState = async (id: string) => {
|
||||
const resp = await axiosInst.get(`/task/${id}/files_state`)
|
||||
return resp.data as {
|
||||
files_state: { [x: string]: UploadTaskFileStatus }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有上传文件的简介
|
||||
*/
|
||||
export const getUploadTasks = async () => {
|
||||
const resp = await axiosInst.get('/tasks')
|
||||
return resp.data as {
|
||||
tasks: UploadTaskSummary[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GlobalConf {
|
||||
global_setting: GlobalSettingPart,
|
||||
|
|
@ -149,19 +37,6 @@ export const checkPathExists = async (paths: string[]) => {
|
|||
}
|
||||
|
||||
|
||||
export const cancelTask = async (id: string) => {
|
||||
const resp = await axiosInst.post(`/task/${id}/cancel`)
|
||||
return resp.data as {
|
||||
last_tick: {
|
||||
tasks: UploadTaskTickStatus[],
|
||||
task_summary: UploadTaskSummary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const removeTask = async (id: string) => {
|
||||
return axiosInst.delete(`/task/${id}`)
|
||||
}
|
||||
|
||||
export const setImgPath = async (path: string) => {
|
||||
return axiosInst.post(`/send_img_path?path=${encodeURIComponent(path)}`)
|
||||
|
|
@ -174,17 +49,3 @@ export const genInfoCompleted = async () => {
|
|||
export const getImageGenerationInfo = async (path: string) => {
|
||||
return (await axiosInst.get(`/image_geninfo?path=${encodeURIComponent(path)}`)).data as string
|
||||
}
|
||||
|
||||
export const autoUploadOutput = async (recv_dir: string) => {
|
||||
const resp = await axiosInst.post(`/auto_upload`, { recv_dir })
|
||||
return resp.data as {
|
||||
pending_files: string[]
|
||||
tick_info?: {
|
||||
tasks: UploadTaskTickStatus[],
|
||||
task_summary: UploadTaskSummary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const checkBaiduyunExists = () => axiosInst.get('/baiduyun_exists').then(v => v.data as boolean)
|
||||
export const downloadBaiduyun = () => axiosInst.post('/download_baiduyun')
|
||||
|
|
@ -11,12 +11,8 @@ import { message } from 'ant-design-vue'
|
|||
import { t } from '@/i18n'
|
||||
const global = useGlobalStore()
|
||||
const compMap: Record<TabPane['type'], ReturnType<typeof defineAsyncComponent>> = {
|
||||
'auto-upload': defineAsyncComponent(() => import('@/page/autoUpload/autoUpload.vue')),
|
||||
local: defineAsyncComponent(() => import('@/page/fileTransfer/stackView.vue')),
|
||||
netdisk: defineAsyncComponent(() => import('@/page/fileTransfer/stackView.vue')),
|
||||
"task-record": defineAsyncComponent(() => import('@/page/taskRecord/taskRecord.vue')),
|
||||
empty: defineAsyncComponent(() => import('./emptyStartup.vue')),
|
||||
"log-detail": defineAsyncComponent(() => import('@/page/taskRecord/logDetail.vue')),
|
||||
"global-setting": defineAsyncComponent(() => import('@/page/globalSetting.vue')),
|
||||
"tag-search-matched-image-grid": defineAsyncComponent(() => import('@/page/TagSearch/MatchedImageGrid.vue')),
|
||||
"tag-search": defineAsyncComponent(() => import('@/page/TagSearch/TagSearch.vue'))
|
||||
|
|
@ -74,8 +70,7 @@ watch(() => global.tabList, async () => {
|
|||
<pane v-for="tab, tabIdx in global.tabList" :key="key(tab)">
|
||||
<edge-trigger :tabIdx="tabIdx">
|
||||
<a-tabs type="editable-card" v-model:activeKey="tab.key" @edit="(key, act) => onEdit(tabIdx, key, act)">
|
||||
<a-tab-pane v-for="pane, paneIdx in tab.panes" :key="pane.key" :tab="pane.name" class="pane"
|
||||
:force-render="pane.type === 'task-record'">
|
||||
<a-tab-pane v-for="pane, paneIdx in tab.panes" :key="pane.key" :tab="pane.name" class="pane">
|
||||
<component :is="compMap[pane.type]" :tabIdx="tabIdx" :paneIdx="paneIdx" v-bind="pane" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
|
|
|||
|
|
@ -13,28 +13,18 @@ const compCnMap: Partial<Record<TabPane['type'], string>> = {
|
|||
local: t('local'),
|
||||
"tag-search": t('imgSearch'),
|
||||
'global-setting': t('globalSettings'),
|
||||
netdisk: t('baiduCloud'),
|
||||
"task-record": t('taskRecord'),
|
||||
"auto-upload": t('autoUpload'),
|
||||
}
|
||||
const openInCurrentTab = (type: TabPane['type'], path?: string, walkMode = false) => {
|
||||
let pane: TabPane
|
||||
if (type === 'task-record' && global.tabList.map(v => v.panes).flat().find(v => v.type === 'task-record')) {
|
||||
return message.error(t('onlyOneTaskRecordAllowed')) // 如果允许多个需要处理一些监听器,懒得改后面再说
|
||||
}
|
||||
switch (type) {
|
||||
case 'tag-search-matched-image-grid':
|
||||
return
|
||||
case 'auto-upload':
|
||||
case 'task-record':
|
||||
case 'log-detail':
|
||||
case 'global-setting':
|
||||
case 'tag-search':
|
||||
case 'empty':
|
||||
pane = { type, name: compCnMap[type]!, key: Date.now() + uniqueId() }
|
||||
break
|
||||
case 'local':
|
||||
case 'netdisk':
|
||||
pane = { type, name: compCnMap[type]!, key: Date.now() + uniqueId(), target: type, path, walkMode }
|
||||
}
|
||||
const tab = global.tabList[props.tabIdx]
|
||||
|
|
@ -87,19 +77,10 @@ const previewInNewWindow = () => window.parent.open('/infinite_image_browsing')
|
|||
<div class="quick-start">
|
||||
<h2>{{ $t('launch') }}</h2>
|
||||
<ul>
|
||||
<li v-for="comp in Object.keys(compCnMap).slice(0, 3) as TabPane['type'][]" :key="comp"
|
||||
<li v-for="comp in Object.keys(compCnMap) as TabPane['type'][]" :key="comp"
|
||||
class="quick-start__item" @click.prevent="openInCurrentTab(comp)">
|
||||
<span class="quick-start__text line-clamp-1">{{ compCnMap[comp] }}</span>
|
||||
</li>
|
||||
<a-collapse style="margin-top: 32px; " v-model:activeKey="global.baiduNetdiskPageOpened" :bordered="false">
|
||||
<a-collapse-panel key="true" :header="$t('baiduNetdiskCollapseTitle')" >
|
||||
<li v-for="comp in Object.keys(compCnMap).slice(3) as TabPane['type'][]" :key="comp"
|
||||
class="quick-start__item" @click.prevent="openInCurrentTab(comp)">
|
||||
<span class="quick-start__text line-clamp-1">{{ compCnMap[comp] }}</span>
|
||||
</li>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="quick-start" v-if="global.recent.length">
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watchEffect } from 'vue'
|
||||
import { autoUploadOutput, type UploadTaskSummary } from '@/api/index'
|
||||
import { delay, Task } from 'vue3-ts-util'
|
||||
import { useGlobalStore } from '@/store/useGlobalStore'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { Loading3QuartersOutlined, CloseCircleOutlined } from '@/icon'
|
||||
import { useTaskListStore } from '@/store/useTaskListStore'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const props = defineProps<{ tabIdx: number, paneIdx: number }>()
|
||||
|
||||
|
||||
|
||||
const emit = defineEmits<{ (e: 'runningChange', v: boolean): void }>()
|
||||
const global = useGlobalStore()
|
||||
const taskStore = useTaskListStore()
|
||||
const pendingFiles = ref<string[]>([])
|
||||
const task = ref<ReturnType<typeof runPollTask>>()
|
||||
const running = computed(() => !!(task.value || pendingFiles.value.length))
|
||||
watchEffect(() => emit('runningChange', running.value))
|
||||
const taskLog = reactive(new Map<string, UploadTaskSummary>())
|
||||
const taskLogList = computed(() => Array.from(taskLog.values()))
|
||||
const completedFiles = computed(() => taskLogList.value.reduce((p, c) => p + c.n_success_files, 0))
|
||||
const failededFiles = computed(() => taskLogList.value.reduce((p, c) => p + c.n_failed_files, 0))
|
||||
// const allFiles = computed(() => taskLogList.value.reduce((p, c) => p + c.n_files, 0) + pendingFiles.value.length)
|
||||
|
||||
onMounted(() => global.openBaiduYunIfNotLogged(props.tabIdx, props.paneIdx))
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
task.value?.clearTask()
|
||||
})
|
||||
|
||||
const runPollTask = () => {
|
||||
return Task.run({
|
||||
action: async () => {
|
||||
const res = await autoUploadOutput(global.autoUploadRecvDir)
|
||||
if (res.tick_info) {
|
||||
const info = res.tick_info!
|
||||
taskLog.set(info.task_summary.id, info.task_summary)
|
||||
taskStore.taskLogMap.set(info.task_summary.id, info.tasks)
|
||||
}
|
||||
pendingFiles.value = res.pending_files
|
||||
await delay(10000 * Math.random())
|
||||
return res
|
||||
},
|
||||
pollInterval: 30_000
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const onStart = async () => {
|
||||
if (task.value) {
|
||||
task.value.clearTask()
|
||||
task.value = undefined
|
||||
pendingFiles.value = []
|
||||
} else {
|
||||
task.value = runPollTask()
|
||||
}
|
||||
}
|
||||
|
||||
const openLogDetail = (id: string) => {
|
||||
global.openLogDetailInRight(props.tabIdx, id)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="container">
|
||||
<AInput v-model:value="global.autoUploadRecvDir"></AInput>
|
||||
<AButton @click="onStart">
|
||||
<template v-if="task">
|
||||
<Loading3QuartersOutlined spin />
|
||||
</template>
|
||||
{{ task ? $t('start') : $t('pause') }}
|
||||
</AButton>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-statistic :title="$t('waitingUploadCount')" :value="pendingFiles.length" style="margin-right: 50px" />
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-statistic :title="$t('uploadFailureCount')" :value="failededFiles" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-statistic :title="$t('completedCount')" :value="completedFiles" style="margin-right: 50px" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="log-container">
|
||||
<h2>
|
||||
{{ $t('realTimeLog') }}
|
||||
</h2> <a-alert :message="$t('tip')" :description="$t('clickToViewLogs')" type="info" show-icon />
|
||||
<ul class="scroll-container">
|
||||
<li v-for="item in taskLog.values()" :key="item.id" :class="{ err: item.n_failed_files }"
|
||||
@click="openLogDetail(item.id)">
|
||||
<CloseCircleOutlined v-if="item.n_failed_files" /> {{ $t('startedAt') + item.start_time }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
margin: 16px;
|
||||
|
||||
&>* {
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.log-container {
|
||||
margin-top: 24px;
|
||||
|
||||
.scroll-container {
|
||||
|
||||
max-height: 512px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
background: var(--zp-secondary-background);
|
||||
border-radius: 4px;
|
||||
|
||||
&.err {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import { useGlobalStore, type FileTransferTabPane } from '@/store/useGlobalStore'
|
||||
import { useTaskListStore } from '@/store/useTaskListStore'
|
||||
import { computedAsync, onLongPress, useElementSize } from '@vueuse/core'
|
||||
import { onLongPress, useElementSize } from '@vueuse/core'
|
||||
import { ref, computed, watch, onMounted, h, reactive } from 'vue'
|
||||
|
||||
import { downloadBaiduyun, genInfoCompleted, getImageGenerationInfo, setImgPath } from '@/api'
|
||||
import { isAxiosError } from 'axios'
|
||||
import { genInfoCompleted, getImageGenerationInfo, setImgPath } from '@/api'
|
||||
import { useWatchDocument, type SearchSelectConv, ok, createTypedShareStateHook, copy2clipboard, delay, FetchQueue, typedEventEmitter, ID } from 'vue3-ts-util'
|
||||
import { gradioApp, isImageFile } from '@/util'
|
||||
import { getTargetFolderFiles, type FileNodeInfo, deleteFiles, moveFiles } from '@/api/files'
|
||||
|
|
@ -17,9 +15,7 @@ import NProgress from 'multi-nprogress'
|
|||
import { Modal, message } from 'ant-design-vue'
|
||||
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
|
||||
import { nextTick } from 'vue'
|
||||
import { loginByBduss } from '@/api/user'
|
||||
import { t } from '@/i18n'
|
||||
import { locale } from '@/i18n'
|
||||
import { CloudServerOutlined, DatabaseOutlined } from '@/icon'
|
||||
|
||||
export const stackCache = new Map<string, Page[]>()
|
||||
|
|
@ -85,13 +81,12 @@ export const { useHookShareState } = createTypedShareStateHook(() => {
|
|||
stackViewEl: ref<HTMLDivElement>(),
|
||||
walkModePath,
|
||||
props,
|
||||
...useBaiduyun(),
|
||||
...typedEventEmitter<{ loadNextDir: undefined, refresh: void }>()
|
||||
}
|
||||
})
|
||||
|
||||
export interface Props {
|
||||
target: 'local' | 'netdisk',
|
||||
target: 'local',
|
||||
tabIdx: number,
|
||||
paneIdx: number,
|
||||
path?: string,
|
||||
|
|
@ -99,54 +94,6 @@ export interface Props {
|
|||
}
|
||||
|
||||
export type ViewMode = 'line' | 'grid' | 'large-size-grid'
|
||||
const taskListStore = useTaskListStore()
|
||||
|
||||
export const useBaiduyun = () => {
|
||||
|
||||
const bduss = ref('')
|
||||
const installedBaiduyun = computedAsync(taskListStore.checkBaiduyunInstalled, false)
|
||||
const baiduyunLoading = ref(false)
|
||||
const failedHint = ref('')
|
||||
const installBaiduyunBin = async () => {
|
||||
try {
|
||||
failedHint.value = ''
|
||||
baiduyunLoading.value = true
|
||||
await downloadBaiduyun()
|
||||
taskListStore.baiduyunInstalled = null
|
||||
await taskListStore.checkBaiduyunInstalled()
|
||||
} catch (e) {
|
||||
if (isAxiosError(e)) {
|
||||
failedHint.value = e.response?.data.detail ?? 'error'
|
||||
}
|
||||
} finally {
|
||||
baiduyunLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onLoginBtnClick = async () => {
|
||||
if (baiduyunLoading.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
baiduyunLoading.value = true
|
||||
global.user = await loginByBduss(bduss.value)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
message.error(isAxiosError(error) ? error.response?.data?.detail ?? t('unknownError') : t('unknownError'))
|
||||
} finally {
|
||||
baiduyunLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
installBaiduyunBin,
|
||||
installedBaiduyun,
|
||||
failedHint,
|
||||
baiduyunLoading,
|
||||
bduss,
|
||||
onLoginBtnClick
|
||||
}
|
||||
}
|
||||
|
||||
export interface Page {
|
||||
files: FileNodeInfo[]
|
||||
|
|
@ -254,7 +201,7 @@ export function usePreview (props: Props) {
|
|||
|
||||
export function useLocation (props: Props) {
|
||||
const np = ref<Progress.NProgress>()
|
||||
const { installedBaiduyun, scroller, stackViewEl, stack, currPage, currLocation, basePath
|
||||
const { scroller, stackViewEl, stack, currPage, currLocation, basePath
|
||||
, sortMethod, useEventListen, walkModePath
|
||||
} = useHookShareState().toRefs()
|
||||
|
||||
|
|
@ -265,9 +212,6 @@ export function useLocation (props: Props) {
|
|||
}, 300))
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.target === 'netdisk' && installedBaiduyun.value) {
|
||||
return
|
||||
}
|
||||
if (!stack.value.length) { // 有传入stack时直接使用传入的
|
||||
const resp = await getTargetFolderFiles(props.target, '/')
|
||||
stack.value.push({
|
||||
|
|
@ -292,20 +236,6 @@ export function useLocation (props: Props) {
|
|||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 登录后重新获取
|
||||
*/
|
||||
watch(() => props.target === 'netdisk' && installedBaiduyun.value && global.user, async (v, last) => {
|
||||
if (v && !last) {
|
||||
const resp = await getTargetFolderFiles(props.target, '/')
|
||||
stack.value = [{
|
||||
files: resp.files,
|
||||
curr: '/'
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
watch(currLocation, debounce((loc) => {
|
||||
const pane = global.tabList[props.tabIdx].panes[props.paneIdx] as FileTransferTabPane
|
||||
pane.path = loc
|
||||
|
|
@ -600,23 +530,7 @@ export function useFileTransfer (props: Props) {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
const type = data.from === 'local' ? 'upload' : 'download'
|
||||
const typeT = type === 'upload' ? t('upload') : t('download')
|
||||
const content = h('div', [
|
||||
h('div', `${locale.value === 'en' ? 'from' : '从'} ${props.target !== 'local' ? t('local') : t('cloud')} `),
|
||||
h('ol', data.path.map(v => v.split(/[/\\]/).pop()).map(v => h('li', v))),
|
||||
h('div', `${typeT} ${props.target === 'local' ? t('local') : t('cloud')} ${toPath}`)
|
||||
])
|
||||
Modal.confirm({
|
||||
title: t('confirmCreateTask', { type: typeT, more: locale.value === 'zh' ? ', 这是文件夹或者包含文件夹!' : ',which contains folders!' }),
|
||||
content,
|
||||
maskClosable: true,
|
||||
async onOk () {
|
||||
await global.createTaskRecordPaneIfNotExist(props.tabIdx)
|
||||
console.log('request createNewTask', { send_dirs: data.path, recv_dir: toPath, type })
|
||||
taskListStore.pendingBaiduyunTaskQueue.push({ send_dirs: data.path, recv_dir: toPath, type })
|
||||
}
|
||||
})
|
||||
// 原有的百度云
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -680,10 +594,10 @@ export function useFileItemActions (props: Props, { openNext }: { openNext: (fil
|
|||
try {
|
||||
spinning.value = true
|
||||
await setImgPath(file.fullpath) // 设置图像路径
|
||||
const btn = gradioApp().querySelector('#bd_hidden_img_update_trigger')! as HTMLButtonElement
|
||||
const btn = gradioApp().querySelector('#iib_hidden_img_update_trigger')! as HTMLButtonElement
|
||||
btn.click() // 触发图像组件更新
|
||||
ok(await genInfoCompleted(), 'genInfoCompleted timeout') // 等待消息生成完成
|
||||
const tabBtn = gradioApp().querySelector(`#bd_hidden_tab_${tab}`) as HTMLButtonElement
|
||||
const tabBtn = gradioApp().querySelector(`#iib_hidden_tab_${tab}`) as HTMLButtonElement
|
||||
tabBtn.click() // 触发粘贴
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import FileItem from './FileItem.vue'
|
|||
|
||||
const global = useGlobalStore()
|
||||
const props = defineProps<{
|
||||
target: 'local' | 'netdisk',
|
||||
target: 'local',
|
||||
tabIdx: number,
|
||||
paneIdx: number,
|
||||
/**
|
||||
|
|
@ -29,8 +29,8 @@ const props = defineProps<{
|
|||
*/
|
||||
stackKey?: string
|
||||
}>()
|
||||
const { installBaiduyunBin, installedBaiduyun, failedHint, baiduyunLoading,
|
||||
scroller, walkModePath, stackViewEl, props: _props, bduss, onLoginBtnClick, multiSelectedIdxs,
|
||||
const {
|
||||
scroller, walkModePath, stackViewEl, props: _props, multiSelectedIdxs,
|
||||
spinning
|
||||
} = useHookShareState().toRefs()
|
||||
const { currLocation, currPage, refresh, copyLocation, back, openNext, stack, to } = useLocation(props)
|
||||
|
|
@ -59,30 +59,8 @@ watch(() => props, () => {
|
|||
<template>
|
||||
<ASpin :spinning="spinning" size="large">
|
||||
<ASelect style="display: none;"></ASelect>
|
||||
<div v-if="props.target === 'netdisk' && (!installedBaiduyun || !global.user)" class="uninstalled-hint">
|
||||
<template v-if="!installedBaiduyun">
|
||||
<div>{{ $t('dependenciesNotInstalled') }}</div>
|
||||
<AButton type="primary" :loading="baiduyunLoading" @click="installBaiduyunBin">{{ $t('clickHere2install') }}
|
||||
</AButton>
|
||||
<p v-if="failedHint">{{ failedHint }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="bduss">
|
||||
<a-input v-model:value="bduss" style="width:300px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button @click="onLoginBtnClick" type="primary" :loading="baiduyunLoading">
|
||||
<template #icon>
|
||||
<login-outlined />
|
||||
</template>
|
||||
{{ $t('login') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</div>
|
||||
<div ref="stackViewEl" @dragover.prevent @drop.prevent="onDrop($event)" class="container" v-else>
|
||||
|
||||
<div ref="stackViewEl" @dragover.prevent @drop.prevent="onDrop($event)" class="container" >
|
||||
<AModal v-model:visible="showGenInfo" width="70vw" mask-closable @ok="showGenInfo = false">
|
||||
<template #cancelText />
|
||||
<ASkeleton active :loading="!q.isIdle">
|
||||
|
|
|
|||
|
|
@ -1,24 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { logout } from '@/api/user'
|
||||
import { t } from '@/i18n'
|
||||
import { useGlobalStore } from '@/store/useGlobalStore'
|
||||
import { useTaskListStore } from '@/store/useTaskListStore'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
import { FetchQueue } from 'vue3-ts-util'
|
||||
|
||||
const queue = reactive(new FetchQueue(-1, 0, 0, 'throw'))
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
const taskStore = useTaskListStore()
|
||||
const { user } = storeToRefs(globalStore)
|
||||
const onLogoutBtnClick = async () => {
|
||||
await queue.pushAction(logout).res
|
||||
user.value = undefined
|
||||
message.info(t('logoutSuccess'))
|
||||
}
|
||||
const langChanged = ref(false)
|
||||
const w = window
|
||||
</script>
|
||||
|
|
@ -28,10 +14,7 @@ const w = window
|
|||
<a-form-item :label="$t('useThumbnailPreview')">
|
||||
<a-switch v-model:checked="globalStore.enableThumbnail" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('pollingInterval')">
|
||||
<a-input-number v-model:value="taskStore.pollInterval" :min="0.5" :disabled="!taskStore.queue.isIdle" /> (s)
|
||||
<sub>{{ $t('smallerIntervalMeansMoreNetworkTraffic') }}</sub>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('gridThumbnailWidth')">
|
||||
<a-input-number v-model:value="globalStore.gridThumbnailSize" :min="256" :max="1024" /> (px)
|
||||
</a-form-item>
|
||||
|
|
@ -54,17 +37,6 @@ const w = window
|
|||
</div>
|
||||
<a-button type="primary" @click="w.location.reload()" v-if="langChanged" ghost>{{ t('langChangeReload') }}</a-button>
|
||||
</a-form-item>
|
||||
<template v-if="user">
|
||||
<a-form-item label="百度云已登录用户">
|
||||
{{ user.username }}
|
||||
<a-button @click="onLogoutBtnClick" :loading="!queue.isIdle">
|
||||
<template #icon>
|
||||
<logout-outlined />
|
||||
</template>
|
||||
登出
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { UploadTaskSummary } from '@/api'
|
||||
import { useGlobalStore } from '@/store/useGlobalStore'
|
||||
import { useTaskListStore } from '@/store/useTaskListStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { deepComputedEffect, type WithId } from 'vue3-ts-util'
|
||||
const props = defineProps<{ task: WithId<UploadTaskSummary>, idx: number }>()
|
||||
const emit = defineEmits<{ (t: 'update:task', v: WithId<UploadTaskSummary>): void }>()
|
||||
const task = deepComputedEffect({ get: () => props.task, set: v => emit('update:task', v) })
|
||||
const store = useTaskListStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const { showDirAutoCompletedIdx } = storeToRefs(store)
|
||||
|
||||
const addDir2task = (dir: string) => {
|
||||
if (task.value.type === 'download') {
|
||||
task.value.recv_dir = dir
|
||||
return
|
||||
}
|
||||
task.value.send_dirs.push(dir)
|
||||
}
|
||||
const colors = ['#f5222d', '#1890ff', '#ff3125', '#d46b08', '#007bff', '#52c41a', '#13c2c2', '#fa541c', '#eb2f96', '#2f54eb']
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="showDirAutoCompletedIdx === idx && globalStore.autoCompletedDirList.length" class="auto-completed-dirs">
|
||||
<a-tooltip v-for="item, tagIdx in globalStore.autoCompletedDirList" :key="item.dir" :title="item.dir + ' 点击添加'">
|
||||
<a-tag :visible="!task.send_dirs.includes(item.dir)" :color="colors[tagIdx % colors.length]"
|
||||
@click="addDir2task(item.dir)">{{ item.zh }}</a-tag>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.auto-completed-dirs {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { useTaskListStore } from '@/store/useTaskListStore'
|
||||
import { computed, watch, nextTick, ref } from 'vue'
|
||||
const props = defineProps<{
|
||||
logDetailId: string
|
||||
}>()
|
||||
const store = useTaskListStore()
|
||||
const logListEl = ref<HTMLDivElement>()
|
||||
const currList = computed(() => store.taskLogMap.get(props.logDetailId))
|
||||
watch(currList, async () => {
|
||||
await nextTick()
|
||||
const el = logListEl.value
|
||||
if (el) {
|
||||
el.scrollTop = el.scrollHeight
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="container" >
|
||||
<ul class="list" ref="logListEl" >
|
||||
<li v-for="log, idx in currList" :key="idx" >
|
||||
<pre>{{ log.log }}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
font-family: Consolas, Menlo, monospace;
|
||||
height: 90vh;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
margin: 16px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--zp-secondary-background);
|
||||
|
||||
color: var(--zp-primary);
|
||||
margin-left: 16px;
|
||||
list-style: none;
|
||||
font-size: 12px;
|
||||
|
||||
pre {
|
||||
margin: 2px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { key, pick } from '@/util'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { typedID, Task, SearchSelect, copy2clipboard } from 'vue3-ts-util'
|
||||
import { PlusOutlined, SyncOutlined, MinusCircleOutlined } from '@/icon'
|
||||
import { cancelTask, createBaiduYunTask, getUploadTasks, getUploadTaskTickStatus, removeTask, type UploadTaskSummary } from '@/api'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useTaskListStore } from '@/store/useTaskListStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import localPathShortcut from './localPathShortcut.vue'
|
||||
import { useGlobalStore } from '@/store/useGlobalStore'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const props = defineProps<{ tabIdx: number, paneIdx: number }>()
|
||||
|
||||
const ID = typedID<UploadTaskSummary>(true)
|
||||
const store = useTaskListStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const { tasks } = storeToRefs(store)
|
||||
const { showDirAutoCompletedIdx } = storeToRefs(store)
|
||||
const pollTaskMap = new Map<string, ReturnType<typeof createPollTask>>()
|
||||
const loadNum = ref(10)
|
||||
|
||||
onMounted(() => globalStore.openBaiduYunIfNotLogged(props.tabIdx, props.paneIdx))
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
pollTaskMap.forEach(v => v.clearTask())
|
||||
})
|
||||
const canProcessQueue = ref(false)
|
||||
watch([() => store.pendingBaiduyunTaskQueue, canProcessQueue], async ([q, can]) => {
|
||||
if (!q.length || !can) {
|
||||
return
|
||||
}
|
||||
console.log('processQueue', q)
|
||||
for (const task of q) {
|
||||
tasks.value.unshift(ID({ ...getEmptyTask(), ...task }))
|
||||
createNewTask(0).then(() => message.success('创建完成,在任务列表查看进度'))
|
||||
}
|
||||
store.pendingBaiduyunTaskQueue = []
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
onMounted(async () => {
|
||||
const resp = await getUploadTasks()
|
||||
tasks.value = uniqBy([...resp.tasks, ...tasks.value].map(ID), v => v.id) // 前后端合并
|
||||
.sort((a, b) => Date.parse(b.start_time) - Date.parse(a.start_time))
|
||||
.slice(0, 100)
|
||||
let runningTasks = tasks.value.filter(v => v.running)
|
||||
runningTasks.filter(task => !resp.tasks.find(beTask => beTask.id === task.id)).forEach(task => { // 在后端中没找到直接标记已完成,防止继续请求
|
||||
task.running = false
|
||||
})
|
||||
runningTasks = tasks.value.filter(v => v.running)
|
||||
if (runningTasks.length) {
|
||||
runningTasks.forEach(v => {
|
||||
createPollTask(v.id).completedTask.then(() => message.success(`${v.type === 'download' ? '下载' : '上传'}完成`))
|
||||
})
|
||||
}
|
||||
if (!tasks.value.length) {
|
||||
addEmptyTask()
|
||||
}
|
||||
canProcessQueue.value = true
|
||||
console.log('task record load')
|
||||
})
|
||||
|
||||
const getEmptyTask = () => ID({
|
||||
type: 'upload',
|
||||
send_dirs: [],
|
||||
recv_dir: '',
|
||||
id: '',
|
||||
running: false,
|
||||
start_time: '',
|
||||
n_failed_files: 0,
|
||||
n_files: 0,
|
||||
n_success_files: 0,
|
||||
canceled: false
|
||||
})
|
||||
|
||||
const addEmptyTask = () => {
|
||||
tasks.value.unshift(getEmptyTask())
|
||||
}
|
||||
|
||||
const createNewTask = async (idx: number) => {
|
||||
const task = tasks.value[idx]
|
||||
task.send_dirs = task.send_dirs.map(v => v.trim()).filter(v => v)
|
||||
task.recv_dir = task.recv_dir.trim()
|
||||
if (!(task.type === 'upload' ? task.recv_dir.startsWith('/') : task.send_dirs.every(v => v.startsWith('/')))) {
|
||||
return message.error('百度云的位置必须以 “/” 开头')
|
||||
}
|
||||
task.running = true
|
||||
task.n_files = 100
|
||||
const resp = await createBaiduYunTask(task)
|
||||
task.id = resp.id
|
||||
createPollTask(resp.id).completedTask.then(() => message.success(task.type === 'upload' ? '上传完成' : '下载完成'))
|
||||
}
|
||||
|
||||
const createPollTask = (id: string) => {
|
||||
store.taskLogMap.set(id, [])
|
||||
const task = Task.run({
|
||||
action: () => getUploadTaskTickStatus(id),
|
||||
pollInterval: store.pollInterval * 1000,
|
||||
validator (r) {
|
||||
store.taskLogMap.get(id)!.push(...r.tasks)
|
||||
const idx = tasks.value.findIndex(v => v.id === id)
|
||||
tasks.value[idx] = ID(r.task_summary)
|
||||
return !r.task_summary.running
|
||||
}
|
||||
})
|
||||
pollTaskMap.set(id, task)
|
||||
store.queue.pushAction(() => task.completedTask) // 使用queue来判断是否所有任务都已经完成
|
||||
return task
|
||||
}
|
||||
|
||||
const getIntPercent = (task: UploadTaskSummary) => parseInt((((task.n_failed_files + task.n_success_files) / task.n_files) * 100).toString())
|
||||
const isDone = (task: UploadTaskSummary) => !!task.id && !task.running && !task.canceled
|
||||
const isDisable = (task: UploadTaskSummary) => task.running || isDone(task)
|
||||
|
||||
const copyFrom = (idx: number) => {
|
||||
const prevTask = tasks.value[idx]
|
||||
tasks.value.unshift({
|
||||
...getEmptyTask(),
|
||||
...pick(prevTask, 'send_dirs', 'type', 'recv_dir')
|
||||
})
|
||||
message.success('复制完成,已添加到最前端')
|
||||
}
|
||||
|
||||
const openLogDetail = (idx: number) => {
|
||||
globalStore.openLogDetailInRight(props.tabIdx, tasks.value[idx].id)
|
||||
}
|
||||
|
||||
const cancel = async (idx: number) => {
|
||||
const task = tasks.value[idx]
|
||||
const { last_tick } = await cancelTask(task.id)
|
||||
store.taskLogMap.get(task.id)!.push(...last_tick.tasks)
|
||||
tasks.value[idx] = ID(last_tick.task_summary)
|
||||
pollTaskMap.get(task.id)?.clearTask()
|
||||
}
|
||||
|
||||
const remove = async (idx: number) => {
|
||||
const task = tasks.value[idx]
|
||||
tasks.value.splice(idx, 1)
|
||||
task.id && removeTask(task.id)
|
||||
message.success('删除完成')
|
||||
}
|
||||
|
||||
|
||||
const copy = (text: string) => {
|
||||
copy2clipboard(text, `复制 "${text}" 成功,粘贴使用"`)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel">
|
||||
|
||||
<div class="actions-bar">
|
||||
<a-button @click="copy('<#%Y-%m-%d#>')">复制日期占位符</a-button>
|
||||
<a-button @click="copy('<#%H-%M-%S#>')">复制时间占位符</a-button>
|
||||
<a-button @click="copy('<#%Y-%m-%d %H-%M-%S#>')">复制日期+时间占位符</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper" @click="showDirAutoCompletedIdx = -1">
|
||||
<a-select style="display: none" />
|
||||
<a-button @click="addEmptyTask" block style="border-radius: 8px;">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
添加一个任务
|
||||
</a-button>
|
||||
<div v-for="task, idx in tasks.slice(0, loadNum)" :key="key(task)" class="task-form">
|
||||
<div class="top-bar">
|
||||
|
||||
<a-tag color="success" v-if="isDone(task)">已完成</a-tag>
|
||||
<a-tag color="processing" v-if="task.running">{{ task.type === 'download' ? '下载' : '上传' }}中 <template #icon>
|
||||
<sync-outlined :spin="true" />
|
||||
</template></a-tag>
|
||||
<a-tag color="default" v-if="task.canceled">
|
||||
<template #icon>
|
||||
<minus-circle-outlined />
|
||||
</template>
|
||||
已取消
|
||||
</a-tag>
|
||||
<div class="flex-placeholder"></div>
|
||||
<div v-if="task.start_time">
|
||||
开始时间: {{ task.start_time }}
|
||||
</div>
|
||||
</div>
|
||||
<a-form layout="vertical" label-align="left">
|
||||
<a-form-item label="任务类型">
|
||||
<search-select v-model:value="task.type" :disabled="isDisable(task)" :options="['upload', 'download']"
|
||||
:conv="{ value: (v) => v, text: (v) => (v === 'upload' ? '上传' : '下载') }"></search-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="`发送的文件夹 (${task.type === 'upload' ? '本地' : '百度云'})`"
|
||||
@click.stop="task.type === 'upload' && (showDirAutoCompletedIdx = idx)">
|
||||
<a-textarea auto-size :disabled="isDisable(task)" :value="task.send_dirs.join()"
|
||||
@update:value="v => task.send_dirs = v.split(',')" allow-clear
|
||||
placeholder="发送文件的文件夹,多个文件夹使用逗号或者换行分隔。支持使用占位符例如stable-diffusion-webui最常用表示日期的<#%Y-%m-%d#>"></a-textarea>
|
||||
<local-path-shortcut v-if="task.type === 'upload'" :task="task" @update:task="v => tasks[idx] = v" :idx="idx" />
|
||||
|
||||
</a-form-item>
|
||||
<a-form-item :label="`接收的文件夹 (${task.type !== 'upload' ? '本地' : '百度云'})`">
|
||||
<a-input v-model:value="task.recv_dir" :disabled="isDisable(task)" allow-clear
|
||||
@click.stop="task.type === 'download' && (showDirAutoCompletedIdx = idx)"
|
||||
placeholder="用于接收的文件夹,支持使用占位符例如stable-diffusion-webui最常用表示日期的<#%Y-%m-%d#>"></a-input>
|
||||
<local-path-shortcut v-if="task.type === 'download'" :task="task" @update:task="v => tasks[idx] = v"
|
||||
:idx="idx" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="action-bar">
|
||||
<a-button @click="openLogDetail(idx)" v-if="store.taskLogMap.get(task.id)">查看详细日志</a-button>
|
||||
<a-button @click="copyFrom(idx)">复制该任务</a-button>
|
||||
<a-button @click="cancel(idx)" v-if="task.running" danger>取消任务</a-button>
|
||||
<a-button @click="remove(idx)" :disabled="task.running" danger>移除</a-button>
|
||||
<a-button type="primary" v-if="!isDone(task)" :loading="task.running" :disabled="task.running"
|
||||
@click="createNewTask(idx)">开始</a-button>
|
||||
</div>
|
||||
<a-progress v-if="task.running" :stroke-color="{
|
||||
from: '#108ee9',
|
||||
to: '#87d068'
|
||||
}" :percent="getIntPercent(task)" status="active" />
|
||||
</div>
|
||||
<a-button @click="loadNum += 5" v-if="loadNum < tasks.length" block style="border-radius: 8px;">
|
||||
继续加载
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.panel {
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
justify-content: space-between;
|
||||
|
||||
.actions-bar>* {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: calc(100vh - 128px);
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
|
||||
&>* {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-form {
|
||||
border-radius: 16px;
|
||||
background: var(--zp-secondary-background);
|
||||
padding: 16px;
|
||||
margin: 16px 8px;
|
||||
|
||||
.top-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 16px;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,20 +1,18 @@
|
|||
import type { GlobalConf, UploadTaskSummary } from '@/api'
|
||||
import type { GlobalConf} from '@/api'
|
||||
import type { UserInfo } from '@/api/user'
|
||||
import { i18n, t } from '@/i18n'
|
||||
import { getPreferredLang } from '@/i18n'
|
||||
import type { getAutoCompletedTagList } from '@/page/taskRecord/autoComplete'
|
||||
import type { ReturnTypeAsync } from '@/util'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
import { defineStore } from 'pinia'
|
||||
import { watch } from 'vue'
|
||||
import { nextTick } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { typedEventEmitter, type UniqueId, ID } from 'vue3-ts-util'
|
||||
|
||||
|
||||
interface OtherTabPane {
|
||||
type: 'auto-upload' | 'task-record' | 'empty' | 'log-detail' | 'global-setting' | 'tag-search'
|
||||
type: 'empty' | 'global-setting' | 'tag-search'
|
||||
name: string
|
||||
readonly key: string
|
||||
}
|
||||
|
|
@ -29,16 +27,9 @@ interface TagSearchMatchedImageGridTabPane {
|
|||
id: string
|
||||
}
|
||||
|
||||
interface LogDetailTabPane {
|
||||
type: 'log-detail'
|
||||
logDetailId: string
|
||||
name: string
|
||||
readonly key: string
|
||||
}
|
||||
|
||||
export interface FileTransferTabPane {
|
||||
type: 'local' | 'netdisk'
|
||||
target: 'local' | 'netdisk' // type 和target一致
|
||||
type: 'local' // | 'netdisk'
|
||||
target: 'local' // | 'netdisk' // type 和target一致
|
||||
name: string
|
||||
readonly key: string
|
||||
path?: string
|
||||
|
|
@ -46,7 +37,7 @@ export interface FileTransferTabPane {
|
|||
stackKey?: string
|
||||
}
|
||||
|
||||
export type TabPane = FileTransferTabPane | OtherTabPane | LogDetailTabPane | TagSearchMatchedImageGridTabPane
|
||||
export type TabPane = FileTransferTabPane | OtherTabPane | TagSearchMatchedImageGridTabPane
|
||||
|
||||
export interface Tab extends UniqueId {
|
||||
panes: TabPane[]
|
||||
|
|
@ -56,7 +47,6 @@ export interface Tab extends UniqueId {
|
|||
|
||||
export const useGlobalStore = defineStore('useGlobalStore', () => {
|
||||
const conf = ref<GlobalConf>()
|
||||
const user = ref<UserInfo>()
|
||||
const autoCompletedDirList = ref([] as ReturnTypeAsync<typeof getAutoCompletedTagList>)
|
||||
const enableThumbnail = ref(true)
|
||||
const stackViewSplit = ref(50)
|
||||
|
|
@ -80,24 +70,6 @@ export const useGlobalStore = defineStore('useGlobalStore', () => {
|
|||
}
|
||||
|
||||
|
||||
|
||||
const createTaskRecordPaneIfNotExist = async (tabIdx = 0) => {
|
||||
if (!tabList.value.map(v => v.panes).flat().find(v => v.type === 'task-record')) {
|
||||
tabList.value[tabIdx].panes.push({ type: 'task-record', key: uniqueId(), name: '任务记录' })
|
||||
}
|
||||
await nextTick()
|
||||
}
|
||||
const openLogDetailInRight = async (tabIdx: number, id: string) => {
|
||||
const tab = tabList.value[tabIdx + 1]
|
||||
const log: LogDetailTabPane = { type: 'log-detail', logDetailId: id, key: uniqueId(), name: `日志详情:${id.split('-')[0]}...` }
|
||||
if (!tab) {
|
||||
tabList.value.push(ID({ panes: [log], key: log.key }))
|
||||
} else {
|
||||
tab.key = log.key
|
||||
tab.panes.push(log)
|
||||
}
|
||||
}
|
||||
|
||||
const openTagSearchMatchedImageGridInRight = async (tabIdx: number, id: string, tagIds: number[]) => {
|
||||
let pane = tabList.value.map(v => v.panes).flat()
|
||||
.find(v => v.type === 'tag-search-matched-image-grid' && v.id === id) as TagSearchMatchedImageGridTabPane
|
||||
|
|
@ -130,20 +102,10 @@ export const useGlobalStore = defineStore('useGlobalStore', () => {
|
|||
const lang = ref(getPreferredLang())
|
||||
watch(lang, v => i18n.global.locale.value = v as any)
|
||||
|
||||
const openBaiduYunIfNotLogged = (tabIdx: number, paneIdx: number) => {
|
||||
if (!user.value) {
|
||||
message.info(t('loginPrompt'))
|
||||
const pane: FileTransferTabPane = { key: uniqueId(), type: 'netdisk', target: 'netdisk', name: t('baiduCloud') + ' ' + t('login') }
|
||||
tabList.value[tabIdx].panes[paneIdx] = pane
|
||||
tabList.value[tabIdx].key = pane.key
|
||||
}
|
||||
}
|
||||
|
||||
const longPressOpenContextMenu = ref(false)
|
||||
const baiduNetdiskPageOpened = ref('')
|
||||
return {
|
||||
lang,
|
||||
user,
|
||||
tabList,
|
||||
conf,
|
||||
autoCompletedDirList,
|
||||
|
|
@ -153,15 +115,11 @@ export const useGlobalStore = defineStore('useGlobalStore', () => {
|
|||
dragingTab,
|
||||
saveRecord,
|
||||
recent, lastTabListRecord,
|
||||
openLogDetailInRight,
|
||||
gridThumbnailSize,
|
||||
largeGridThumbnailSize,
|
||||
createTaskRecordPaneIfNotExist,
|
||||
openBaiduYunIfNotLogged,
|
||||
longPressOpenContextMenu,
|
||||
baiduNetdiskPageOpened,
|
||||
openTagSearchMatchedImageGridInRight,
|
||||
...typedEventEmitter<{ createNewTask: Partial<UploadTaskSummary> }>()
|
||||
baiduNetdiskPageOpened : ref('')
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
import { checkBaiduyunExists, type UploadTaskSummary, type UploadTaskTickStatus } from '@/api'
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { FetchQueue } from 'vue3-ts-util'
|
||||
import type { WithId } from 'vue3-ts-util'
|
||||
|
||||
export const useTaskListStore = defineStore('useTaskListStore', () => {
|
||||
const taskLogMap = ref(new Map<string, UploadTaskTickStatus[]>())
|
||||
const queue = reactive(new FetchQueue())
|
||||
const pollInterval = ref(3)
|
||||
const tasks = ref<WithId<UploadTaskSummary>[]>([])
|
||||
/**
|
||||
* 添加进去等待taskRecord处理
|
||||
*/
|
||||
const pendingBaiduyunTaskQueue = reactive([] as Partial<UploadTaskSummary>[])
|
||||
const showDirAutoCompletedIdx = ref(-1)
|
||||
const baiduyunInstalled = ref(null as null | Promise<boolean>)
|
||||
const checkBaiduyunInstalled = async () => {
|
||||
if (baiduyunInstalled.value === null) {
|
||||
baiduyunInstalled.value = checkBaiduyunExists()
|
||||
}
|
||||
return baiduyunInstalled.value
|
||||
}
|
||||
|
||||
return {
|
||||
checkBaiduyunInstalled,
|
||||
baiduyunInstalled,
|
||||
pollInterval,
|
||||
taskLogMap,
|
||||
queue,
|
||||
tasks,
|
||||
showDirAutoCompletedIdx,
|
||||
pendingBaiduyunTaskQueue
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
paths: ['pollInterval', 'tasks'],
|
||||
key: 'useTaskListStore-v0.0.1'
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue