Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added chatbox issue 1.mp4
Binary file not shown.
Binary file added chatbox issue 2.mp4
Binary file not shown.
633 changes: 630 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"update-electron-app": "^2.0.1",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"yarn": "^1.22.22"
}
}
60 changes: 60 additions & 0 deletions src/devtools/App.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,63 @@
.App {
text-align: center;
}

.code-block-wrapper {
position: relative;
margin: 1em 0;
}

.code-copy-btn {
position: absolute;
top: 8px;
right: 8px;
padding: 6px 12px;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
color: #333;
display: flex;
align-items: center;
gap: 4px;
opacity: 0;
transition: opacity 0.2s ease, background-color 0.2s ease;
z-index: 1;
}

.code-block-wrapper:hover .code-copy-btn {
opacity: 1;
}

.code-copy-btn:hover {
background-color: rgba(255, 255, 255, 1);
border-color: rgba(0, 0, 0, 0.2);
}

.code-copy-btn:active {
background-color: rgba(240, 240, 240, 1);
}

.code-copy-btn.copied {
background-color: #4caf50;
color: white;
border-color: #4caf50;
}

.code-copy-btn svg {
width: 14px;
height: 14px;
}

/* Drag and Drop Session Styling */
.session-item-dragging {
opacity: 0.5;
cursor: grabbing !important;
}

.session-item-drag-over {
border-top: 2px solid #1976d2;
background-color: rgba(25, 118, 210, 0.08);
}
6 changes: 4 additions & 2 deletions src/devtools/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ function Main() {
store.chatSessions.map((session, ix) => (
<SessionItem selected={store.currentSession.id === session.id}
session={session}
index={ix}
switchMe={() => {
store.switchCurrentSession(session)
document.getElementById('message-input')?.focus() // better way?
Expand All @@ -207,6 +208,7 @@ function Main() {
store.createChatSession(newSession, ix)
}}
editMe={() => setConfigureChatConfig(session)}
reorderSession={store.reorderSessions}
/>
))
}
Expand Down Expand Up @@ -423,7 +425,7 @@ function MessageInput(props: {
if (event) {
event.preventDefault()
}
if (messageInput.length === 0) {
if (messageInput.trim().length === 0) {
return
}
props.onSubmit(createMessage('user', messageInput))
Expand All @@ -443,7 +445,7 @@ function MessageInput(props: {
autoFocus
id='message-input'
onKeyDown={(event) => {
if (event.keyCode === 13 && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
event.preventDefault()
submit()
return
Expand Down
50 changes: 49 additions & 1 deletion src/devtools/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,17 @@ const md = new MarkdownIt({
} else {
content = md.utils.escapeHtml(str)
}
return `<pre class="hljs" style="max-width: 50vw; overflow: auto"><code>${content}</code></pre>`;
const escapedCode = md.utils.escapeHtml(str)
return `<div class="code-block-wrapper">
<button class="code-copy-btn" data-code="${escapedCode}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy
</button>
<pre class="hljs" style="max-width: 50vw; overflow: auto"><code>${content}</code></pre>
</div>`;
}
});
md.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
Expand All @@ -65,6 +75,7 @@ function _Block(props: Props) {
const { msg, setMsg } = props
const [isHovering, setIsHovering] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const contentRef = useRef<HTMLDivElement>(null)

const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
Expand All @@ -75,6 +86,42 @@ function _Block(props: Props) {
setAnchorEl(null);
};

useEffect(() => {
if (!contentRef.current) return

const handleCopyClick = (e: Event) => {
const button = e.currentTarget as HTMLButtonElement
const code = button.getAttribute('data-code')
if (!code) return

navigator.clipboard.writeText(code).then(() => {
const originalText = button.innerHTML
button.classList.add('copied')
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>Copied!`

setTimeout(() => {
button.classList.remove('copied')
button.innerHTML = originalText
}, 2000)
}).catch(err => {
console.error('Failed to copy:', err)
})
}

const copyButtons = contentRef.current.querySelectorAll('.code-copy-btn')
copyButtons.forEach(button => {
button.addEventListener('click', handleCopyClick)
})

return () => {
copyButtons.forEach(button => {
button.removeEventListener('click', handleCopyClick)
})
}
}, [msg.content])


const tips: string[] = []
if (props.showWordCount) {
Expand Down Expand Up @@ -151,6 +198,7 @@ function _Block(props: Props) {
/>
) : (
<Box
ref={contentRef}
sx={{
// bgcolor: "Background",
}}
Expand Down
55 changes: 54 additions & 1 deletion src/devtools/SessionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@ const { useState } = React
export interface Props {
session: Session
selected: boolean
index: number
switchMe: () => void
deleteMe: () => void
copyMe: () => void
editMe: () => void
reorderSession: (fromIndex: number, toIndex: number) => void
}

export default function SessionItem(props: Props) {
const { session, selected, switchMe, deleteMe, copyMe, editMe } = props
const { session, selected, switchMe, deleteMe, copyMe, editMe, index, reorderSession } = props
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [isDragOver, setIsDragOver] = useState(false);

const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault()
Expand All @@ -38,11 +43,59 @@ export default function SessionItem(props: Props) {
setAnchorEl(null);
};

const handleDragStart = (e: React.DragEvent) => {
setIsDragging(true);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', index.toString());
};

const handleDragEnd = () => {
setIsDragging(false);
};

const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};

const handleDragEnter = () => {
setIsDragOver(true);
};

const handleDragLeave = () => {
setIsDragOver(false);
};

const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(false);
const fromIndex = parseInt(e.dataTransfer.getData('text/plain'));
if (fromIndex !== index) {
reorderSession(fromIndex, index);
}
};

return (
<MenuItem
key={session.id}
selected={selected}
onClick={() => switchMe()}
draggable
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
sx={{
opacity: isDragging ? 0.5 : 1,
borderTop: isDragOver ? '2px solid #1976d2' : 'none',
transition: 'opacity 0.2s ease',
cursor: 'grab',
'&:active': {
cursor: 'grabbing',
},
}}
>
<ListItemIcon>
<IconButton><ChatBubbleOutlineOutlinedIcon fontSize="small" /></IconButton>
Expand Down
7 changes: 7 additions & 0 deletions src/devtools/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ export default function useStore() {
const createEmptyChatSession = () => {
createChatSession(createSession())
}
const reorderSessions = (fromIndex: number, toIndex: number) => {
const sessions = [...chatSessions]
const [movedSession] = sessions.splice(fromIndex, 1)
sessions.splice(toIndex, 0, movedSession)
setSessions(sessions)
}

const setMessages = (session: Session, messages: Message[]) => {
updateChatSession({
Expand Down Expand Up @@ -180,6 +186,7 @@ export default function useStore() {
updateChatSession,
deleteChatSession,
createEmptyChatSession,
reorderSessions,

currentSession,
switchCurrentSession,
Expand Down
Loading