支持快速添加一些常用的文件夹

pull/1/head
zanllp 2023-03-12 20:17:25 +08:00
parent a7804c5102
commit 510a00db32
14 changed files with 352 additions and 192 deletions

View File

@ -6,8 +6,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="/baidu_netdisk/fe-static/assets/index-9de6dddf.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-83870f85.css">
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-521b6f7a.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-3fdcc1a6.css">
</head>
<body>
<div id="zanllp_dev_gradio_fe"></div>

View File

@ -1,5 +1,6 @@
import asyncio
import datetime
import os
from typing import List, Dict, Union, Literal
import uuid
import subprocess
@ -72,7 +73,7 @@ class BaiduyunTask:
process = await asyncio.create_subprocess_exec(
bin_file_path,
type,
*str(send_dirs).split(","),
*process_path_arr(str(send_dirs).split(",")),
recv_dir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@ -92,3 +93,18 @@ class BaiduyunTask:
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 result

View File

@ -212,7 +212,7 @@ def get_default_conf():
)
upload_dir = "/stable-diffusion-upload"
return {
"output_dirs": outputs_dirs,
#"output_dirs": outputs_dirs,
"upload_dir": upload_dir,
}
@ -273,6 +273,13 @@ def baidu_netdisk_api(_: gr.Blocks, app: FastAPI):
@app.get(f"{pre}/hello")
async def greeting():
return "hello"
@app.get(f'{pre}/global_setting')
async def global_setting():
return {
"global_setting": opts.data,
"default_conf": get_default_conf()
}
class BaiduyunUploadDownloadReq(BaseModel):
type: Literal["upload", "download"]

1
vue/components.d.ts vendored
View File

@ -17,6 +17,7 @@ declare module '@vue/runtime-core' {
ASelect: typeof import('ant-design-vue/es')['Select']
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']
}

File diff suppressed because one or more lines are too long

171
vue/dist/assets/index-521b6f7a.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
vue/dist/index.html vendored
View File

@ -5,8 +5,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="/baidu_netdisk/fe-static/assets/index-9de6dddf.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-83870f85.css">
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-521b6f7a.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-3fdcc1a6.css">
</head>
<body>
<div id="zanllp_dev_gradio_fe"></div>

View File

@ -24,8 +24,9 @@ const percent = computed(() => !store.splitView.open ? 100 : store.splitView.per
</split-view>
</div>
</template>
<style scoped>
<style scoped lang="scss">
.split-view-container {
height: 70vh;
height: 95vh; // todo
width: 95%;
}
</style>

View File

@ -1,4 +1,5 @@
import axios from 'axios'
import type { GlobalSettingPart } from './type'
const axiosInst = axios.create({
baseURL: '/baidu_netdisk'
})
@ -118,3 +119,13 @@ export const getUploadTasks = async () => {
tasks: UploadTaskSummary[]
}
}
export const getGlobalSetting = async () => {
const resp = await axiosInst.get('/global_setting')
return resp.data as {
global_setting: GlobalSettingPart,
default_conf: {
upload_dir: string
}
}
}

31
vue/src/api/type.ts Normal file
View File

@ -0,0 +1,31 @@
export interface GlobalSettingPart {
outdir_samples: string;
outdir_txt2img_samples: string;
outdir_img2img_samples: string;
outdir_extras_samples: string;
outdir_grids: string;
outdir_txt2img_grids: string;
outdir_img2img_grids: string;
outdir_save: string;
dataset_filename_word_regex: string;
dataset_filename_join_string: string;
sd_model_checkpoint: string;
sd_vae: string;
img2img_background_color: string;
deepbooru_filter_tags: string;
sd_hypernetwork: string;
font: string;
quicksettings: string;
ui_reorder: string;
ui_extra_networks_tab_reorder: string;
localization: string;
show_progress_type: string;
live_preview_content: string;
ddim_discretize: string;
sd_checkpoint_hash: string;
sd_lora: string;
control_net_model_config: string;
control_net_model_adapter_config: string;
control_net_models_path: string;
additional_networks_extra_lora_path: string;
}

View File

@ -0,0 +1,45 @@
import type { getGlobalSetting } from '@/api'
import { pick, type ReturnTypeAsync } from '@/util'
export const getAutoCompletedTagList = ({ global_setting }: ReturnTypeAsync<typeof getGlobalSetting>) => {
const picked = pick(global_setting,
'additional_networks_extra_lora_path',
'outdir_grids',
'outdir_extras_samples',
'outdir_img2img_grids',
'outdir_img2img_samples',
'outdir_grids',
'outdir_extras_samples',
'outdir_samples',
'outdir_txt2img_grids',
'outdir_txt2img_samples',
'outdir_save'
)
const allTag = {
...picked,
'embeddings': 'embeddings',
'hypernetworks': 'models/hypernetworks'
}
type Keys = keyof (typeof allTag)
const cnMap: Record<Keys, string> = {
additional_networks_extra_lora_path: '扫描 LoRA 模型的附加目录',
outdir_grids: '宫格图的输出目录',
outdir_extras_samples: '附加功能选项卡的输出目录',
outdir_img2img_grids: '图生图网格文件夹',
outdir_img2img_samples: '图生图的输出目录',
outdir_samples: '图像的输出目录',
outdir_txt2img_samples: '文生图的输出目录',
outdir_txt2img_grids: '文生图宫格的输出目录',
hypernetworks: '超网络模型的路径',
outdir_save: '使用“保存”按钮保存图像的目录',
embeddings: 'Embedding的文件夹'
}
return Object.keys(cnMap).map((k) => {
const key = k as Keys
return {
key,
zh: cnMap[key],
dir: allTag[key]
}
})
}

View File

@ -1,17 +1,23 @@
<script setup lang="ts">
import { key } from '@/util'
import { key, pick } from '@/util'
import { onMounted, ref } from 'vue'
import { SearchSelect, type WithId, typedID, Task } from 'vue3-ts-util'
import { type WithId, typedID, Task } from 'vue3-ts-util'
import { PlusOutlined, SyncOutlined } from '@/icon'
import { createBaiduYunTask, getUploadTasks, getUploadTaskTickStatus, type UploadTaskSummary } from '@/api'
import { createBaiduYunTask, getGlobalSetting, getUploadTasks, getUploadTaskTickStatus, type UploadTaskSummary } from '@/api'
import { message } from 'ant-design-vue'
import { pick } from 'lodash-es'
import { useTaskListStore } from '@/store/useTaskListStore'
import { getAutoCompletedTagList } from './autoComplete'
const tasks = ref<WithId<UploadTaskSummary>[]>([])
const ID = typedID<UploadTaskSummary>(true)
const store = useTaskListStore()
const autoCompletedDirList = ref([] as ReturnType<typeof getAutoCompletedTagList>)
const showDirAutoCompletedIdx = ref(-1)
onMounted(async () => {
getGlobalSetting().then((resp) => {
autoCompletedDirList.value = getAutoCompletedTagList(resp).filter(v => v.dir.trim())
})
const resp = await getUploadTasks()
tasks.value = resp.tasks.map(ID)
const runningTasks = tasks.value.filter(v => v.running)
@ -43,6 +49,11 @@ const addEmptyTask = () => {
const createNewTask = async (idx: number) => {
const task = tasks.value[idx]
task.send_dirs = task.send_dirs.split(/,\n/).map(v => v.trim()).filter(v => v).join()
task.recv_dir = task.recv_dir.trim()
if (!task.recv_dir.startsWith('/')) {
return message.error('百度云接收位置必须以 “/” 开头')
}
task.running = true
task.n_files = 100
const resp = await createBaiduYunTask(task)
@ -72,7 +83,7 @@ const copyFrom = (idx: number) => {
const prevTask = tasks.value[idx]
tasks.value.unshift({
...getEmptyTask(),
...pick(prevTask, ['send_dirs', 'type', 'recv_dir'])
...pick(prevTask, 'send_dirs', 'type', 'recv_dir')
})
message.success('复制完成,已添加到最前端')
}
@ -81,10 +92,19 @@ const openLogDetail = (idx: number) => {
store.currLogDetailId = tasks.value[idx].id
store.splitView.open = true
}
const colors = ['#f5222d', '#1890ff', '#ff3125', '#d46b08', '#007bff', '#52c41a', '#13c2c2', '#fa541c', '#eb2f96', '#2f54eb']
const addDir2task = (idx: number, dir: string) => {
const task = tasks.value[idx]
if (/[,\n]$/.test(task.send_dirs) || !task.send_dirs.trim()) {
task.send_dirs += dir
} else {
task.send_dirs += ` , ${dir}`
}
}
</script>
<template>
<div class="wrapper">
<div class="wrapper" @click="showDirAutoCompletedIdx = -1">
<a-select style="display: none" />
<a-button @click="addEmptyTask">
<template>
@ -105,17 +125,22 @@ const openLogDetail = (idx: number) => {
</div>
</div>
<a-form layout="vertical" label-align="left">
<a-form-item label="发送的文件夹">
<a-form-item label="发送的文件夹" @click.stop="showDirAutoCompletedIdx = idx">
<a-textarea auto-size :disabled="task.running" v-model:value="task.send_dirs"
placeholder="发送文件的文件夹,多个文件夹使用逗号分隔"></a-textarea>
placeholder="发送文件的文件夹,多个文件夹使用逗号或者换行分隔"></a-textarea>
<div v-if="idx === showDirAutoCompletedIdx" class="auto-completed-dirs">
<a-tooltip v-for="item, tagIdx in autoCompletedDirList" :key="item.dir" :title="item.dir+ ' 点击添加'">
<a-tag :visible="!task.send_dirs.includes(item.dir)" :color="colors[tagIdx % colors.length]" @click="addDir2task(idx, item.dir)">{{ item.zh}}</a-tag>
</a-tooltip>
</div>
</a-form-item>
<a-form-item label="百度云文件夹">
<a-input v-model:value="task.recv_dir" :disabled="task.running" placeholder="用于接收的文件夹,可以使用占位符进行动态生成"></a-input>
</a-form-item>
<!--a-form-item label="任务类型">
<search-select v-model:value="task.type" :disabled="task.running" :options="['upload', 'download']"
:conv="{ value: (v) => v, text: (v) => (v === 'upload' ? '上传' : '下载') }"></search-select>
</a-form-item-->
<search-select v-model:value="task.type" :disabled="task.running" :options="['upload', 'download']"
:conv="{ value: (v) => v, text: (v) => (v === 'upload' ? '上传' : '下载') }"></search-select>
</a-form-item-->
</a-form>
<div class="action-bar">
<a-button @click="openLogDetail(idx)" v-if="store.taskLogMap.get(task.id)"></a-button>
@ -140,6 +165,7 @@ const openLogDetail = (idx: number) => {
height: 100%;
overflow: auto;
padding: 8px;
&::-webkit-scrollbar {
display: none;
}
@ -166,6 +192,10 @@ const openLogDetail = (idx: number) => {
margin-bottom: 16px;
}
.auto-completed-dirs {
margin-top: 16px;
}
}
}
</style>

View File

@ -22,4 +22,22 @@ export const asyncCheck = async<T> (getter: () => T, checkSize = 100, timeout =
});
}
export const key = (obj: UniqueId) => obj[idKey]
export const key = (obj: UniqueId) => obj[idKey]
export type Dict<T = any> = Record<string, T>
/**
* loadsh
* @param v
* @param keys
*/
export const pick = <T extends Dict, keys extends Array<keyof T>> (v: T, ...keys: keys) => {
return keys.reduce((p, c) => {
p[c] = v?.[c]
return p
}, {} as Pick<T, keys[number]>)
}
/**
*
*
* ReturnTypeAsync\<typeof fn\>
*/
export type ReturnTypeAsync<T extends (...arg: any) => Promise<any>> = Awaited<ReturnType<T>>