allow reading attachments even if the model is offline
This commit is contained in:
parent
645e8e2f44
commit
177b2c54d9
752
frontend/package-lock.json
generated
752
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@
|
||||
"i18next": "^22.4.15",
|
||||
"mobx": "^6.9.0",
|
||||
"mobx-react-lite": "^3.4.3",
|
||||
"pdfjs-dist": "^4.0.189",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
@ -50,6 +51,7 @@
|
||||
"sass": "^1.62.1",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.6"
|
||||
"vite": "^4.3.6",
|
||||
"vite-plugin-top-level-await": "^1.3.1"
|
||||
}
|
||||
}
|
||||
|
@ -271,5 +271,6 @@
|
||||
"You can increase the number of stored layers in Configs page to improve performance": "パフォーマンスを向上させるために、保存されるレイヤーの数を設定ページで増やすことができます",
|
||||
"Failed to load model, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "モデルの読み込みに失敗しました、仮想メモリ (WSL Swap) を増やすか小さなベースモデルを使用してみてください。",
|
||||
"Save Conversation": "会話を保存",
|
||||
"Use Hugging Face Mirror": "Hugging Faceミラーを使用"
|
||||
"Use Hugging Face Mirror": "Hugging Faceミラーを使用",
|
||||
"File is empty": "ファイルが空です"
|
||||
}
|
@ -271,5 +271,6 @@
|
||||
"You can increase the number of stored layers in Configs page to improve performance": "你可以在配置页面增加载入显存层数以提升性能",
|
||||
"Failed to load model, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "模型载入失败,尝试增加虚拟内存(WSL Swap),或使用一个更小规模的基底模型",
|
||||
"Save Conversation": "保存对话",
|
||||
"Use Hugging Face Mirror": "使用Hugging Face镜像源"
|
||||
"Use Hugging Face Mirror": "使用Hugging Face镜像源",
|
||||
"File is empty": "文件为空"
|
||||
}
|
@ -34,6 +34,7 @@ import { botName, ConversationMessage, MessageType, userName, welcomeUuid } from
|
||||
import { Labeled } from '../components/Labeled';
|
||||
import { ValuedSlider } from '../components/ValuedSlider';
|
||||
import { PresetsButton } from './PresetsManager/PresetsButton';
|
||||
import { webOpenOpenFileDialog } from '../utils/web-file-operations';
|
||||
|
||||
let chatSseControllers: {
|
||||
[id: string]: AbortController
|
||||
@ -560,43 +561,42 @@ const ChatPanel: FC = observer(() => {
|
||||
: <Attach16Regular />}
|
||||
size="small" shape="circular" appearance="secondary"
|
||||
onClick={() => {
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl && commonStore.platform !== 'web') {
|
||||
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (commonStore.attachmentUploading)
|
||||
return;
|
||||
|
||||
OpenOpenFileDialog('*.txt;*.pdf').then(async filePath => {
|
||||
if (!filePath)
|
||||
return;
|
||||
const filterPattern = '*.txt;*.pdf';
|
||||
const setUploading = () => commonStore.setAttachmentUploading(true);
|
||||
// actually, status of web platform is always Offline
|
||||
if (commonStore.platform === 'web' || commonStore.status.status === ModelStatus.Offline) {
|
||||
webOpenOpenFileDialog({ filterPattern, fnStartLoading: setUploading }).then(webReturn => {
|
||||
if (webReturn.content)
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: webReturn.blob.name,
|
||||
size: webReturn.blob.size,
|
||||
content: webReturn.content
|
||||
});
|
||||
else
|
||||
toast(t('File is empty'), {
|
||||
type: 'info',
|
||||
autoClose: 1000
|
||||
});
|
||||
}).catch(e => {
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
}).finally(() => {
|
||||
commonStore.setAttachmentUploading(false);
|
||||
});
|
||||
} else {
|
||||
OpenOpenFileDialog(filterPattern).then(async filePath => {
|
||||
if (!filePath)
|
||||
return;
|
||||
|
||||
commonStore.setAttachmentUploading(true);
|
||||
setUploading();
|
||||
|
||||
let blob: Blob;
|
||||
let attachmentName: string | undefined;
|
||||
let attachmentContent: string | undefined;
|
||||
if (commonStore.platform === 'web') {
|
||||
const webReturn = filePath as any;
|
||||
blob = webReturn.blob;
|
||||
attachmentName = blob.name;
|
||||
attachmentContent = webReturn.content;
|
||||
} else {
|
||||
// Both are slow. Communication between frontend and backend is slow. Use AssetServer Handler to read the file.
|
||||
// const blob = new Blob([atob(info.content as unknown as string)]); // await fetch(`data:application/octet-stream;base64,${info.content}`).then(r => r.blob());
|
||||
blob = await fetch(absPathAsset(filePath)).then(r => r.blob());
|
||||
attachmentName = filePath.split(/[\\/]/).pop();
|
||||
}
|
||||
if (attachmentContent) {
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: attachmentName!,
|
||||
size: blob.size,
|
||||
content: attachmentContent
|
||||
});
|
||||
commonStore.setAttachmentUploading(false);
|
||||
} else {
|
||||
const blob = await fetch(absPathAsset(filePath)).then(r => r.blob());
|
||||
const attachmentName = filePath.split(/[\\/]/).pop();
|
||||
const urlPath = `/file-to-text?file_name=${attachmentName}`;
|
||||
const bodyForm = new FormData();
|
||||
bodyForm.append('file_data', blob, attachmentName);
|
||||
@ -606,31 +606,38 @@ const ChatPanel: FC = observer(() => {
|
||||
}).then(async r => {
|
||||
if (r.status === 200) {
|
||||
const pages = (await r.json()).pages as any[];
|
||||
let attachmentContent: string;
|
||||
if (pages.length === 1)
|
||||
attachmentContent = pages[0].page_content;
|
||||
else
|
||||
attachmentContent = pages.map((p, i) => `Page ${i + 1}:\n${p.page_content}`).join('\n\n');
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: attachmentName!,
|
||||
size: blob.size,
|
||||
content: attachmentContent!
|
||||
if (attachmentContent)
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: attachmentName!,
|
||||
size: blob.size,
|
||||
content: attachmentContent!
|
||||
});
|
||||
else
|
||||
toast(t('File is empty'), {
|
||||
type: 'info',
|
||||
autoClose: 1000
|
||||
});
|
||||
} else {
|
||||
toast(r.statusText + '\n' + (await r.text()), {
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
commonStore.setAttachmentUploading(false);
|
||||
}
|
||||
).catch(e => {
|
||||
commonStore.setAttachmentUploading(false);
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
}).finally(() => {
|
||||
commonStore.setAttachmentUploading(false);
|
||||
});
|
||||
}
|
||||
}).catch(e => {
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
});
|
||||
}).catch(e => {
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
});
|
||||
}
|
||||
}}
|
||||
/> :
|
||||
<div>
|
||||
|
72
frontend/src/utils/web-file-operations.ts
Normal file
72
frontend/src/utils/web-file-operations.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { getDocument, GlobalWorkerOptions, PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import { TextItem } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
export function webOpenOpenFileDialog({ filterPattern, fnStartLoading }: { filterPattern: string, fnStartLoading: Function | null }): Promise<{ blob: Blob, content?: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = filterPattern
|
||||
.replaceAll('*.txt', 'text/plain')
|
||||
.replaceAll('*.', 'application/')
|
||||
.replaceAll(';', ',');
|
||||
|
||||
input.onchange = async e => {
|
||||
// @ts-ignore
|
||||
const file: Blob = e.target?.files[0];
|
||||
if (fnStartLoading && typeof fnStartLoading === 'function')
|
||||
fnStartLoading();
|
||||
if (!GlobalWorkerOptions.workerSrc)
|
||||
// @ts-ignore
|
||||
GlobalWorkerOptions.workerSrc = await import('pdfjs-dist/build/pdf.worker.min.mjs');
|
||||
if (file.type === 'text/plain') {
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
|
||||
reader.onload = event => {
|
||||
const content = event.target?.result as string;
|
||||
resolve({
|
||||
blob: file,
|
||||
content: content
|
||||
});
|
||||
};
|
||||
reader.onerror = reject;
|
||||
} else if (file.type === 'application/pdf') {
|
||||
const readPDFPage = async (doc: PDFDocumentProxy, pageNo: number) => {
|
||||
const page = await doc.getPage(pageNo);
|
||||
const tokenizedText = await page.getTextContent();
|
||||
return tokenizedText.items.map(token => (token as TextItem).str).join('');
|
||||
};
|
||||
let reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
const doc = await getDocument(event.target?.result!).promise;
|
||||
const pageTextPromises = [];
|
||||
for (let pageNo = 1; pageNo <= doc.numPages; pageNo++) {
|
||||
pageTextPromises.push(readPDFPage(doc, pageNo));
|
||||
}
|
||||
const pageTexts = await Promise.all(pageTextPromises);
|
||||
let content;
|
||||
if (pageTexts.length === 1)
|
||||
content = pageTexts[0];
|
||||
else
|
||||
content = pageTexts.map((p, i) => `Page ${i + 1}:\n${p}`).join('\n\n');
|
||||
resolve({
|
||||
blob: file,
|
||||
content: content
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
reader.onerror = reject;
|
||||
} else {
|
||||
resolve({
|
||||
blob: file
|
||||
});
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { webOpenOpenFileDialog } from './utils/web-file-operations'
|
||||
|
||||
function defineRuntime(name, func) {
|
||||
window.runtime[name] = func
|
||||
}
|
||||
@ -107,37 +109,7 @@ if (!window.go) {
|
||||
defineApp('ListDirFiles', async () => {
|
||||
return []
|
||||
})
|
||||
defineApp('OpenOpenFileDialog', async (filterPattern) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = filterPattern
|
||||
.replaceAll('*.txt', 'text/plain')
|
||||
.replaceAll('*.', 'application/')
|
||||
.replaceAll(';', ',')
|
||||
|
||||
input.onchange = e => {
|
||||
const file = e.target?.files[0]
|
||||
if (file.type === 'text/plain') {
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(file, 'UTF-8')
|
||||
|
||||
reader.onload = readerEvent => {
|
||||
const content = readerEvent.target?.result
|
||||
resolve({
|
||||
blob: file,
|
||||
content: content
|
||||
})
|
||||
}
|
||||
} else {
|
||||
resolve({
|
||||
blob: file
|
||||
})
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
})
|
||||
})
|
||||
defineApp('OpenOpenFileDialog', webOpenOpenFileDialog)
|
||||
defineApp('OpenSaveFileDialog', async (filterPattern, defaultFileName, savedContent) => {
|
||||
const saver = await import('file-saver')
|
||||
saver.saveAs(new Blob([savedContent], { type: 'text/plain;charset=utf-8' }), defaultFileName)
|
||||
|
@ -3,6 +3,7 @@ import { dependencies } from './package.json';
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
|
||||
// dependencies that exist anywhere
|
||||
const vendor = [
|
||||
@ -35,12 +36,18 @@ function renderChunks(deps: Record<string, string>) {
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(),
|
||||
plugins: [
|
||||
react(),
|
||||
visualizer({
|
||||
template: 'treemap',
|
||||
gzipSize: true,
|
||||
brotliSize: true
|
||||
})],
|
||||
}),
|
||||
topLevelAwait({
|
||||
promiseExportName: '__tla',
|
||||
promiseImportName: i => `__tla_${i}`
|
||||
})
|
||||
],
|
||||
build: {
|
||||
chunkSizeWarningLimit: 3000,
|
||||
rollupOptions: {
|
||||
|
Loading…
Reference in New Issue
Block a user