Spaces:
Sleeping
Sleeping
Upload 21 files
Browse files- backend/app.py +0 -110
- frontend/assets/app.js +25 -201
- frontend/index.html +0 -27
backend/app.py
CHANGED
|
@@ -62,116 +62,6 @@ def update_config(update: ConfigUpdate) -> Dict[str, Any]:
|
|
| 62 |
return cfg.model_dump()
|
| 63 |
|
| 64 |
|
| 65 |
-
@app.get("/api/browse-dir")
|
| 66 |
-
def api_browse_dir(path: str = "") -> Dict[str, Any]:
|
| 67 |
-
"""
|
| 68 |
-
浏览服务器上的目录,返回子目录列表。
|
| 69 |
-
用于网页版目录选择器,支持手机和电脑。
|
| 70 |
-
"""
|
| 71 |
-
# 默认从用户主目录或根目录开始
|
| 72 |
-
if not path:
|
| 73 |
-
if platform.system() == "Windows":
|
| 74 |
-
# Windows: 列出所有驱动器
|
| 75 |
-
import string
|
| 76 |
-
drives = []
|
| 77 |
-
for letter in string.ascii_uppercase:
|
| 78 |
-
drive_path = f"{letter}:\\"
|
| 79 |
-
if os.path.exists(drive_path):
|
| 80 |
-
drives.append({"name": f"{letter}:", "path": drive_path, "type": "drive"})
|
| 81 |
-
return {"current": "", "parent": "", "items": drives, "is_root": True, "is_drive_list": True}
|
| 82 |
-
else:
|
| 83 |
-
# Linux/HF Space 环境:使用主目录
|
| 84 |
-
home_dir = os.path.expanduser("~")
|
| 85 |
-
# 如果是 HF Space 环境,检查 /home/user 或工作目录
|
| 86 |
-
if os.environ.get("SPACE_ID"):
|
| 87 |
-
# HF Space 通常在 /home/user 目录
|
| 88 |
-
if os.path.exists("/home/user"):
|
| 89 |
-
path = "/home/user"
|
| 90 |
-
else:
|
| 91 |
-
path = home_dir
|
| 92 |
-
else:
|
| 93 |
-
path = home_dir
|
| 94 |
-
|
| 95 |
-
# 规范化路径
|
| 96 |
-
try:
|
| 97 |
-
p = Path(path).resolve()
|
| 98 |
-
if not p.exists():
|
| 99 |
-
raise HTTPException(status_code=404, detail=f"路径不存在: {path}")
|
| 100 |
-
if not p.is_dir():
|
| 101 |
-
raise HTTPException(status_code=400, detail=f"不是目录: {path}")
|
| 102 |
-
except Exception as e:
|
| 103 |
-
raise HTTPException(status_code=400, detail=str(e))
|
| 104 |
-
|
| 105 |
-
# 获取父目录
|
| 106 |
-
parent = str(p.parent) if p.parent != p else ""
|
| 107 |
-
|
| 108 |
-
# Windows根目录特殊处理
|
| 109 |
-
is_root = False
|
| 110 |
-
if platform.system() == "Windows":
|
| 111 |
-
# 如果是驱动器根目录 (如 C:\),parent设为空表示返回驱动器列表
|
| 112 |
-
if len(str(p)) <= 3: # C:\ 或类似
|
| 113 |
-
parent = ""
|
| 114 |
-
is_root = True
|
| 115 |
-
else:
|
| 116 |
-
if str(p) == "/":
|
| 117 |
-
parent = ""
|
| 118 |
-
is_root = True
|
| 119 |
-
|
| 120 |
-
# 列出子目录
|
| 121 |
-
items = []
|
| 122 |
-
try:
|
| 123 |
-
for item in sorted(p.iterdir()):
|
| 124 |
-
if item.is_dir():
|
| 125 |
-
try:
|
| 126 |
-
# 尝试访问目录以确保有权限
|
| 127 |
-
list(item.iterdir())
|
| 128 |
-
items.append({
|
| 129 |
-
"name": item.name,
|
| 130 |
-
"path": str(item),
|
| 131 |
-
"type": "folder"
|
| 132 |
-
})
|
| 133 |
-
except PermissionError:
|
| 134 |
-
# 无权限访问的目录仍然显示,但标记为不可访问
|
| 135 |
-
items.append({
|
| 136 |
-
"name": item.name,
|
| 137 |
-
"path": str(item),
|
| 138 |
-
"type": "folder",
|
| 139 |
-
"accessible": False
|
| 140 |
-
})
|
| 141 |
-
except Exception:
|
| 142 |
-
pass
|
| 143 |
-
except PermissionError:
|
| 144 |
-
raise HTTPException(status_code=403, detail=f"无权限访问: {path}")
|
| 145 |
-
except Exception as e:
|
| 146 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 147 |
-
|
| 148 |
-
return {
|
| 149 |
-
"current": str(p),
|
| 150 |
-
"parent": parent,
|
| 151 |
-
"items": items,
|
| 152 |
-
"is_root": is_root
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
@app.post("/api/create-dir")
|
| 157 |
-
def api_create_dir(payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 158 |
-
"""
|
| 159 |
-
创建新目录
|
| 160 |
-
"""
|
| 161 |
-
path = (payload or {}).get("path", "")
|
| 162 |
-
if not path:
|
| 163 |
-
raise HTTPException(status_code=400, detail="未提供路径")
|
| 164 |
-
|
| 165 |
-
try:
|
| 166 |
-
p = Path(path)
|
| 167 |
-
p.mkdir(parents=True, exist_ok=True)
|
| 168 |
-
return {"ok": True, "path": str(p.resolve())}
|
| 169 |
-
except PermissionError:
|
| 170 |
-
raise HTTPException(status_code=403, detail=f"无权限创建目录: {path}")
|
| 171 |
-
except Exception as e:
|
| 172 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 173 |
-
|
| 174 |
-
|
| 175 |
@app.post("/api/open-dir")
|
| 176 |
def api_open_dir(payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 177 |
"""
|
|
|
|
| 62 |
return cfg.model_dump()
|
| 63 |
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
@app.post("/api/open-dir")
|
| 66 |
def api_open_dir(payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 67 |
"""
|
frontend/assets/app.js
CHANGED
|
@@ -972,215 +972,45 @@
|
|
| 972 |
ensureCountField("tab-i2i", "i2i-count");
|
| 973 |
ensureCountField("tab-inpaint", "inpaint-count");
|
| 974 |
|
| 975 |
-
// =====
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
try {
|
| 981 |
-
const res = await fetch(`/api/browse-dir?path=${encodeURIComponent(path)}`);
|
| 982 |
-
if (!res.ok) {
|
| 983 |
-
const body = await res.json().catch(() => ({}));
|
| 984 |
-
throw new Error(body?.detail || "加载目录失败");
|
| 985 |
-
}
|
| 986 |
-
return await res.json();
|
| 987 |
-
} catch (e) {
|
| 988 |
-
throw e;
|
| 989 |
-
}
|
| 990 |
-
}
|
| 991 |
-
|
| 992 |
-
function renderDirList(data) {
|
| 993 |
-
const listEl = byId("dir-list");
|
| 994 |
-
const pathInput = byId("dir-current-path");
|
| 995 |
-
if (!listEl || !pathInput) return;
|
| 996 |
-
|
| 997 |
-
currentBrowsePath = data.current || "";
|
| 998 |
-
isDriveListMode = !!data.is_drive_list; // 是否在驱动器列表模式
|
| 999 |
-
pathInput.value = currentBrowsePath || (isDriveListMode ? "(请选择驱动器)" : "");
|
| 1000 |
-
|
| 1001 |
-
if (!data.items || data.items.length === 0) {
|
| 1002 |
-
listEl.innerHTML = '<div class="dir-browser-empty">此目录为空或无子目录</div>';
|
| 1003 |
-
return;
|
| 1004 |
-
}
|
| 1005 |
-
|
| 1006 |
-
listEl.innerHTML = data.items.map(item => {
|
| 1007 |
-
const icon = item.type === "drive" ? "💾" : "📁";
|
| 1008 |
-
const accessible = item.accessible !== false;
|
| 1009 |
-
const cls = accessible ? "dir-item" : "dir-item inaccessible";
|
| 1010 |
-
return `<div class="${cls}" data-path="${item.path}" data-accessible="${accessible}">
|
| 1011 |
-
<span class="dir-item-icon">${icon}</span>
|
| 1012 |
-
<span class="dir-item-name">${item.name}</span>
|
| 1013 |
-
</div>`;
|
| 1014 |
-
}).join("");
|
| 1015 |
-
|
| 1016 |
-
// 绑定点击事件
|
| 1017 |
-
listEl.querySelectorAll(".dir-item").forEach(el => {
|
| 1018 |
-
el.addEventListener("click", async () => {
|
| 1019 |
-
if (el.dataset.accessible === "false") {
|
| 1020 |
-
toast("无权限访问此目录", "error");
|
| 1021 |
-
return;
|
| 1022 |
-
}
|
| 1023 |
-
const p = el.dataset.path;
|
| 1024 |
-
if (p) {
|
| 1025 |
-
try {
|
| 1026 |
-
loading.show();
|
| 1027 |
-
const data = await loadDirList(p);
|
| 1028 |
-
renderDirList(data);
|
| 1029 |
-
} catch (e) {
|
| 1030 |
-
toast(String(e?.message || e), "error");
|
| 1031 |
-
} finally {
|
| 1032 |
-
loading.hide();
|
| 1033 |
-
}
|
| 1034 |
-
}
|
| 1035 |
-
});
|
| 1036 |
-
});
|
| 1037 |
-
}
|
| 1038 |
-
|
| 1039 |
-
function openDirBrowser() {
|
| 1040 |
-
const modal = byId("dir-browser-modal");
|
| 1041 |
-
if (modal) {
|
| 1042 |
-
modal.classList.remove("hidden");
|
| 1043 |
-
// 加载当前配置的目录或根目录
|
| 1044 |
-
const currentDir = byId("cfg-output-dir")?.value || "";
|
| 1045 |
-
loading.show();
|
| 1046 |
-
loadDirList(currentDir)
|
| 1047 |
-
.then(data => renderDirList(data))
|
| 1048 |
-
.catch(e => {
|
| 1049 |
-
// 如果当前目录无效,加载根目录
|
| 1050 |
-
return loadDirList("").then(data => renderDirList(data));
|
| 1051 |
-
})
|
| 1052 |
-
.catch(e => toast(String(e?.message || e), "error"))
|
| 1053 |
-
.finally(() => loading.hide());
|
| 1054 |
-
}
|
| 1055 |
-
}
|
| 1056 |
-
|
| 1057 |
-
function closeDirBrowser() {
|
| 1058 |
-
const modal = byId("dir-browser-modal");
|
| 1059 |
-
if (modal) modal.classList.add("hidden");
|
| 1060 |
-
}
|
| 1061 |
-
|
| 1062 |
-
// 目录浏览器事件绑定
|
| 1063 |
-
const dirCloseBtn = byId("dir-browser-close");
|
| 1064 |
-
if (dirCloseBtn) dirCloseBtn.addEventListener("click", closeDirBrowser);
|
| 1065 |
-
|
| 1066 |
-
const dirCancelBtn = byId("dir-cancel");
|
| 1067 |
-
if (dirCancelBtn) dirCancelBtn.addEventListener("click", closeDirBrowser);
|
| 1068 |
-
|
| 1069 |
-
const dirConfirmBtn = byId("dir-confirm");
|
| 1070 |
-
if (dirConfirmBtn) {
|
| 1071 |
-
dirConfirmBtn.addEventListener("click", () => {
|
| 1072 |
-
// 检查是否在驱动器列表模式(没有选择具体目录)
|
| 1073 |
-
if (isDriveListMode || !currentBrowsePath) {
|
| 1074 |
-
toast("请先进入一个目录再选择", "error");
|
| 1075 |
-
return;
|
| 1076 |
-
}
|
| 1077 |
-
|
| 1078 |
-
const input = byId("cfg-output-dir");
|
| 1079 |
-
if (input) {
|
| 1080 |
-
input.value = currentBrowsePath;
|
| 1081 |
-
autoSaveConfig(); // 自动保存
|
| 1082 |
-
toast("已选择保存目录: " + currentBrowsePath, "success");
|
| 1083 |
-
}
|
| 1084 |
-
closeDirBrowser();
|
| 1085 |
-
});
|
| 1086 |
-
}
|
| 1087 |
-
|
| 1088 |
-
const dirGoUpBtn = byId("dir-go-up");
|
| 1089 |
-
if (dirGoUpBtn) {
|
| 1090 |
-
dirGoUpBtn.addEventListener("click", async () => {
|
| 1091 |
-
try {
|
| 1092 |
-
loading.show();
|
| 1093 |
-
// 获取当前目录信息以获取父目录
|
| 1094 |
-
const data = await loadDirList(currentBrowsePath);
|
| 1095 |
-
if (data.parent) {
|
| 1096 |
-
const parentData = await loadDirList(data.parent);
|
| 1097 |
-
renderDirList(parentData);
|
| 1098 |
-
} else {
|
| 1099 |
-
// 已经是根目录,显示驱动器列表(Windows)或根目录
|
| 1100 |
-
const rootData = await loadDirList("");
|
| 1101 |
-
renderDirList(rootData);
|
| 1102 |
-
}
|
| 1103 |
-
} catch (e) {
|
| 1104 |
-
toast(String(e?.message || e), "error");
|
| 1105 |
-
} finally {
|
| 1106 |
-
loading.hide();
|
| 1107 |
-
}
|
| 1108 |
-
});
|
| 1109 |
-
}
|
| 1110 |
-
|
| 1111 |
-
const dirGoHomeBtn = byId("dir-go-home");
|
| 1112 |
-
if (dirGoHomeBtn) {
|
| 1113 |
-
dirGoHomeBtn.addEventListener("click", async () => {
|
| 1114 |
-
try {
|
| 1115 |
-
loading.show();
|
| 1116 |
-
const data = await loadDirList("");
|
| 1117 |
-
renderDirList(data);
|
| 1118 |
-
} catch (e) {
|
| 1119 |
-
toast(String(e?.message || e), "error");
|
| 1120 |
-
} finally {
|
| 1121 |
-
loading.hide();
|
| 1122 |
-
}
|
| 1123 |
-
});
|
| 1124 |
-
}
|
| 1125 |
-
|
| 1126 |
-
const dirNewFolderBtn = byId("dir-new-folder");
|
| 1127 |
-
if (dirNewFolderBtn) {
|
| 1128 |
-
dirNewFolderBtn.addEventListener("click", async () => {
|
| 1129 |
-
const folderName = prompt("请输入新文件夹名称:");
|
| 1130 |
-
if (!folderName || !folderName.trim()) return;
|
| 1131 |
-
|
| 1132 |
-
const newPath = currentBrowsePath
|
| 1133 |
-
? `${currentBrowsePath}/${folderName.trim()}`.replace(/\\/g, "/")
|
| 1134 |
-
: folderName.trim();
|
| 1135 |
-
|
| 1136 |
try {
|
| 1137 |
-
|
| 1138 |
-
|
| 1139 |
-
method: "POST",
|
| 1140 |
-
headers: { "Content-Type": "application/json" },
|
| 1141 |
-
body: JSON.stringify({ path: newPath }),
|
| 1142 |
});
|
| 1143 |
-
|
| 1144 |
-
|
| 1145 |
-
|
|
|
|
|
|
|
|
|
|
| 1146 |
}
|
| 1147 |
-
toast("文件夹创建成功", "success");
|
| 1148 |
-
// 刷新当前目录
|
| 1149 |
-
const data = await loadDirList(currentBrowsePath);
|
| 1150 |
-
renderDirList(data);
|
| 1151 |
} catch (e) {
|
| 1152 |
-
|
| 1153 |
-
|
| 1154 |
-
|
|
|
|
| 1155 |
}
|
| 1156 |
-
}
|
| 1157 |
-
|
| 1158 |
-
|
| 1159 |
-
|
| 1160 |
-
|
| 1161 |
-
if (modalOverlay) {
|
| 1162 |
-
modalOverlay.addEventListener("click", closeDirBrowser);
|
| 1163 |
}
|
| 1164 |
|
| 1165 |
-
// 选择保存目录按钮
|
| 1166 |
const selOutBtn = byId("btn-select-output-dir");
|
| 1167 |
if (selOutBtn) {
|
| 1168 |
-
selOutBtn.addEventListener("click",
|
| 1169 |
-
openDirBrowser();
|
| 1170 |
-
});
|
| 1171 |
}
|
| 1172 |
|
| 1173 |
// 打开保存目录按钮
|
| 1174 |
const openOutBtn = byId("btn-open-output-dir");
|
| 1175 |
if (openOutBtn) {
|
| 1176 |
openOutBtn.addEventListener("click", async () => {
|
| 1177 |
-
// 移动端检测:手机无法打开服务器上的目录,但可以浏览
|
| 1178 |
-
if (isMobileDevice()) {
|
| 1179 |
-
// 手机端打开目录浏览器查看
|
| 1180 |
-
openDirBrowser();
|
| 1181 |
-
return;
|
| 1182 |
-
}
|
| 1183 |
-
|
| 1184 |
try {
|
| 1185 |
loading.show();
|
| 1186 |
const p = byId("cfg-output-dir")?.value || "";
|
|
@@ -1195,13 +1025,7 @@
|
|
| 1195 |
});
|
| 1196 |
if (!res.ok) {
|
| 1197 |
const body = await res.json().catch(() => ({}));
|
| 1198 |
-
|
| 1199 |
-
if (isHF) {
|
| 1200 |
-
toast("HF 环境不支持打开系统目录", "info");
|
| 1201 |
-
} else {
|
| 1202 |
-
throw new Error(body?.detail || "打开目录失败");
|
| 1203 |
-
}
|
| 1204 |
-
return;
|
| 1205 |
}
|
| 1206 |
toast("已打开保存目录", "success");
|
| 1207 |
} catch (e) {
|
|
|
|
| 972 |
ensureCountField("tab-i2i", "i2i-count");
|
| 973 |
ensureCountField("tab-inpaint", "inpaint-count");
|
| 974 |
|
| 975 |
+
// ===== 选择保存目录 =====
|
| 976 |
+
// 使用 File System Access API(支持的浏览器)或手动输入
|
| 977 |
+
async function selectOutputDirectory() {
|
| 978 |
+
// 尝试使用浏览器原生目录选择器
|
| 979 |
+
if ('showDirectoryPicker' in window) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 980 |
try {
|
| 981 |
+
const dirHandle = await window.showDirectoryPicker({
|
| 982 |
+
mode: 'readwrite'
|
|
|
|
|
|
|
|
|
|
| 983 |
});
|
| 984 |
+
// 获取目录名称作为路径提示
|
| 985 |
+
const input = byId("cfg-output-dir");
|
| 986 |
+
if (input) {
|
| 987 |
+
input.value = dirHandle.name;
|
| 988 |
+
autoSaveConfig();
|
| 989 |
+
toast("已选择目录: " + dirHandle.name + "(注意:这是本地目录名,服务器需手动填写完整路径)", "info");
|
| 990 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 991 |
} catch (e) {
|
| 992 |
+
// 用户取消或不支持
|
| 993 |
+
if (e.name !== 'AbortError') {
|
| 994 |
+
toast("请在输入框中直接填写服务器保存路径", "info");
|
| 995 |
+
}
|
| 996 |
}
|
| 997 |
+
} else {
|
| 998 |
+
// 不支持目录选择API,提示用户手动输入
|
| 999 |
+
toast("请在输入框中直接填写保存目录路径", "info");
|
| 1000 |
+
byId("cfg-output-dir")?.focus();
|
| 1001 |
+
}
|
|
|
|
|
|
|
| 1002 |
}
|
| 1003 |
|
| 1004 |
+
// 选择保存目录按钮
|
| 1005 |
const selOutBtn = byId("btn-select-output-dir");
|
| 1006 |
if (selOutBtn) {
|
| 1007 |
+
selOutBtn.addEventListener("click", selectOutputDirectory);
|
|
|
|
|
|
|
| 1008 |
}
|
| 1009 |
|
| 1010 |
// 打开保存目录按钮
|
| 1011 |
const openOutBtn = byId("btn-open-output-dir");
|
| 1012 |
if (openOutBtn) {
|
| 1013 |
openOutBtn.addEventListener("click", async () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1014 |
try {
|
| 1015 |
loading.show();
|
| 1016 |
const p = byId("cfg-output-dir")?.value || "";
|
|
|
|
| 1025 |
});
|
| 1026 |
if (!res.ok) {
|
| 1027 |
const body = await res.json().catch(() => ({}));
|
| 1028 |
+
throw new Error(body?.detail || "打开目录失败");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1029 |
}
|
| 1030 |
toast("已打开保存目录", "success");
|
| 1031 |
} catch (e) {
|
frontend/index.html
CHANGED
|
@@ -500,33 +500,6 @@
|
|
| 500 |
<div class="loading-text">处理中...</div>
|
| 501 |
</div>
|
| 502 |
|
| 503 |
-
<!-- 目录浏览器模态框 -->
|
| 504 |
-
<div id="dir-browser-modal" class="modal hidden">
|
| 505 |
-
<div class="modal-overlay"></div>
|
| 506 |
-
<div class="modal-content dir-browser">
|
| 507 |
-
<div class="modal-header">
|
| 508 |
-
<span class="modal-title">选择保存目录</span>
|
| 509 |
-
<button class="modal-close" id="dir-browser-close">×</button>
|
| 510 |
-
</div>
|
| 511 |
-
<div class="dir-browser-path">
|
| 512 |
-
<span>当前路径:</span>
|
| 513 |
-
<input type="text" id="dir-current-path" readonly />
|
| 514 |
-
</div>
|
| 515 |
-
<div class="dir-browser-actions">
|
| 516 |
-
<button id="dir-go-up" class="dir-action-btn">⬆ 上级目录</button>
|
| 517 |
-
<button id="dir-go-home" class="dir-action-btn">🏠 主目录</button>
|
| 518 |
-
<button id="dir-new-folder" class="dir-action-btn">📁 新建文件夹</button>
|
| 519 |
-
</div>
|
| 520 |
-
<div class="dir-browser-list" id="dir-list">
|
| 521 |
-
<!-- 目录列表将动态填充 -->
|
| 522 |
-
</div>
|
| 523 |
-
<div class="dir-browser-footer">
|
| 524 |
-
<button id="dir-cancel" class="secondary">取消</button>
|
| 525 |
-
<button id="dir-confirm" class="primary">选择此目录</button>
|
| 526 |
-
</div>
|
| 527 |
-
</div>
|
| 528 |
-
</div>
|
| 529 |
-
|
| 530 |
<audio id="sound-player" src="" preload="auto"></audio>
|
| 531 |
<script src="assets/app.js"></script>
|
| 532 |
</body>
|
|
|
|
| 500 |
<div class="loading-text">处理中...</div>
|
| 501 |
</div>
|
| 502 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
<audio id="sound-player" src="" preload="auto"></audio>
|
| 504 |
<script src="assets/app.js"></script>
|
| 505 |
</body>
|