allow conversation with some document (.pdf, .txt)
This commit is contained in:
parent
810843a5ab
commit
c87de93498
@ -53,12 +53,12 @@ type FileInfo struct {
|
|||||||
ModTime string `json:"modTime"`
|
ModTime string `json:"modTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ReadFileInfo(fileName string) (FileInfo, error) {
|
func (a *App) ReadFileInfo(fileName string) (*FileInfo, error) {
|
||||||
info, err := os.Stat(a.exDir + fileName)
|
info, err := os.Stat(a.exDir + fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FileInfo{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return FileInfo{
|
return &FileInfo{
|
||||||
Name: info.Name(),
|
Name: info.Name(),
|
||||||
Size: info.Size(),
|
Size: info.Size(),
|
||||||
IsDir: info.IsDir(),
|
IsDir: info.IsDir(),
|
||||||
@ -145,6 +145,20 @@ func (a *App) OpenSaveFileDialogBytes(filterPattern string, defaultFileName stri
|
|||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only return the path of the selected file, because communication between frontend and backend is slow. Use AssetServer Handler to read the file.
|
||||||
|
func (a *App) OpenOpenFileDialog(filterPattern string) (string, error) {
|
||||||
|
path, err := wruntime.OpenFileDialog(a.ctx, wruntime.OpenDialogOptions{
|
||||||
|
Filters: []wruntime.FileFilter{{Pattern: filterPattern}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if path == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) OpenFileFolder(path string, relative bool) error {
|
func (a *App) OpenFileFolder(path string, relative bool) error {
|
||||||
var absPath string
|
var absPath string
|
||||||
var err error
|
var err error
|
||||||
|
@ -58,17 +58,22 @@ async def file_to_text(
|
|||||||
|
|
||||||
file_parsers = {".txt": parse_text, ".pdf": parse_pdf}
|
file_parsers = {".txt": parse_text, ".pdf": parse_pdf}
|
||||||
|
|
||||||
file_ext = os.path.splitext(params.file_name)[-1]
|
file_name = file_data.filename or params.file_name
|
||||||
|
file_ext = os.path.splitext(file_name)[-1]
|
||||||
|
|
||||||
if file_ext not in file_parsers:
|
if file_ext not in file_parsers:
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "file type not supported")
|
raise HTTPException(status.HTTP_400_BAD_REQUEST, "file type not supported")
|
||||||
|
|
||||||
|
try:
|
||||||
pages: Iterator[Document] = file_parsers[file_ext](
|
pages: Iterator[Document] = file_parsers[file_ext](
|
||||||
Blob.from_data(
|
Blob.from_data(
|
||||||
await file_data.read(),
|
await file_data.read(),
|
||||||
encoding=params.file_encoding,
|
encoding=params.file_encoding,
|
||||||
path=params.file_name,
|
path=file_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
pages = list(pages)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status.HTTP_400_BAD_REQUEST, f"{e}")
|
||||||
|
|
||||||
return {"pages": pages}
|
return {"pages": pages}
|
||||||
|
@ -254,5 +254,12 @@
|
|||||||
"User Name": "ユーザー名",
|
"User Name": "ユーザー名",
|
||||||
"Assistant Name": "アシスタント名",
|
"Assistant Name": "アシスタント名",
|
||||||
"Insert default system prompt at the beginning": "最初にデフォルトのシステムプロンプトを挿入",
|
"Insert default system prompt at the beginning": "最初にデフォルトのシステムプロンプトを挿入",
|
||||||
"Format Content": "内容フォーマットの規格化"
|
"Format Content": "内容フォーマットの規格化",
|
||||||
|
"Add An Attachment (Accepts pdf, txt)": "添付ファイルを追加 (pdf, txtを受け付けます)",
|
||||||
|
"Uploading Attachment": "添付ファイルアップロード中",
|
||||||
|
"Remove Attachment": "添付ファイルを削除",
|
||||||
|
"The content of file": "ファイル",
|
||||||
|
"is as follows. When replying to me, consider the file content and respond accordingly:": "の内容は以下の通りです。私に返信する際は、ファイルの内容を考慮して適切に返信してください:",
|
||||||
|
"What's the file name": "ファイル名は何ですか",
|
||||||
|
"The file name is: ": "ファイル名は次のとおりです: "
|
||||||
}
|
}
|
@ -254,5 +254,12 @@
|
|||||||
"User Name": "用户名称",
|
"User Name": "用户名称",
|
||||||
"Assistant Name": "AI名称",
|
"Assistant Name": "AI名称",
|
||||||
"Insert default system prompt at the beginning": "在开头自动插入默认系统提示",
|
"Insert default system prompt at the beginning": "在开头自动插入默认系统提示",
|
||||||
"Format Content": "规范格式"
|
"Format Content": "规范格式",
|
||||||
|
"Add An Attachment (Accepts pdf, txt)": "添加一个附件 (支持pdf, txt)",
|
||||||
|
"Uploading Attachment": "正在上传附件",
|
||||||
|
"Remove Attachment": "移除附件",
|
||||||
|
"The content of file": "文件",
|
||||||
|
"is as follows. When replying to me, consider the file content and respond accordingly:": "内容如下。回复时考虑文件内容并做出相应回复:",
|
||||||
|
"What's the file name": "文件名是什么",
|
||||||
|
"The file name is: ": "文件名是:"
|
||||||
}
|
}
|
@ -10,14 +10,22 @@ import { KebabHorizontalIcon, PencilIcon, SyncIcon, TrashIcon } from '@primer/oc
|
|||||||
import logo from '../assets/images/logo.png';
|
import logo from '../assets/images/logo.png';
|
||||||
import MarkdownRender from '../components/MarkdownRender';
|
import MarkdownRender from '../components/MarkdownRender';
|
||||||
import { ToolTipButton } from '../components/ToolTipButton';
|
import { ToolTipButton } from '../components/ToolTipButton';
|
||||||
import { ArrowCircleUp28Regular, Delete28Regular, RecordStop28Regular, Save28Regular } from '@fluentui/react-icons';
|
import {
|
||||||
|
ArrowCircleUp28Regular,
|
||||||
|
ArrowClockwise16Regular,
|
||||||
|
Attach16Regular,
|
||||||
|
Delete28Regular,
|
||||||
|
Dismiss16Regular,
|
||||||
|
RecordStop28Regular,
|
||||||
|
Save28Regular
|
||||||
|
} from '@fluentui/react-icons';
|
||||||
import { CopyButton } from '../components/CopyButton';
|
import { CopyButton } from '../components/CopyButton';
|
||||||
import { ReadButton } from '../components/ReadButton';
|
import { ReadButton } from '../components/ReadButton';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { WorkHeader } from '../components/WorkHeader';
|
import { WorkHeader } from '../components/WorkHeader';
|
||||||
import { DialogButton } from '../components/DialogButton';
|
import { DialogButton } from '../components/DialogButton';
|
||||||
import { OpenFileFolder, OpenSaveFileDialog } from '../../wailsjs/go/backend_golang/App';
|
import { OpenFileFolder, OpenOpenFileDialog, OpenSaveFileDialog } from '../../wailsjs/go/backend_golang/App';
|
||||||
import { toastWithButton } from '../utils';
|
import { bytesToReadable, toastWithButton } from '../utils';
|
||||||
import { PresetsButton } from './PresetsManager/PresetsButton';
|
import { PresetsButton } from './PresetsManager/PresetsButton';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
|
||||||
@ -267,6 +275,16 @@ const ChatPanel: FC = observer(() => {
|
|||||||
let targetRange = commonStore.conversationOrder.slice(startIndex, endIndex);
|
let targetRange = commonStore.conversationOrder.slice(startIndex, endIndex);
|
||||||
|
|
||||||
const messages: ConversationMessage[] = [];
|
const messages: ConversationMessage[] = [];
|
||||||
|
if (commonStore.attachmentContent) {
|
||||||
|
messages.push({
|
||||||
|
role: 'user',
|
||||||
|
content: t('The content of file') + ` "${commonStore.attachmentName}" `
|
||||||
|
+ t('is as follows. When replying to me, consider the file content and respond accordingly:')
|
||||||
|
+ '\n\n' + commonStore.attachmentContent
|
||||||
|
});
|
||||||
|
messages.push({ role: 'user', content: t('What\'s the file name') });
|
||||||
|
messages.push({ role: 'assistant', content: t('The file name is: ') + commonStore.attachmentName });
|
||||||
|
}
|
||||||
targetRange.forEach((uuid, index) => {
|
targetRange.forEach((uuid, index) => {
|
||||||
if (uuid === welcomeUuid)
|
if (uuid === welcomeUuid)
|
||||||
return;
|
return;
|
||||||
@ -385,6 +403,7 @@ const ChatPanel: FC = observer(() => {
|
|||||||
commonStore.setConversation({});
|
commonStore.setConversation({});
|
||||||
commonStore.setConversationOrder([]);
|
commonStore.setConversationOrder([]);
|
||||||
}} />
|
}} />
|
||||||
|
<div className="relative flex grow">
|
||||||
<Textarea
|
<Textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
style={{ minWidth: 0 }}
|
style={{ minWidth: 0 }}
|
||||||
@ -395,6 +414,90 @@ const ChatPanel: FC = observer(() => {
|
|||||||
onChange={(e) => commonStore.setCurrentInput(e.target.value)}
|
onChange={(e) => commonStore.setCurrentInput(e.target.value)}
|
||||||
onKeyDown={handleKeyDownOrClick}
|
onKeyDown={handleKeyDownOrClick}
|
||||||
/>
|
/>
|
||||||
|
<div className="absolute right-2 bottom-2">
|
||||||
|
{!commonStore.attachmentContent ?
|
||||||
|
<ToolTipButton
|
||||||
|
desc={commonStore.attachmentUploading ?
|
||||||
|
t('Uploading Attachment') :
|
||||||
|
t('Add An Attachment (Accepts pdf, txt)')}
|
||||||
|
icon={commonStore.attachmentUploading ?
|
||||||
|
<ArrowClockwise16Regular className="animate-spin" />
|
||||||
|
: <Attach16Regular />}
|
||||||
|
size="small" shape="circular" appearance="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
commonStore.setAttachmentUploading(true);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
const blob = await fetch(`=>${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);
|
||||||
|
fetch(commonStore.settings.apiUrl ?
|
||||||
|
commonStore.settings.apiUrl + urlPath :
|
||||||
|
`http://127.0.0.1:${port}${urlPath}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: bodyForm
|
||||||
|
}).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.setAttachmentName(attachmentName!);
|
||||||
|
commonStore.setAttachmentSize(blob.size);
|
||||||
|
commonStore.setAttachmentContent(attachmentContent);
|
||||||
|
} 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 });
|
||||||
|
});
|
||||||
|
}).catch(e => {
|
||||||
|
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/> :
|
||||||
|
<div>
|
||||||
|
<ToolTipButton
|
||||||
|
text={
|
||||||
|
commonStore.attachmentName.replace(
|
||||||
|
new RegExp('(^[^\\.]{5})[^\\.]+'), '$1...')
|
||||||
|
}
|
||||||
|
desc={`${commonStore.attachmentName} (${bytesToReadable(commonStore.attachmentSize)})`}
|
||||||
|
size="small" shape="circular" appearance="secondary" />
|
||||||
|
<ToolTipButton desc={t('Remove Attachment')}
|
||||||
|
icon={<Dismiss16Regular />}
|
||||||
|
size="small" shape="circular" appearance="subtle"
|
||||||
|
onClick={() => {
|
||||||
|
commonStore.setAttachmentName('');
|
||||||
|
commonStore.setAttachmentSize(0);
|
||||||
|
commonStore.setAttachmentContent('');
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ToolTipButton desc={generating ? t('Stop') : t('Send')}
|
<ToolTipButton desc={generating ? t('Stop') : t('Send')}
|
||||||
icon={generating ? <RecordStop28Regular /> : <ArrowCircleUp28Regular />}
|
icon={generating ? <RecordStop28Regular /> : <ArrowCircleUp28Regular />}
|
||||||
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle"
|
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle"
|
||||||
|
@ -54,6 +54,10 @@ class CommonStore {
|
|||||||
conversation: Conversation = {};
|
conversation: Conversation = {};
|
||||||
conversationOrder: string[] = [];
|
conversationOrder: string[] = [];
|
||||||
activePreset: Preset | null = null;
|
activePreset: Preset | null = null;
|
||||||
|
attachmentUploading: boolean = false;
|
||||||
|
attachmentName: string = '';
|
||||||
|
attachmentSize: number = 0;
|
||||||
|
attachmentContent: string = '';
|
||||||
// completion
|
// completion
|
||||||
completionPreset: CompletionPreset | null = null;
|
completionPreset: CompletionPreset | null = null;
|
||||||
completionGenerating: boolean = false;
|
completionGenerating: boolean = false;
|
||||||
@ -325,6 +329,22 @@ class CommonStore {
|
|||||||
setLoraModels(value: string[]) {
|
setLoraModels(value: string[]) {
|
||||||
this.loraModels = value;
|
this.loraModels = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAttachmentUploading(value: boolean) {
|
||||||
|
this.attachmentUploading = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttachmentName(value: string) {
|
||||||
|
this.attachmentName = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttachmentSize(value: number) {
|
||||||
|
this.attachmentSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttachmentContent(value: string) {
|
||||||
|
this.attachmentContent = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new CommonStore();
|
export default new CommonStore();
|
@ -282,6 +282,13 @@ export function bytesToKb(size: number) {
|
|||||||
return (size / 1024).toFixed(2);
|
return (size / 1024).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bytesToReadable(size: number) {
|
||||||
|
if (size < 1024) return size + ' B';
|
||||||
|
else if (size < 1024 * 1024) return bytesToKb(size) + ' KB';
|
||||||
|
else if (size < 1024 * 1024 * 1024) return bytesToMb(size) + ' MB';
|
||||||
|
else return bytesToGb(size) + ' GB';
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkUpdate(notifyEvenLatest: boolean = false) {
|
export async function checkUpdate(notifyEvenLatest: boolean = false) {
|
||||||
fetch(!commonStore.settings.giteeUpdatesSource ?
|
fetch(!commonStore.settings.giteeUpdatesSource ?
|
||||||
'https://api.github.com/repos/josstorer/RWKV-Runner/releases/latest' :
|
'https://api.github.com/repos/josstorer/RWKV-Runner/releases/latest' :
|
||||||
|
2
frontend/wailsjs/go/backend_golang/App.d.ts
generated
vendored
2
frontend/wailsjs/go/backend_golang/App.d.ts
generated
vendored
@ -34,6 +34,8 @@ export function MergeLora(arg1:string,arg2:boolean,arg3:number,arg4:string,arg5:
|
|||||||
|
|
||||||
export function OpenFileFolder(arg1:string,arg2:boolean):Promise<void>;
|
export function OpenFileFolder(arg1:string,arg2:boolean):Promise<void>;
|
||||||
|
|
||||||
|
export function OpenOpenFileDialog(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function OpenSaveFileDialog(arg1:string,arg2:string,arg3:string):Promise<string>;
|
export function OpenSaveFileDialog(arg1:string,arg2:string,arg3:string):Promise<string>;
|
||||||
|
|
||||||
export function OpenSaveFileDialogBytes(arg1:string,arg2:string,arg3:Array<number>):Promise<string>;
|
export function OpenSaveFileDialogBytes(arg1:string,arg2:string,arg3:Array<number>):Promise<string>;
|
||||||
|
4
frontend/wailsjs/go/backend_golang/App.js
generated
4
frontend/wailsjs/go/backend_golang/App.js
generated
@ -66,6 +66,10 @@ export function OpenFileFolder(arg1, arg2) {
|
|||||||
return window['go']['backend_golang']['App']['OpenFileFolder'](arg1, arg2);
|
return window['go']['backend_golang']['App']['OpenFileFolder'](arg1, arg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function OpenOpenFileDialog(arg1) {
|
||||||
|
return window['go']['backend_golang']['App']['OpenOpenFileDialog'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function OpenSaveFileDialog(arg1, arg2, arg3) {
|
export function OpenSaveFileDialog(arg1, arg2, arg3) {
|
||||||
return window['go']['backend_golang']['App']['OpenSaveFileDialog'](arg1, arg2, arg3);
|
return window['go']['backend_golang']['App']['OpenSaveFileDialog'](arg1, arg2, arg3);
|
||||||
}
|
}
|
||||||
|
1
main.go
1
main.go
@ -27,6 +27,7 @@ func NewFileLoader() *FileLoader {
|
|||||||
func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
requestedFilename := strings.TrimPrefix(req.URL.Path, "/")
|
requestedFilename := strings.TrimPrefix(req.URL.Path, "/")
|
||||||
|
requestedFilename = strings.TrimPrefix(requestedFilename, "=>") // absolute path
|
||||||
println("Requesting file:", requestedFilename)
|
println("Requesting file:", requestedFilename)
|
||||||
fileData, err := os.ReadFile(requestedFilename)
|
fileData, err := os.ReadFile(requestedFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user