mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
fix(web): remove upload dir button, one-time upload all files,path check adapt windows
This commit is contained in:
@@ -104,8 +104,9 @@ const I18N = {
|
||||
context_cleared: '— 以上内容已从上下文中移除 —',
|
||||
tip_new_chat: '新建对话',
|
||||
tip_clear_context: '清除上下文',
|
||||
tip_attach_file: '上传附件',
|
||||
tip_attach_folder: '上传目录',
|
||||
tip_attach: '添加附件',
|
||||
attach_menu_file: '上传文件',
|
||||
attach_menu_folder: '上传文件夹',
|
||||
confirm_yes: '确认',
|
||||
confirm_cancel: '取消',
|
||||
error_send: '发送失败,请稍后再试。', error_timeout: '请求超时,请再试一次。',
|
||||
@@ -204,8 +205,9 @@ const I18N = {
|
||||
context_cleared: '— Context above has been cleared —',
|
||||
tip_new_chat: 'New Chat',
|
||||
tip_clear_context: 'Clear Context',
|
||||
tip_attach_file: 'Attach File',
|
||||
tip_attach_folder: 'Attach Folder',
|
||||
tip_attach: 'Add Attachment',
|
||||
attach_menu_file: 'Upload File',
|
||||
attach_menu_folder: 'Upload Folder',
|
||||
confirm_yes: 'Confirm',
|
||||
confirm_cancel: 'Cancel',
|
||||
error_send: 'Failed to send. Please try again.', error_timeout: 'Request timeout. Please try again.',
|
||||
@@ -392,14 +394,34 @@ window.addEventListener('resize', () => {
|
||||
// =====================================================================
|
||||
// Markdown Renderer
|
||||
// =====================================================================
|
||||
const FALLBACK_HLJS = {
|
||||
getLanguage() { return false; },
|
||||
highlight(str) { return { value: escapeHtml(str) }; },
|
||||
highlightAuto(str) { return { value: escapeHtml(str) }; },
|
||||
highlightElement() {},
|
||||
};
|
||||
|
||||
function getHljs() {
|
||||
return window.hljs || FALLBACK_HLJS;
|
||||
}
|
||||
|
||||
function createMd() {
|
||||
const md = window.markdownit({
|
||||
const hljsLib = getHljs();
|
||||
const mdFactory = window.markdownit;
|
||||
if (typeof mdFactory !== 'function') {
|
||||
return {
|
||||
render(text) {
|
||||
return `<p>${escapeHtml(text || '')}</p>`;
|
||||
}
|
||||
};
|
||||
}
|
||||
const md = mdFactory({
|
||||
html: false, breaks: true, linkify: true, typographer: true,
|
||||
highlight: function(str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try { return hljs.highlight(str, { language: lang }).value; } catch (_) {}
|
||||
if (lang && hljsLib.getLanguage(lang)) {
|
||||
try { return hljsLib.highlight(str, { language: lang }).value; } catch (_) {}
|
||||
}
|
||||
return hljs.highlightAuto(str).value;
|
||||
return hljsLib.highlightAuto(str).value;
|
||||
}
|
||||
});
|
||||
const defaultLinkOpen = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
|
||||
@@ -581,13 +603,13 @@ const sendBtn = document.getElementById('send-btn');
|
||||
const messagesDiv = document.getElementById('chat-messages');
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const folderInput = document.getElementById('folder-input');
|
||||
const attachFolderBtn = document.getElementById('attach-folder-btn');
|
||||
const attachBtn = document.getElementById('attach-btn');
|
||||
const attachMenu = document.getElementById('attach-menu');
|
||||
const attachFolderOption = document.getElementById('attach-folder-option');
|
||||
const supportsDirectoryUpload = !!folderInput && 'webkitdirectory' in folderInput;
|
||||
|
||||
if (folderInput && attachFolderBtn) {
|
||||
const supportsDirectoryUpload = 'webkitdirectory' in folderInput;
|
||||
if (!supportsDirectoryUpload) {
|
||||
attachFolderBtn.classList.add('hidden');
|
||||
}
|
||||
if (!supportsDirectoryUpload && attachFolderOption) {
|
||||
attachFolderOption.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Smart auto-scroll: pause when user scrolls up, resume when near bottom
|
||||
@@ -690,6 +712,34 @@ function removeAttachment(idx) {
|
||||
renderAttachmentPreview();
|
||||
}
|
||||
|
||||
function isAttachMenuVisible() {
|
||||
return attachMenu && !attachMenu.classList.contains('hidden');
|
||||
}
|
||||
|
||||
function hideAttachMenu() {
|
||||
if (attachMenu) attachMenu.classList.add('hidden');
|
||||
}
|
||||
|
||||
function toggleAttachMenu(event) {
|
||||
if (!attachMenu) return;
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
attachMenu.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
function triggerFileUpload() {
|
||||
hideAttachMenu();
|
||||
fileInput?.click();
|
||||
}
|
||||
|
||||
function triggerFolderUpload() {
|
||||
if (!supportsDirectoryUpload) return;
|
||||
hideAttachMenu();
|
||||
folderInput?.click();
|
||||
}
|
||||
|
||||
async function handleFileSelect(files) {
|
||||
if (!files || files.length === 0) return;
|
||||
const tasks = [];
|
||||
@@ -757,45 +807,37 @@ async function handleFolderSelect(files) {
|
||||
_uploading: true,
|
||||
};
|
||||
pendingAttachments.push(placeholder);
|
||||
uploadingCount++;
|
||||
renderAttachmentPreview();
|
||||
|
||||
const uploadId = _makeUploadId();
|
||||
const tasks = entries.map(async ({ file, relPath }) => {
|
||||
uploadingCount++;
|
||||
renderAttachmentPreview();
|
||||
groupTasks.push((async () => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('session_id', sessionId);
|
||||
formData.append('upload_id', uploadId);
|
||||
formData.append('relative_path', relPath);
|
||||
for (const { file, relPath } of entries) {
|
||||
formData.append('files', file);
|
||||
formData.append('relative_paths', relPath);
|
||||
}
|
||||
|
||||
const resp = await fetch('/upload', { method: 'POST', body: formData });
|
||||
const data = await resp.json();
|
||||
if (data.status !== 'success') {
|
||||
throw new Error(data.message || 'Upload failed');
|
||||
}
|
||||
if (!placeholder.file_path && data.root_path) {
|
||||
placeholder.file_path = data.root_path;
|
||||
placeholder.file_name = data.root_name || rootName;
|
||||
}
|
||||
} finally {
|
||||
uploadingCount--;
|
||||
renderAttachmentPreview();
|
||||
}
|
||||
});
|
||||
|
||||
groupTasks.push((async () => {
|
||||
try {
|
||||
await Promise.all(tasks);
|
||||
if (!placeholder.file_path) {
|
||||
if (!data.root_path) {
|
||||
throw new Error('Directory root path missing');
|
||||
}
|
||||
placeholder.file_path = data.root_path;
|
||||
placeholder.file_name = data.root_name || rootName;
|
||||
delete placeholder._uploading;
|
||||
} catch (e) {
|
||||
console.error('Directory upload failed:', e);
|
||||
const i = pendingAttachments.indexOf(placeholder);
|
||||
if (i !== -1) pendingAttachments.splice(i, 1);
|
||||
} finally {
|
||||
uploadingCount--;
|
||||
}
|
||||
renderAttachmentPreview();
|
||||
})());
|
||||
@@ -814,6 +856,12 @@ folderInput.addEventListener('change', function() {
|
||||
this.value = '';
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!isAttachMenuVisible()) return;
|
||||
if (attachMenu.contains(e.target) || attachBtn.contains(e.target)) return;
|
||||
hideAttachMenu();
|
||||
});
|
||||
|
||||
// Drag-and-drop support on chat input area
|
||||
const chatInputArea = chatInput.closest('.flex-shrink-0');
|
||||
chatInputArea.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); chatInputArea.classList.add('drag-over'); });
|
||||
@@ -994,6 +1042,11 @@ chatInput.addEventListener('input', function() {
|
||||
chatInput.addEventListener('keydown', function(e) {
|
||||
if (e.keyCode === 229 || e.isComposing || isComposing) return;
|
||||
|
||||
if (e.key === 'Escape' && isAttachMenuVisible()) {
|
||||
hideAttachMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSlashMenuVisible()) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
@@ -2085,8 +2138,7 @@ function _applyInputTooltips() {
|
||||
};
|
||||
set('new-chat-btn', 'tip_new_chat');
|
||||
set('clear-context-btn', 'tip_clear_context');
|
||||
set('attach-btn', 'tip_attach_file');
|
||||
set('attach-folder-btn', 'tip_attach_folder');
|
||||
set('attach-btn', 'tip_attach');
|
||||
set('session-toggle-btn', 'session_history', 'bottom');
|
||||
}
|
||||
|
||||
@@ -2402,9 +2454,10 @@ function _updateScrollToBottomBtn() {
|
||||
function applyHighlighting(container) {
|
||||
const root = container || document;
|
||||
setTimeout(() => {
|
||||
const hljsLib = getHljs();
|
||||
root.querySelectorAll('pre code').forEach(block => {
|
||||
if (!block.classList.contains('hljs')) {
|
||||
hljs.highlightElement(block);
|
||||
hljsLib.highlightElement(block);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
@@ -4528,18 +4581,38 @@ function switchKnowledgeTab(tab) {
|
||||
}
|
||||
}
|
||||
|
||||
let _d3LoadPromise = null;
|
||||
|
||||
function ensureD3Loaded() {
|
||||
if (window.d3) return Promise.resolve(window.d3);
|
||||
if (_d3LoadPromise) return _d3LoadPromise;
|
||||
_d3LoadPromise = new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
|
||||
script.async = true;
|
||||
script.onload = () => resolve(window.d3);
|
||||
script.onerror = () => reject(new Error('Failed to load d3'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
return _d3LoadPromise;
|
||||
}
|
||||
|
||||
function loadKnowledgeGraph() {
|
||||
_knowledgeGraphLoaded = true;
|
||||
const container = document.getElementById('knowledge-graph-container');
|
||||
container.innerHTML = '';
|
||||
container.innerHTML = '<div class="flex items-center justify-center h-full text-slate-400 text-sm"><i class="fas fa-spinner fa-spin mr-2"></i>Loading graph...</div>';
|
||||
|
||||
fetch('/api/knowledge/graph').then(r => r.json()).then(data => {
|
||||
Promise.all([
|
||||
ensureD3Loaded(),
|
||||
fetch('/api/knowledge/graph').then(r => r.json()),
|
||||
]).then(([, data]) => {
|
||||
const nodes = data.nodes || [];
|
||||
const links = data.links || [];
|
||||
if (nodes.length === 0) {
|
||||
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-slate-400"><i class="fas fa-diagram-project text-3xl mb-3 opacity-40"></i><p class="text-sm">${t('knowledge_empty_hint')}</p></div>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
renderKnowledgeGraph(container, nodes, links);
|
||||
}).catch(() => {
|
||||
container.innerHTML = '<div class="flex items-center justify-center h-full text-slate-400 text-sm">Failed to load graph</div>';
|
||||
|
||||
Reference in New Issue
Block a user