Skip to content

Commit

Permalink
chore: adds context handling for copy to clipboard, added permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Adebesin-Cell committed Dec 28, 2024
1 parent 838475e commit 5a45d6c
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 71 deletions.
1 change: 1 addition & 0 deletions manifest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default defineManifest(async (env) => ({
'contextMenus',
'tabs',
'activeTab',
'clipboardWrite',
],
background: {
service_worker: 'src/pages/background/index.ts',
Expand Down
106 changes: 67 additions & 39 deletions src/components/Sidebar/chat/ChatList.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,75 @@
import { useEffect, useRef } from 'react'
import { RiCloseLine, RiErrorWarningLine, RiLoader4Line } from 'react-icons/ri'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
import rehypeRaw from 'rehype-raw'
import remarkBreaks from 'remark-breaks'
import remarkGfm from 'remark-gfm'
import { type ChatMessage, ChatRole } from '../../../hooks/useCurrentChat'
import FilePreviewBar from './FilePreviewBar'
import CodeBlock from './markdown-components/CodeBlock'
import { Table } from './markdown-components/Table'
import { useEffect, useRef } from 'react';
import {
RiCloseLine,
RiErrorWarningLine,
RiLoader4Line,
RiFileCopyLine
} from 'react-icons/ri';
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkBreaks from 'remark-breaks';
import remarkGfm from 'remark-gfm';
import { type ChatMessage, ChatRole } from '../../../hooks/useCurrentChat';
import FilePreviewBar from './FilePreviewBar';
import CodeBlock from './markdown-components/CodeBlock';
import { Table } from './markdown-components/Table';

interface ChatListProps {
messages: ChatMessage[]
removeMessagePair: (timestamp: number) => void
generating: boolean
error: Error | null
messages: ChatMessage[];
removeMessagePair: (timestamp: number) => void;
generating: boolean;
error: Error | null;
}

const ChatList = ({
messages,
removeMessagePair,
generating,
error,
error
}: ChatListProps) => {
const containerRef = useRef<HTMLDivElement>(null)
const containerRef = useRef<HTMLDivElement>(null);

// biome-ignore lint/correctness/useExhaustiveDependencies: This is intentional, we need this for scroll to bottom
useEffect(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [messages]) // Add messages as a dependency
}, [messages]); // Add messages as a dependency

const filteredMsgs = messages.filter((msg) => msg.role !== ChatRole.SYSTEM)
const filteredMsgs = messages.filter((msg) => msg.role !== ChatRole.SYSTEM);

const formatContent = (content: string) => {
return content.replace(/(?<=\n\n)(?![*-])\n/gi, '&nbsp;\n ');
};

const handleCopyMessage = (content: string) => {
window.parent.postMessage(
{
action: 'copy-to-clipboard',
_payload: { content }
},
'*'
);
};

return (
<div
ref={containerRef}
className="cdx-h-[calc(100vh-200px)] cdx-text-sm cdx-overflow-y-auto cdx-pb-12 cdx-break-words"
className='cdx-h-[calc(100vh-200px)] cdx-text-sm cdx-overflow-y-auto cdx-pb-12 cdx-break-words'
>
{filteredMsgs.length < 1 ? (
<div className="cdx-mt-10 cdx-text-center">
<div className='cdx-mt-10 cdx-text-center'>
<img
alt="robot"
src="/images/robot.png"
className="cdx-mx-auto"
alt='robot'
src='/images/robot.png'
className='cdx-mx-auto'
height={300}
width={300}
/>
<h1 className="cdx-text-xl cdx-text-gray-500 dark:cdx-text-gray-400">
<h1 className='cdx-text-xl cdx-text-gray-500 dark:cdx-text-gray-400'>
Start a new conversation 🎉
</h1>
<p className="cdx-text-gray-500 dark:cdx-text-gray-400 cdx-mt-1 cdx-leading-tight cdx-font-light">
<p className='cdx-text-gray-500 dark:cdx-text-gray-400 cdx-mt-1 cdx-leading-tight cdx-font-light'>
Type your message at the bottom <br /> and press send button
</p>
</div>
Expand All @@ -60,44 +79,53 @@ const ChatList = ({
.map((msg, i) => (
<div
data-user={msg.role === ChatRole.USER ? 'true' : undefined}
className="markdown cdx-group cdx-relative cdx-px-4 cdx-py-2 data-[user]:cdx-border-l-2 cdx-border-blue-400 data-[user]:cdx-bg-black/5 data-[user]:dark:cdx-bg-neutral-900/50 cdx-max-w-[400px]"
className='markdown cdx-group cdx-relative cdx-px-4 cdx-py-2 data-[user]:cdx-border-l-2 cdx-border-blue-400 data-[user]:cdx-bg-black/5 data-[user]:dark:cdx-bg-neutral-900/50 cdx-max-w-[400px]'
key={`${msg.timestamp}-${i}`}
>
{msg.role === ChatRole.USER && (
<button
type="button"
type='button'
onClick={() => removeMessagePair(msg.timestamp)}
className="cdx-absolute group-hover:cdx-visible cdx-invisible cdx-right-2 cdx-top-2 cdx-p-0.5 cdx-bg-black/20 cdx-rounded"
className='cdx-absolute group-hover:cdx-visible cdx-invisible cdx-right-2 cdx-top-2 cdx-p-0.5 cdx-bg-black/20 cdx-rounded'
>
<RiCloseLine />
</button>
)}
{msg.role === ChatRole.ASSISTANT && (
<button
type='button'
onClick={() => handleCopyMessage(formatContent(msg.content))}
className='cdx-absolute group-hover:cdx-visible cdx-invisible cdx-right-2 cdx-top-2 cdx-p-0.5 cdx-bg-black/20 cdx-rounded'
>
<RiFileCopyLine />
</button>
)}
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkBreaks]}
rehypePlugins={[rehypeRaw]}
components={{
code: CodeBlock,
table: Table,
table: Table
}}
>
{msg.content.replace(/(?<=\n\n)(?![*-])\n/gi, '&nbsp;\n ')}
{formatContent(msg.content)}
</ReactMarkdown>
{msg.files && <FilePreviewBar files={msg.files} />}
</div>
))
)}
{messages[messages.length - 1]?.role === ChatRole.USER && (
<div className="cdx-text-neutral-500">
<div className='cdx-text-neutral-500'>
{generating && !error && (
<div className="cdx-animate-pulse cdx-mt-4 cdx-flex cdx-justify-center cdx-items-center cdx-gap-2">
<RiLoader4Line className="cdx-animate-spin" />
<div className='cdx-animate-pulse cdx-mt-4 cdx-flex cdx-justify-center cdx-items-center cdx-gap-2'>
<RiLoader4Line className='cdx-animate-spin' />
<span>Generating</span>
</div>
)}
{error && (
<div className="cdx-p-4 cdx-flex cdx-items-center cdx-gap-4 cdx-bg-red-500/10">
<div className='cdx-p-4 cdx-flex cdx-items-center cdx-gap-4 cdx-bg-red-500/10'>
<RiErrorWarningLine
className="cdx-text-red-500 cdx-flex-shrink-0"
className='cdx-text-red-500 cdx-flex-shrink-0'
size={20}
/>
<span>{error.message}</span>
Expand All @@ -106,7 +134,7 @@ const ChatList = ({
</div>
)}
</div>
)
}
);
};

export default ChatList
export default ChatList;
70 changes: 38 additions & 32 deletions src/pages/content/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import type { Settings } from '../../config/settings'
import { getScreenshotImage } from '../../lib/getScreenshotImage'
import { contentScriptLog } from '../../logs'
import type { Settings } from '../../config/settings';
import { getScreenshotImage } from '../../lib/getScreenshotImage';
import { contentScriptLog } from '../../logs';

contentScriptLog('Sidebar')
contentScriptLog('Sidebar');

const iframe = document.createElement('iframe')
iframe.style.background = '#0000002a'
iframe.style.height = '100%'
iframe.style.width = '0px'
iframe.style.position = 'fixed'
iframe.style.top = '0px'
iframe.style.right = '0px'
iframe.style.zIndex = '9000000000000000000'
iframe.style.border = '0px'
iframe.style.colorScheme = 'auto'
iframe.src = chrome.runtime.getURL('/src/pages/sidebar/index.html')
iframe.id = 'syncia_sidebar'
const iframe = document.createElement('iframe');
iframe.style.background = '#0000002a';
iframe.style.height = '100%';
iframe.style.width = '0px';
iframe.style.position = 'fixed';
iframe.style.top = '0px';
iframe.style.right = '0px';
iframe.style.zIndex = '9000000000000000000';
iframe.style.border = '0px';
iframe.style.colorScheme = 'auto';
iframe.src = chrome.runtime.getURL('/src/pages/sidebar/index.html');
iframe.id = 'syncia_sidebar';

document.body.appendChild(iframe)
document.body.appendChild(iframe);

/**
* BG SCRIPT <-> CONTENT SCRIPT
Expand All @@ -28,12 +28,12 @@ document.body.appendChild(iframe)
chrome.runtime.onMessage.addListener((msg) => {
if (msg.action === 'open-sidebar') {
if (iframe.style.width === '0px') {
iframe.style.width = '400px'
iframe.style.width = '400px';
} else {
iframe.style.width = '0px'
iframe.style.width = '0px';
}
}
})
});

/**
* SIDEBAR <-> CONTENT SCRIPT
Expand All @@ -42,31 +42,37 @@ chrome.runtime.onMessage.addListener((msg) => {
* The page content is sent back to the sidebar by posting a message with the action 'get-page-content'.
*/
window.addEventListener('message', async (event) => {
const { action, _payload } = event.data as { action: string; _payload: any }
const { action, _payload } = event.data as { action: string; _payload: any };

// ACTION: get-page-content ==============================
if (action === 'get-page-content') {
const pageContent = document.body.innerText
const pageContent = document.body.innerText;
iframe.contentWindow?.postMessage(
{
action: 'get-page-content',
payload: pageContent,
payload: pageContent
},
'*',
)
'*'
);
}

// ACTION: copy-to-clipboard =============================
if (action === 'copy-to-clipboard') {
const { content } = _payload as { content: string };
navigator.clipboard.writeText(content);
}

// ACTION: get-screenshot-image ===========================
if (action === 'get-screenshot-image') {
iframe.style.width = '0px'
const image = await getScreenshotImage()
iframe.style.width = '400px'
iframe.style.width = '0px';
const image = await getScreenshotImage();
iframe.style.width = '400px';
iframe.contentWindow?.postMessage(
{
action: 'get-screenshot-image',
payload: image,
payload: image
},
'*',
)
'*'
);
}
})
});

0 comments on commit 5a45d6c

Please sign in to comment.