Skip to content

Commit d614b81

Browse files
added files attachment functionality
1 parent 5818b83 commit d614b81

File tree

16 files changed

+372
-34
lines changed

16 files changed

+372
-34
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scribe-pal",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"description": "ScribePal is an intelligent browser extension that leverages AI to empower your web experience.",
55
"author": "Code Forge Temple",
66
"type": "module",

src/tab/assets/attachment.svg

Lines changed: 1 addition & 0 deletions
Loading

src/tab/components/ChatBox/ChatBox.tsx

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import React, {useRef, useEffect, useState, useCallback} from "react";
99
import {usePersistentState, useDraggablePosition} from "../../hooks";
10-
import {ChatBoxIds, Model} from "../../utils/types";
10+
import {ChatBoxIds, FileData, Model} from "../../utils/types";
1111
import {EXTENSION_NAME, MESSAGE_TYPES} from "../../../common/constants";
1212
import {ChatInput} from "./components/ChatInput";
1313
import {ChatLog} from "./components/ChatLog";
@@ -18,23 +18,44 @@ import {useKeepInViewport} from "../../hooks/useKeepInViewport";
1818
import {startCapture} from "../../features/capture/captureTool";
1919
import {RichTextModal} from "./components/RichTextModal";
2020
import {ReferencesList} from "./components/ReferencesList";
21-
import {prefixChatBoxId} from "../../utils/utils";
21+
import {getLanguageForExtension, prefixChatBoxId} from "../../utils/utils";
2222
import PromptSvg from '../../assets/eye-solid.svg';
2323
import CaptureTxtSvg from '../../assets/menu-scale.svg';
2424
import CaptureImgSvg from '../../assets/media-image.svg';
25+
import AttachSvg from '../../assets/attachment.svg';
2526
import {useTheme} from "../../hooks/useTheme";
2627
import {polyfillRuntimeSendMessage, polyfillGetTabStorage, polyfillRuntimeConnect, polyfillSetTabStorage} from "../../privilegedAPIs/privilegedAPIs";
2728
import {browser} from "../../../common/browser";
2829
import styles from "./ChatBox.scss?inline";
2930
import {withShadowStyles} from "../../utils/withShadowStyles";
3031
import {startCaptureImage} from "../../features/capture/captureImageTool";
3132
import {ImageModal} from "./components/ImageModal";
32-
import {CAPTURED_IMAGE_TAG, CAPTURED_TAG} from "../../utils/constants";
33+
import {ATTACHED_TAG, CAPTURED_IMAGE_TAG, CAPTURED_TAG} from "../../utils/constants";
34+
import {FilesModal} from "./components/FilesModal";
3335

34-
const formatMessage = (message: string, capturedText: string, capturedImage: string) => {
35-
let newMessage = message.replace(CAPTURED_IMAGE_TAG, capturedImage ? (`\n![Captured Image](${capturedImage})\n`) : "");
36+
const formatMessage = (message: string, {capturedText, capturedImage, attachedFiles}: {capturedText: string, capturedImage: string, attachedFiles: FileData[]}) => {
37+
let newMessage = message.replace(
38+
CAPTURED_IMAGE_TAG,
39+
capturedImage ? (`\n![Captured Image](${capturedImage})\n`) : ""
40+
);
41+
42+
let attachedFilesContents: string|undefined = "";
43+
44+
for(const attachedFile of attachedFiles) {
45+
const fileExtension = attachedFile.name.split(".").pop()?.toLowerCase();
46+
const language = getLanguageForExtension(fileExtension);
47+
48+
attachedFilesContents += `\n\`\`\`${language}\n${attachedFile.content}\n\`\`\`\n`;
49+
}
50+
51+
if(attachedFilesContents) {
52+
newMessage = newMessage.replace(ATTACHED_TAG, attachedFilesContents);
53+
}
3654

37-
newMessage = newMessage.replace(CAPTURED_TAG, capturedText ? ("\n```text\n" + capturedText + "\n```\n") : "");
55+
newMessage = newMessage.replace(
56+
CAPTURED_TAG,
57+
capturedText ? ("\n```text\n" + capturedText + "\n```\n") : ""
58+
);
3859

3960
return newMessage;
4061
}
@@ -70,6 +91,10 @@ export const ChatBox = withShadowStyles(({tabId, chatBoxId, onRemove, coordsOffs
7091
const [capturedImageModalVisible, setCapturedImageModalVisible] = useState<boolean>(false);
7192
const [promptMessage, setPromptMessage] = usePersistentState<string>("promptMessage", "", {tabId, chatBoxId});
7293
const [promptModalVisible, setPromptModalVisible] = useState<boolean>(false);
94+
95+
const [attachedFiles, setAttachedFiles] = usePersistentState<FileData[]>("attachedFiles", [], {tabId, chatBoxId});
96+
const [attachedFilesModalVisible, setAttachedFilesModalVisible] = useState<boolean>(false);
97+
7398
const updateTheme = useTheme(boxRef);
7499

75100
const fetchModels = useCallback(() => {
@@ -157,7 +182,7 @@ export const ChatBox = withShadowStyles(({tabId, chatBoxId, onRemove, coordsOffs
157182
const handleSend = async () => {
158183
if (message.trim() === "") return;
159184

160-
const formattedMessage = formatMessage(message, capturedText, capturedImage);
185+
const formattedMessage = formatMessage(message, {capturedText, capturedImage, attachedFiles});
161186

162187
const conversationHistory = chatLog
163188
.filter((msg) => !msg.loading)
@@ -264,6 +289,38 @@ export const ChatBox = withShadowStyles(({tabId, chatBoxId, onRemove, coordsOffs
264289
);
265290
};
266291

292+
const fileInputRef = useRef<HTMLInputElement>(null);
293+
294+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
295+
const files = event.target.files;
296+
297+
if (!files) return;
298+
299+
if (files.length > 0) {
300+
for (let i = 0; i < files.length; i++) {
301+
const file = files[i];
302+
const reader = new FileReader();
303+
304+
reader.onload = (e) => {
305+
const fileContent = e.target?.result as string;
306+
307+
const fileData: FileData = {
308+
name: file.name,
309+
content: fileContent,
310+
};
311+
312+
setAttachedFiles((prevFiles) => [...prevFiles, fileData]);
313+
};
314+
315+
reader.readAsText(file);
316+
}
317+
318+
if (fileInputRef.current) {
319+
fileInputRef.current.value = "";
320+
}
321+
}
322+
};
323+
267324
return (
268325
<div
269326
id={prefixChatBoxId(chatBoxId)}
@@ -302,6 +359,14 @@ export const ChatBox = withShadowStyles(({tabId, chatBoxId, onRemove, coordsOffs
302359
}
303360
}
304361
/>
362+
<input
363+
ref={fileInputRef}
364+
type="file"
365+
multiple
366+
accept=".txt,.md,.html,.css,.scss,.js,.ts,.tsx,.json,.xml,.csv,.yaml,.yml,.ini,.log,.sh,.sql,.py,.java,.c,.cpp,.h,.bat,.env"
367+
style={{display: "none"}}
368+
onChange={handleFileChange}
369+
/>
305370
<Tools actions={[
306371
{
307372
call: () => {
@@ -331,14 +396,23 @@ export const ChatBox = withShadowStyles(({tabId, chatBoxId, onRemove, coordsOffs
331396
label: <>Capture <CaptureImgSvg className={capturedImage ? "icon" : ""} /></>,
332397
tooltip: "capture image",
333398
},
399+
{
400+
call: () => {
401+
if (fileInputRef.current) {
402+
fileInputRef.current.click();
403+
}
404+
},
405+
label: <>Attach <AttachSvg className={attachedFiles.length ? "icon" : ""} /></>,
406+
tooltip: "attach files",
407+
},
334408
{
335409
call: () => {
336410
setPromptModalVisible(true);
337411
},
338412
label: "Prompt",
339413
tooltip: "prompt",
340414
icon: promptMessage ? <PromptSvg className="icon" /> : undefined
341-
}
415+
},
342416
]} />
343417
<ReferencesList list={[
344418
... (capturedText ? [
@@ -365,13 +439,30 @@ export const ChatBox = withShadowStyles(({tabId, chatBoxId, onRemove, coordsOffs
365439
}
366440
}
367441
] : []),
442+
... (attachedFiles.length ? [
443+
{
444+
text: ATTACHED_TAG,
445+
tooltip: "Click to view attached files",
446+
onClick: () => {
447+
setAttachedFilesModalVisible(true);
448+
},
449+
onClose: () => {
450+
setAttachedFiles([]);
451+
}
452+
}
453+
] : []),
368454

369455
]} />
370456
<RichTextModal
371457
visible={capturedModalVisible}
372458
richText={capturedText}
373459
closeButtonName="Save"
374460
onUpdate={(txt:string) => { setCapturedText(txt); setCapturedModalVisible(false); }} />
461+
<FilesModal
462+
visible={attachedFilesModalVisible}
463+
files={attachedFiles}
464+
closeButtonName="Save"
465+
onUpdate={(files: FileData[]) => { setAttachedFiles(files); setAttachedFilesModalVisible(false); }} />
375466
<ImageModal
376467
visible={capturedImageModalVisible}
377468
imageBase64={capturedImage}

src/tab/components/ChatBox/components/ChatLog/ChatLog.scss

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
@use "../../../../../common/styles/variables.scss" as commonVars;
2+
@use "../../../../styles/variables.scss" as vars;
3+
24

35
.chat-log {
46
background: white;
@@ -26,7 +28,7 @@
2628

2729
.delete-message,
2830
.copy-message {
29-
color: #551a8b;
31+
color: vars.$default-highlight-color;
3032
}
3133

3234
svg {
@@ -65,7 +67,7 @@
6567
}
6668

6769
&:visited {
68-
color: #551a8b;
70+
color: vars.$default-highlight-color;
6971
}
7072

7173
&:active {
@@ -76,7 +78,7 @@
7678

7779
.scroll-to-end {
7880
border: none;
79-
border-radius: 4px;
81+
border-radius: vars.$default-border-radius;
8082
cursor: pointer;
8183
color: #fff;
8284
text-align: center;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
@use "../../../../../common/styles/variables.scss" as commonVars;
2+
@use "../../../../styles/mixins.scss" as mixins;
3+
@use "../../../../styles/variables.scss" as vars;
4+
5+
.attached-files-modal {
6+
@include mixins.modal-window;
7+
8+
.contents {
9+
display: flex;
10+
height: 300px;
11+
12+
.list-group {
13+
width: 300px;
14+
display: flex;
15+
flex-direction: column;
16+
padding: 0;
17+
margin: 0;
18+
height: auto;
19+
overflow-y: auto;
20+
margin-right: 3px;
21+
22+
.list-group-item {
23+
position: relative;
24+
display: flex;
25+
flex-direction: row;
26+
flex-wrap: nowrap;
27+
justify-content: space-between;
28+
align-items: center;
29+
padding: 5px;
30+
margin-right: 5px;
31+
border-radius: vars.$default-border-radius;
32+
33+
.file-name {
34+
display: inline-block;
35+
max-width: 230px;
36+
white-space: nowrap;
37+
overflow: hidden;
38+
text-overflow: ellipsis;
39+
vertical-align: middle;
40+
}
41+
42+
.delete-file {
43+
color: vars.$default-highlight-color;
44+
display: block;
45+
height: 22px;
46+
width: auto;
47+
margin-right: 5px;
48+
cursor: pointer;
49+
}
50+
51+
.delete-file:hover {
52+
color: white;
53+
}
54+
}
55+
56+
.list-group-item:hover {
57+
cursor: pointer;
58+
background-color: #0d6efd;
59+
color: white;
60+
}
61+
}
62+
63+
.textarea {
64+
@include mixins.main-textarea;
65+
width: 600px;
66+
height: auto;
67+
white-space: pre-wrap;
68+
}
69+
}
70+
71+
.modal-buttons {
72+
display: flex;
73+
justify-content: flex-end;
74+
margin-top: 10px;
75+
76+
button {
77+
@include mixins.button-style;
78+
margin-left: 10px;
79+
}
80+
}
81+
}
82+
83+
.dark-theme {
84+
.attached-files-modal {
85+
background: commonVars.$dark-theme-primary-color;
86+
border-color: commonVars.$dark-theme-primary-color;
87+
88+
.textarea{
89+
background: commonVars.$dark-theme-secondary-color;
90+
color: white;
91+
border-color: commonVars.$dark-theme-secondary-color;
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)