Merge pull request #52 from anetaj/feature/style-include-files

Input box style
This commit is contained in:
Andrew Pareles 2024-10-11 17:12:18 -07:00 committed by GitHub
commit 1b8271d217
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 180 additions and 124 deletions

View file

@ -1,6 +1,6 @@
import React, { useState, ChangeEvent, useEffect, useRef, useCallback, FormEvent } from "react"
import { ApiConfig, LLMMessage, sendLLMMessage } from "../common/sendLLMMessage"
import { Command, File, Selection, WebviewMessage } from "../shared_types"
import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react"
import { ApiConfig, sendLLMMessage } from "../common/sendLLMMessage"
import { File, Selection, WebviewMessage } from "../shared_types"
import { awaitVSCodeResponse, getVSCodeAPI, resolveAwaitingVSCodeResponse } from "./getVscodeApi"
import { marked } from 'marked';
@ -8,6 +8,7 @@ import MarkdownRender from "./markdown/MarkdownRender";
import BlockCode from "./markdown/BlockCode";
import * as vscode from 'vscode'
import { FilesSelector, IncludedFiles } from "./components/Files";
const filesStr = (fullFiles: File[]) => {
@ -36,40 +37,6 @@ If you make a change, rewrite the entire file.
}
const FilesSelector = ({ files, setFiles }: { files: vscode.Uri[], setFiles: (files: vscode.Uri[]) => void }) => {
return files.length !== 0 && <div className='my-2'>
Include files:
{files.map((filename, i) =>
<div key={i} className='flex'>
{/* X button on a file */}
<button type='button' onClick={() => {
let file_index = files.indexOf(filename)
setFiles([...files.slice(0, file_index), ...files.slice(file_index + 1, Infinity)])
}}>
-{' '}<span className='text-gray-500'>{getBasename(filename.fsPath)}</span>
</button>
</div>
)}
</div>
}
const IncludedFiles = ({ files }: { files: vscode.Uri[] }) => {
return files.length !== 0 && <div className='text-xs my-2'>
{files.map((filename, i) =>
<div key={i} className='flex'>
<button type='button'
className='btn btn-secondary pointer-events-none'
onClick={() => {
// TODO redirect to the document filename.fsPath, when add this remove pointer-events-none
}}>
-{' '}<span className='text-gray-100'>{getBasename(filename.fsPath)}</span>
</button>
</div>
)}
</div>
}
const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
const role = chatMessage.role
@ -100,13 +67,6 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
</div>
}
const getBasename = (pathStr: string) => {
// "unixify" path
pathStr = pathStr.replace(/[/\\]+/g, '/'); // replace any / or \ or \\ with /
const parts = pathStr.split('/') // split on /
return parts[parts.length - 1]
}
type ChatMessage = {
role: 'user'
content: string, // content sent to the llm
@ -265,62 +225,62 @@ const Sidebar = () => {
</div>
{/* chatbar */}
<div className="shrink-0 py-4">
{/* selection */}
<div className="text-left">
{/* selected files */}
<FilesSelector files={files} setFiles={setFiles} />
{/* selected code */}
{!selection?.selectionStr ? null
: (
<div className="relative">
<div className="input">
{/* selection */}
{(files.length || selection?.selectionStr) && <div className="p-2 pb-0 space-y-2">
{/* selected files */}
<FilesSelector files={files} setFiles={setFiles} />
{/* selected code */}
{!!selection?.selectionStr && (
<BlockCode className="rounded bg-vscode-sidebar-bg" text={selection.selectionStr} toolbar={(
<button
onClick={clearSelection}
className="absolute top-2 right-2 text-white hover:text-gray-300 z-10"
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
>
X
Remove
</button>
<BlockCode text={selection.selectionStr} hideToolbar />
</div>
)} />
)}
</div>}
<form
ref={formRef}
className="flex flex-row items-center rounded-md p-2"
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) onSubmit(e) }}
onSubmit={(e) => {
console.log('submit!')
e.preventDefault();
onSubmit(e)
}}>
{/* input */}
<textarea
onChange={(e) => { setInstructions(e.target.value) }}
className="w-full p-2 leading-tight resize-none max-h-[50vh] overflow-hidden bg-transparent border-none !outline-none"
placeholder="Ctrl+L to select"
rows={1}
onInput={e => { e.currentTarget.style.height = 'auto'; e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px' }} // Adjust height dynamically
/>
{/* submit button */}
{isLoading ?
<button
onClick={onStop}
className="btn btn-primary rounded-r-lg max-h-10 p-2"
type='button'
>Stop</button>
: <button
className="btn btn-primary font-bold size-8 flex justify-center items-center rounded-full p-2 max-h-10"
disabled={!instructions}
type='submit'
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</button>
}
</form>
</div>
<form
ref={formRef}
className="flex flex-row items-center rounded-md p-2 input"
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) onSubmit(e) }}
onSubmit={(e) => {
console.log('submit!')
e.preventDefault();
onSubmit(e)
}}>
{/* input */}
<textarea
onChange={(e) => { setInstructions(e.target.value) }}
className="w-full p-2 leading-tight resize-none max-h-[50vh] overflow-hidden bg-transparent border-none !outline-none"
placeholder="Ctrl+L to select"
rows={1}
onInput={e => { e.currentTarget.style.height = 'auto'; e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px' }} // Adjust height dynamically
/>
{/* submit button */}
{isLoading ?
<button
onClick={onStop}
className="btn btn-primary rounded-r-lg max-h-10 p-2"
type='button'
>Stop</button>
: <button
className="btn btn-primary font-bold size-8 flex justify-center items-center rounded-full p-2 max-h-10"
disabled={!instructions}
type='submit'
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</button>
}
</form>
</div>
</div>

View file

@ -0,0 +1,77 @@
import React from "react"
import * as vscode from "vscode"
const getBasename = (pathStr: string) => {
// "unixify" path
pathStr = pathStr.replace(/[/\\]+/g, "/") // replace any / or \ or \\ with /
const parts = pathStr.split("/") // split on /
return parts[parts.length - 1]
}
export const FilesSelector = ({
files,
setFiles,
}: {
files: vscode.Uri[]
setFiles: (files: vscode.Uri[]) => void
}) => {
const onRemove = (index: number) => () =>
setFiles([...files.slice(0, index), ...files.slice(index + 1, Infinity)])
return (
files.length !== 0 && (
<div className="flex flex-wrap -mx-1 -mb-1">
{files.map((filename, i) => (
<button
key={filename.path}
className="btn btn-secondary btn-sm border border-vscode-input-border rounded flex items-center space-x-2 mx-1 mb-1"
type="button"
onClick={onRemove(i)}
>
<span>{getBasename(filename.fsPath)}</span>
<span className="">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="size-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</span>
</button>
))}
</div>
)
)
}
export const IncludedFiles = ({ files }: { files: vscode.Uri[] }) => {
return (
files.length !== 0 && (
<div className="text-xs my-2">
{files.map((filename, i) => (
<div key={i} className="flex">
<button
type="button"
className="btn btn-secondary pointer-events-none"
onClick={() => {
// TODO redirect to the document filename.fsPath, when add this remove pointer-events-none
}}
>
-{" "}
<span className="text-gray-100">
{getBasename(filename.fsPath)}
</span>
</button>
</div>
))}
</div>
)
)
}

View file

@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useState } from "react"
import React, { ReactNode, useCallback, useEffect, useState } from "react"
import { getVSCodeAPI } from "../getVscodeApi"
import { classNames } from "../utils"
enum CopyButtonState {
Copy = "Copy",
@ -12,10 +13,14 @@ const COPY_FEEDBACK_TIMEOUT = 1000
// code block with toolbar (Apply, Copy, etc) at top
const BlockCode = ({
text,
toolbar,
hideToolbar = false,
className,
}: {
text: string
toolbar?: ReactNode
hideToolbar?: boolean
className?: string
}) => {
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
@ -38,32 +43,40 @@ const BlockCode = ({
)
}, [text])
const defaultToolbar = (
<>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={onCopy}
>
{copyButtonState}
</button>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
getVSCodeAPI().postMessage({ type: "applyCode", code: text })
}}
>
Apply
</button>
</>
)
return (
<div className="relative group">
{!hideToolbar && (
<div className="absolute top-0 right-0 invisible group-hover:visible">
<div className="flex space-x-2 p-2">
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={onCopy}
>
{copyButtonState}
</button>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
getVSCodeAPI().postMessage({ type: "applyCode", code: text })
}}
>
Apply
</button>
</div>
<div className="flex space-x-2 p-2">{toolbar || defaultToolbar}</div>
</div>
)}
<div
className={`overflow-x-auto rounded-sm text-vscode-editor-fg bg-vscode-editor-bg ${hideToolbar ? "" : "rounded-tl-none"}`}
className={classNames(
"overflow-x-auto rounded-sm text-vscode-editor-fg bg-vscode-editor-bg",
!hideToolbar && "rounded-tl-none",
className
)}
>
<pre className="p-4">{text}</pre>
<pre className="p-2">{text}</pre>
</div>
</div>
)

View file

@ -0,0 +1,3 @@
export default function classNames(...classes: any[]) {
return classes.filter(Boolean).join(' ')
}

View file

@ -0,0 +1 @@
export { default as classNames } from "./classNames";

View file

@ -7,17 +7,19 @@ module.exports = {
extend: {
colors: {
vscode: {
'editor-bg': "var(--vscode-editor-background)",
'editor-fg': "var(--vscode-editor-foreground)",
'input-bg': "var(--vscode-input-background)",
'input-fg': "var(--vscode-input-foreground)",
'input-border': "var(--vscode-input-border)",
'button-fg': "var(--vscode-button-foreground)",
'button-bg': "var(--vscode-button-background)",
'button-hoverBg': "var(--vscode-button-hoverBackground)",
'button-secondary-fg': "var(--vscode-button-secondaryForeground)",
'button-secondary-bg': "var(--vscode-button-secondaryBackground)",
'button-secondary-hoverBg': "var(--vscode-button-secondaryHoverBackground)",
"sidebar-bg": "var(--vscode-sideBar-background)",
"editor-bg": "var(--vscode-editor-background)",
"editor-fg": "var(--vscode-editor-foreground)",
"input-bg": "var(--vscode-input-background)",
"input-fg": "var(--vscode-input-foreground)",
"input-border": "var(--vscode-input-border)",
"button-fg": "var(--vscode-button-foreground)",
"button-bg": "var(--vscode-button-background)",
"button-hoverBg": "var(--vscode-button-hoverBackground)",
"button-secondary-fg": "var(--vscode-button-secondaryForeground)",
"button-secondary-bg": "var(--vscode-button-secondaryBackground)",
"button-secondary-hoverBg":
"var(--vscode-button-secondaryHoverBackground)",
},
},
},