mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-15 04:51:01 +02:00
Add files via upload
This commit is contained in:
@@ -846,6 +846,7 @@ func setupRoutes(
|
||||
protected.GET("/chat-uploads/download", chatUploadsHandler.Download)
|
||||
protected.GET("/chat-uploads/content", chatUploadsHandler.GetContent)
|
||||
protected.POST("/chat-uploads", chatUploadsHandler.Upload)
|
||||
protected.POST("/chat-uploads/mkdir", chatUploadsHandler.Mkdir)
|
||||
protected.DELETE("/chat-uploads", chatUploadsHandler.Delete)
|
||||
protected.PUT("/chat-uploads/rename", chatUploadsHandler.Rename)
|
||||
protected.PUT("/chat-uploads/content", chatUploadsHandler.PutContent)
|
||||
|
||||
@@ -205,6 +205,77 @@ func (h *ChatUploadsHandler) Delete(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
type chatUploadMkdirBody struct {
|
||||
Parent string `json:"parent"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Mkdir POST /api/chat-uploads/mkdir — 在 parent 目录下新建子目录(parent 为 chat_uploads 下相对路径,空表示根目录;name 为单段目录名)
|
||||
func (h *ChatUploadsHandler) Mkdir(c *gin.Context) {
|
||||
var body chatUploadMkdirBody
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body"})
|
||||
return
|
||||
}
|
||||
name := strings.TrimSpace(body.Name)
|
||||
if name == "" || strings.ContainsAny(name, `/\`) || name == "." || name == ".." {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid name"})
|
||||
return
|
||||
}
|
||||
if utf8.RuneCountInString(name) > 200 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "name too long"})
|
||||
return
|
||||
}
|
||||
|
||||
parent := strings.TrimSpace(body.Parent)
|
||||
parent = filepath.ToSlash(filepath.Clean(filepath.FromSlash(parent)))
|
||||
parent = strings.Trim(parent, "/")
|
||||
if parent == "." {
|
||||
parent = ""
|
||||
}
|
||||
|
||||
root, err := h.absRoot()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
absParent, err := h.resolveUnderChatUploads(parent)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
st, err := os.Stat(absParent)
|
||||
if err != nil || !st.IsDir() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "parent not found"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var rel string
|
||||
if parent == "" {
|
||||
rel = name
|
||||
} else {
|
||||
rel = parent + "/" + name
|
||||
}
|
||||
absNew, err := h.resolveUnderChatUploads(rel)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(absNew); err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "already exists"})
|
||||
return
|
||||
}
|
||||
if err := os.Mkdir(absNew, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
relOut, _ := filepath.Rel(root, absNew)
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "relativePath": filepath.ToSlash(relOut)})
|
||||
}
|
||||
|
||||
type chatUploadRenameBody struct {
|
||||
Path string `json:"path"`
|
||||
NewName string `json:"newName"`
|
||||
|
||||
@@ -13330,6 +13330,86 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 新建文件夹弹窗:层次清晰、留白舒适,无强装饰 */
|
||||
.chat-files-mkdir-modal-content {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-body {
|
||||
padding: 26px 28px 28px;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-location {
|
||||
margin: 0 0 22px;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-location-caption {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-path-box {
|
||||
padding: 11px 14px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-path {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.55;
|
||||
color: var(--text-primary);
|
||||
word-break: break-all;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-label {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-field-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chat-files-mkdir-field-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-input {
|
||||
min-height: 40px;
|
||||
padding: 9px 12px;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 8px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.chat-files-mkdir-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.14);
|
||||
}
|
||||
|
||||
.chat-files-mkdir-footer {
|
||||
padding: 18px 28px 22px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chat-files-toast {
|
||||
position: fixed;
|
||||
z-index: 1100;
|
||||
|
||||
@@ -1021,6 +1021,14 @@
|
||||
"confirmDeleteFolder": "Delete this folder and everything inside it? This cannot be undone.",
|
||||
"deleteFolderTitle": "Delete folder",
|
||||
"uploadToFolderTitle": "Upload file into this folder",
|
||||
"newFolderButton": "New folder",
|
||||
"newFolderTitle": "New folder",
|
||||
"newFolderLocation": "Location",
|
||||
"newFolderNameLabel": "Folder name",
|
||||
"newFolderNamePlaceholder": "Name only, no slashes",
|
||||
"mkdirOk": "Folder created",
|
||||
"mkdirExists": "A file or folder with that name already exists",
|
||||
"mkdirInvalidName": "Invalid name: cannot be empty or contain /, \\, or use . or ..",
|
||||
"colSubPath": "Subfolder",
|
||||
"folderRoot": "(root)",
|
||||
"groupCount": "{{count}} files",
|
||||
|
||||
@@ -1021,6 +1021,14 @@
|
||||
"confirmDeleteFolder": "确定删除该文件夹及其中的全部文件?此操作不可恢复。",
|
||||
"deleteFolderTitle": "删除文件夹",
|
||||
"uploadToFolderTitle": "上传文件到此文件夹",
|
||||
"newFolderButton": "新建文件夹",
|
||||
"newFolderTitle": "新建文件夹",
|
||||
"newFolderLocation": "位置",
|
||||
"newFolderNameLabel": "文件夹名称",
|
||||
"newFolderNamePlaceholder": "仅名称,不含 /",
|
||||
"mkdirOk": "文件夹已创建",
|
||||
"mkdirExists": "该名称已存在",
|
||||
"mkdirInvalidName": "名称无效:不能为空,且不能包含 /、\\ 或 . / ..",
|
||||
"colSubPath": "子路径",
|
||||
"folderRoot": "(根目录)",
|
||||
"groupCount": "{{count}} 个文件",
|
||||
|
||||
+187
-4
@@ -13,6 +13,98 @@ let chatFilesBrowsePath = [];
|
||||
/** 非空时,下一次上传文件落到此相对路径(chat_uploads 下目录),如 2026-03-21/uuid/sub */
|
||||
let chatFilesPendingUploadDir = '';
|
||||
|
||||
/** 仅前端记录的「空目录」键 parentPath('' 表示 chat_uploads 根)-> 子目录名列表,与树合并以便 mkdir 后可见 */
|
||||
const CHAT_FILES_SYNTHETIC_DIRS_KEY = 'csai_chat_files_synthetic_dirs';
|
||||
let chatFilesSyntheticEmptyDirs = {};
|
||||
|
||||
function chatFilesLoadSyntheticDirsFromStorage() {
|
||||
try {
|
||||
const raw = localStorage.getItem(CHAT_FILES_SYNTHETIC_DIRS_KEY);
|
||||
if (!raw) return;
|
||||
const o = JSON.parse(raw);
|
||||
if (o && typeof o === 'object') {
|
||||
chatFilesSyntheticEmptyDirs = o;
|
||||
}
|
||||
} catch (e) {
|
||||
chatFilesSyntheticEmptyDirs = {};
|
||||
}
|
||||
}
|
||||
|
||||
function chatFilesRegisterSyntheticEmptyDir(parentSegments, name) {
|
||||
const p = parentSegments.join('/');
|
||||
if (!chatFilesSyntheticEmptyDirs[p]) {
|
||||
chatFilesSyntheticEmptyDirs[p] = [];
|
||||
}
|
||||
const arr = chatFilesSyntheticEmptyDirs[p];
|
||||
if (arr.indexOf(name) === -1) {
|
||||
arr.push(name);
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(CHAT_FILES_SYNTHETIC_DIRS_KEY, JSON.stringify(chatFilesSyntheticEmptyDirs));
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function chatFilesRemoveSyntheticDirSubtree(relPathUnderRoot) {
|
||||
const rel = String(relPathUnderRoot || '').replace(/\\/g, '/').replace(/^\/+/, '').replace(/\/+$/, '');
|
||||
if (!rel) return;
|
||||
const parts = rel.split('/').filter(function (x) {
|
||||
return x.length > 0;
|
||||
});
|
||||
if (parts.length === 0) return;
|
||||
const leaf = parts[parts.length - 1];
|
||||
const parentKey = parts.slice(0, -1).join('/');
|
||||
const arr = chatFilesSyntheticEmptyDirs[parentKey];
|
||||
if (arr) {
|
||||
const ix = arr.indexOf(leaf);
|
||||
if (ix >= 0) arr.splice(ix, 1);
|
||||
if (arr.length === 0) delete chatFilesSyntheticEmptyDirs[parentKey];
|
||||
}
|
||||
const prefix = rel + '/';
|
||||
let k;
|
||||
for (k in chatFilesSyntheticEmptyDirs) {
|
||||
if (!Object.prototype.hasOwnProperty.call(chatFilesSyntheticEmptyDirs, k)) continue;
|
||||
if (k === rel || k.indexOf(prefix) === 0) {
|
||||
delete chatFilesSyntheticEmptyDirs[k];
|
||||
}
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(CHAT_FILES_SYNTHETIC_DIRS_KEY, JSON.stringify(chatFilesSyntheticEmptyDirs));
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function chatFilesMergeSyntheticDirsIntoTree(root) {
|
||||
function ensurePath(node, segments) {
|
||||
let n = node;
|
||||
let i;
|
||||
for (i = 0; i < segments.length; i++) {
|
||||
const s = segments[i];
|
||||
if (!n.dirs[s]) n.dirs[s] = chatFilesTreeMakeNode();
|
||||
n = n.dirs[s];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
let k;
|
||||
for (k in chatFilesSyntheticEmptyDirs) {
|
||||
if (!Object.prototype.hasOwnProperty.call(chatFilesSyntheticEmptyDirs, k)) continue;
|
||||
const names = chatFilesSyntheticEmptyDirs[k];
|
||||
if (!Array.isArray(names)) continue;
|
||||
const segs = k ? k.split('/').filter(function (x) {
|
||||
return x.length > 0;
|
||||
}) : [];
|
||||
const node = ensurePath(root, segs);
|
||||
let ni;
|
||||
for (ni = 0; ni < names.length; ni++) {
|
||||
const nm = names[ni];
|
||||
if (!nm || typeof nm !== 'string') continue;
|
||||
if (!node.dirs[nm]) node.dirs[nm] = chatFilesTreeMakeNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function chatFilesLoadBrowsePathFromStorage() {
|
||||
try {
|
||||
const raw = localStorage.getItem(CHAT_FILES_BROWSE_PATH_KEY);
|
||||
@@ -63,6 +155,7 @@ function chatFilesNormalizeBrowsePathForTree(root) {
|
||||
|
||||
function initChatFilesPage() {
|
||||
chatFilesLoadBrowsePathFromStorage();
|
||||
chatFilesLoadSyntheticDirsFromStorage();
|
||||
ensureChatFilesDocClickClose();
|
||||
const sel = document.getElementById('chat-files-group-by');
|
||||
if (sel) {
|
||||
@@ -368,6 +461,12 @@ function chatFilesBuildTree(files) {
|
||||
return root;
|
||||
}
|
||||
|
||||
function chatFilesTreeRootMerged() {
|
||||
const root = chatFilesBuildTree(chatFilesDisplayed);
|
||||
chatFilesMergeSyntheticDirsIntoTree(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
function chatFilesTreeNodeMaxMod(node) {
|
||||
let m = 0;
|
||||
let i;
|
||||
@@ -570,7 +669,7 @@ function renderChatFilesTable() {
|
||||
let innerHtml;
|
||||
|
||||
if (groupMode === 'folder') {
|
||||
const root = chatFilesBuildTree(chatFilesDisplayed);
|
||||
const root = chatFilesTreeRootMerged();
|
||||
chatFilesNormalizeBrowsePathForTree(root);
|
||||
const node = chatFilesResolveTreeNode(root, chatFilesBrowsePath);
|
||||
const current = node || root;
|
||||
@@ -582,6 +681,7 @@ function renderChatFilesTable() {
|
||||
|
||||
const tRoot = escapeHtml((typeof window.t === 'function') ? window.t('chatFilesPage.browseRoot') : 'chat_uploads');
|
||||
const tUp = escapeHtml((typeof window.t === 'function') ? window.t('chatFilesPage.browseUp') : '上级');
|
||||
const tMkdir = escapeHtml((typeof window.t === 'function') ? window.t('chatFilesPage.newFolderButton') : '新建文件夹');
|
||||
const tEmpty = escapeHtml((typeof window.t === 'function') ? window.t('chatFilesPage.folderEmpty') : '此文件夹为空');
|
||||
const tCopyFolder = escapeHtml((typeof window.t === 'function') ? window.t('chatFilesPage.copyFolderPathTitle') : '复制 chat_uploads 下相对路径');
|
||||
const tEnter = escapeHtml((typeof window.t === 'function') ? window.t('chatFilesPage.enterFolderTitle') : '进入');
|
||||
@@ -603,6 +703,7 @@ function renderChatFilesTable() {
|
||||
|
||||
const upDisabled = chatFilesBrowsePath.length === 0 ? ' disabled' : '';
|
||||
const toolbarHtml = '<div class="chat-files-browse-toolbar">' + breadcrumbHtml +
|
||||
'<button type="button" class="btn-secondary chat-files-mkdir-btn" onclick="openChatFilesMkdirModal()">' + tMkdir + '</button>' +
|
||||
'<button type="button" class="btn-secondary chat-files-browse-up"' + upDisabled + ' onclick="chatFilesNavigateUp()">' + tUp + '</button></div>';
|
||||
|
||||
const svgTrash = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
||||
@@ -735,7 +836,7 @@ function renderChatFilesTable() {
|
||||
window.chatFilesGroupByChange = chatFilesGroupByChange;
|
||||
|
||||
function chatFilesNavigateInto(name) {
|
||||
const root = chatFilesBuildTree(chatFilesDisplayed);
|
||||
const root = chatFilesTreeRootMerged();
|
||||
chatFilesNormalizeBrowsePathForTree(root);
|
||||
const next = chatFilesBrowsePath.concat([name]);
|
||||
if (!chatFilesResolveTreeNode(root, next)) return;
|
||||
@@ -744,7 +845,7 @@ function chatFilesNavigateInto(name) {
|
||||
}
|
||||
|
||||
function chatFilesNavigateBreadcrumb(level) {
|
||||
const root = chatFilesBuildTree(chatFilesDisplayed);
|
||||
const root = chatFilesTreeRootMerged();
|
||||
chatFilesNormalizeBrowsePathForTree(root);
|
||||
if (level < 0) {
|
||||
chatFilesSetBrowsePath([]);
|
||||
@@ -805,6 +906,7 @@ async function deleteChatFolderFromBrowse(folderName) {
|
||||
if (!res.ok) {
|
||||
throw new Error(await res.text());
|
||||
}
|
||||
chatFilesRemoveSyntheticDirSubtree(rel);
|
||||
loadChatFilesPage();
|
||||
} catch (e) {
|
||||
alert((e && e.message) ? e.message : String(e));
|
||||
@@ -842,6 +944,9 @@ window.chatFilesCopyFolderPathFromBtn = chatFilesCopyFolderPathFromBtn;
|
||||
window.chatFilesDeleteFolderFromBtn = chatFilesDeleteFolderFromBtn;
|
||||
window.chatFilesOpenUploadPicker = chatFilesOpenUploadPicker;
|
||||
window.chatFilesUploadToFolderClick = chatFilesUploadToFolderClick;
|
||||
window.openChatFilesMkdirModal = openChatFilesMkdirModal;
|
||||
window.closeChatFilesMkdirModal = closeChatFilesMkdirModal;
|
||||
window.submitChatFilesMkdir = submitChatFilesMkdir;
|
||||
|
||||
function openChatFilesConversationIdx(idx) {
|
||||
const f = chatFilesDisplayed[idx];
|
||||
@@ -1018,8 +1123,86 @@ async function submitChatFilesRename() {
|
||||
}
|
||||
}
|
||||
|
||||
function openChatFilesMkdirModal() {
|
||||
if (chatFilesGetGroupByMode() !== 'folder') return;
|
||||
const hint = document.getElementById('chat-files-mkdir-parent-hint');
|
||||
const input = document.getElementById('chat-files-mkdir-input');
|
||||
const modal = document.getElementById('chat-files-mkdir-modal');
|
||||
const p = chatFilesBrowsePath.join('/');
|
||||
if (hint) hint.textContent = p ? ('chat_uploads/' + p) : 'chat_uploads';
|
||||
if (input) input.value = '';
|
||||
if (modal) modal.style.display = 'block';
|
||||
if (modal && typeof window.applyTranslations === 'function') {
|
||||
window.applyTranslations(modal);
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (input) input.focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function closeChatFilesMkdirModal() {
|
||||
const modal = document.getElementById('chat-files-mkdir-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
const input = document.getElementById('chat-files-mkdir-input');
|
||||
if (input) input.value = '';
|
||||
}
|
||||
|
||||
async function submitChatFilesMkdir() {
|
||||
const input = document.getElementById('chat-files-mkdir-input');
|
||||
const name = input ? String(input.value).trim() : '';
|
||||
if (!name) {
|
||||
closeChatFilesMkdirModal();
|
||||
return;
|
||||
}
|
||||
if (name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
|
||||
const msg = (typeof window.t === 'function')
|
||||
? window.t('chatFilesPage.mkdirInvalidName')
|
||||
: '名称无效';
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
const parent = chatFilesBrowsePath.join('/');
|
||||
try {
|
||||
const res = await apiFetch('/api/chat-uploads/mkdir', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ parent: parent, name: name })
|
||||
});
|
||||
if (!res.ok) {
|
||||
let errText = '';
|
||||
try {
|
||||
const j = await res.json();
|
||||
errText = j.error || JSON.stringify(j);
|
||||
} catch (e2) {
|
||||
errText = await res.text();
|
||||
}
|
||||
if (res.status === 409) {
|
||||
const msg = (typeof window.t === 'function')
|
||||
? window.t('chatFilesPage.mkdirExists')
|
||||
: errText;
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
throw new Error(errText || String(res.status));
|
||||
}
|
||||
chatFilesRegisterSyntheticEmptyDir(chatFilesBrowsePath.slice(), name);
|
||||
closeChatFilesMkdirModal();
|
||||
loadChatFilesPage();
|
||||
const okMsg = (typeof window.t === 'function')
|
||||
? window.t('chatFilesPage.mkdirOk')
|
||||
: '文件夹已创建';
|
||||
chatFilesShowToast(okMsg);
|
||||
} catch (e) {
|
||||
alert((e && e.message) ? e.message : String(e));
|
||||
}
|
||||
}
|
||||
|
||||
function chatFilesOpenUploadPicker() {
|
||||
chatFilesPendingUploadDir = '';
|
||||
if (chatFilesGetGroupByMode() === 'folder') {
|
||||
chatFilesPendingUploadDir = chatFilesBrowsePath.join('/');
|
||||
} else {
|
||||
chatFilesPendingUploadDir = '';
|
||||
}
|
||||
const inp = document.getElementById('chat-files-upload-input');
|
||||
if (inp) inp.click();
|
||||
}
|
||||
|
||||
@@ -1818,6 +1818,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-files-mkdir-modal" class="modal">
|
||||
<div class="modal-content chat-files-mkdir-modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="chatFilesPage.newFolderTitle">新建文件夹</h2>
|
||||
<span class="modal-close" onclick="closeChatFilesMkdirModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body chat-files-mkdir-body">
|
||||
<div class="chat-files-mkdir-location" aria-live="polite">
|
||||
<div class="chat-files-mkdir-location-caption" data-i18n="chatFilesPage.newFolderLocation">位置</div>
|
||||
<div class="chat-files-mkdir-path-box">
|
||||
<code class="chat-files-mkdir-path" id="chat-files-mkdir-parent-hint">chat_uploads</code>
|
||||
</div>
|
||||
</div>
|
||||
<label class="chat-files-rename-label chat-files-mkdir-label">
|
||||
<span class="chat-files-mkdir-field-name">
|
||||
<svg class="chat-files-mkdir-field-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
<span data-i18n="chatFilesPage.newFolderNameLabel">文件夹名称</span>
|
||||
</span>
|
||||
<input type="text" id="chat-files-mkdir-input" class="form-control chat-files-mkdir-input" data-i18n="chatFilesPage.newFolderNamePlaceholder" data-i18n-attr="placeholder" placeholder="仅名称,不含 /" autocomplete="off" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-footer chat-files-mkdir-footer">
|
||||
<button type="button" class="btn-secondary chat-files-mkdir-btn-cancel" onclick="closeChatFilesMkdirModal()" data-i18n="common.cancel">取消</button>
|
||||
<button type="button" class="btn-primary chat-files-mkdir-btn-submit" onclick="submitChatFilesMkdir()" data-i18n="common.ok">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Marked.js for Markdown parsing -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
|
||||
<!-- DOMPurify for HTML sanitization to prevent XSS -->
|
||||
|
||||
Reference in New Issue
Block a user