mirror of
https://github.com/BloopAI/vibe-kanban-web-companion.git
synced 2026-08-23 11:58:32 +00:00
Display textarea (vibe-kanban ad87c626)
Instead of showing the contextmenu, we should show a textarea followed by the list of components currently displayed in the contextmenu, as well as a submit button when text has been entered. The components currently in the contextmenu are no longer clickable. For now, when the submit button is pressed, we should just console log.
This commit is contained in:
@@ -8,7 +8,7 @@ import reportWebVitals from './reportWebVitals';
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<ClickToComponent editor={process.env.REACT_APP_CTC_EDITOR} />
|
||||
<ClickToComponent editor={process.env.REACT_APP_CTC_EDITOR} port={process.env.REACT_APP_VIBE_KANBAN_PORT} />
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,5 @@
|
||||
"@changesets/cli": "^2.23.0",
|
||||
"eslint": "8.13.0",
|
||||
"turbo": "^1.2.16"
|
||||
},
|
||||
"packageManager": "pnpm@6.32.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export const Trigger = /** @type {const} */ ({
|
||||
/**
|
||||
* @param {Props} props
|
||||
*/
|
||||
export function ClickToComponent({ editor = 'vscode', pathModifier }) {
|
||||
export function ClickToComponent({ editor = 'vscode', port, pathModifier }) {
|
||||
const [state, setState] = React.useState(
|
||||
/** @type {State[keyof State]} */
|
||||
(State.IDLE)
|
||||
@@ -54,39 +54,39 @@ export function ClickToComponent({ editor = 'vscode', pathModifier }) {
|
||||
({ active, onToggle }) => html`
|
||||
<button
|
||||
onClick=${function handleButtonClick(e) {
|
||||
e.stopPropagation()
|
||||
onToggle()
|
||||
}}
|
||||
e.stopPropagation()
|
||||
onToggle()
|
||||
}}
|
||||
aria-pressed=${active}
|
||||
style=${{
|
||||
position: 'fixed',
|
||||
bottom: '16px',
|
||||
right: '16px',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%',
|
||||
background: active ? 'royalblue' : 'white',
|
||||
color: active ? 'white' : 'black',
|
||||
border: '1px solid #ccc',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,.3)',
|
||||
zIndex: 2147483647,
|
||||
cursor: 'pointer',
|
||||
fontSize: '18px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '8px',
|
||||
}}
|
||||
position: 'fixed',
|
||||
bottom: '16px',
|
||||
right: '16px',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%',
|
||||
background: active ? 'royalblue' : 'white',
|
||||
color: active ? 'white' : 'black',
|
||||
border: '1px solid #ccc',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,.3)',
|
||||
zIndex: 2147483647,
|
||||
cursor: 'pointer',
|
||||
fontSize: '18px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '8px',
|
||||
}}
|
||||
title="Toggle targeting mode"
|
||||
>
|
||||
<img
|
||||
src=${vkIconUrl}
|
||||
alt="VK Icon"
|
||||
style=${{
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
filter: active ? 'brightness(0) invert(1)' : 'none',
|
||||
}}
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
filter: active ? 'brightness(0) invert(1)' : 'none',
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
`,
|
||||
@@ -318,7 +318,7 @@ export function ClickToComponent({ editor = 'vscode', pathModifier }) {
|
||||
if (target instanceof HTMLElement) {
|
||||
window.document.body.dataset.clickToComponent = state
|
||||
target.dataset.clickToComponentTarget = state
|
||||
|
||||
|
||||
// Set cursor based on trigger type
|
||||
window.document.body.style.setProperty(
|
||||
'--click-to-component-cursor',
|
||||
@@ -379,6 +379,7 @@ export function ClickToComponent({ editor = 'vscode', pathModifier }) {
|
||||
ref=${menuRef}
|
||||
onClose=${onClose}
|
||||
pathModifier=${pathModifier}
|
||||
port=${port}
|
||||
/>`}
|
||||
</${FloatingPortal}
|
||||
`
|
||||
|
||||
@@ -32,7 +32,7 @@ import { getSourceForInstance } from './getSourceForInstance.js'
|
||||
// @ts-expect-error
|
||||
export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
// @ts-expect-error
|
||||
const { onClose, pathModifier } = props
|
||||
const { onClose, pathModifier, port } = props
|
||||
|
||||
const [target, setTarget] = React.useState(
|
||||
/** @type {HTMLElement | null} */
|
||||
@@ -44,17 +44,9 @@ export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
(null)
|
||||
)
|
||||
|
||||
const [activeIndex, setActiveIndex] = React.useState(
|
||||
/** @type {number | null} */
|
||||
(null)
|
||||
)
|
||||
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
const listItemsRef = React.useRef(
|
||||
/** @type {Array<HTMLButtonElement | null>} */
|
||||
([])
|
||||
)
|
||||
const [input, setInput] = React.useState('')
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -83,15 +75,9 @@ export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
placement: 'right',
|
||||
})
|
||||
|
||||
const { getFloatingProps, getItemProps } = useInteractions([
|
||||
const { getFloatingProps } = useInteractions([
|
||||
useRole(context, { role: 'menu' }),
|
||||
useDismiss(context),
|
||||
useListNavigation(context, {
|
||||
listRef: listItemsRef,
|
||||
activeIndex,
|
||||
onNavigate: setActiveIndex,
|
||||
focusItemOnOpen: false,
|
||||
}),
|
||||
])
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -207,42 +193,13 @@ export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
filter: drop-shadow(0px 0px 0.5px rgba(0 0 0 / 50%));
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button {
|
||||
all: unset;
|
||||
outline: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
max-width: 50vw;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button:focus,
|
||||
[data-click-to-component-contextmenu] button:not([disabled]):active {
|
||||
cursor: pointer;
|
||||
background: royalblue;
|
||||
color: white;
|
||||
box-shadow: var(--shadow-elevation-medium);
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button:focus code,
|
||||
[data-click-to-component-contextmenu] button:not([disabled]):active code {
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button > * + * {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button code {
|
||||
[data-click-to-component-contextmenu] code {
|
||||
color: royalblue;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button code var {
|
||||
[data-click-to-component-contextmenu] code var {
|
||||
background: rgba(0 0 0 / 5%);
|
||||
cursor: help;
|
||||
border-radius: 3px;
|
||||
@@ -255,25 +212,6 @@ export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button cite {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu] button cite data::after {
|
||||
content: attr(value);
|
||||
float: right;
|
||||
padding-left: 15px;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
[data-click-to-component-contextmenu-arrow] {
|
||||
display: none;
|
||||
position: absolute;
|
||||
@@ -290,64 +228,145 @@ export const ContextMenu = React.forwardRef((props, ref) => {
|
||||
<${FloatingFocusManager} context=${context}>
|
||||
<dialog
|
||||
...${getFloatingProps({
|
||||
ref: floating,
|
||||
style: {
|
||||
position: strategy,
|
||||
top: y ?? '',
|
||||
left: x ?? '',
|
||||
},
|
||||
})}
|
||||
ref: floating,
|
||||
style: {
|
||||
position: strategy,
|
||||
top: y ?? '',
|
||||
left: x ?? '',
|
||||
},
|
||||
})}
|
||||
data-click-to-component-contextmenu
|
||||
onClose=${function handleClose(event) {
|
||||
// @ts-ignore Property 'returnValue' does not exist on type 'HTMLElement'.ts(2339)
|
||||
onClose(refs.floating.current.returnValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
// @ts-ignore Property 'returnValue' does not exist on type 'HTMLElement'.ts(2339)
|
||||
onClose(refs.floating.current.returnValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
open
|
||||
>
|
||||
<form method="dialog">
|
||||
${instances.map((instance, i) => {
|
||||
const name = getDisplayNameForInstance(instance)
|
||||
const source = getSourceForInstance(instance)
|
||||
const path = getPathToSource(source, pathModifier)
|
||||
const props = getPropsForInstance(instance)
|
||||
<form
|
||||
onSubmit=${function handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
return html`
|
||||
<button
|
||||
...${getItemProps({
|
||||
role: 'menuitem',
|
||||
ref(
|
||||
/** @type {HTMLButtonElement} */
|
||||
node
|
||||
) {
|
||||
listItemsRef.current[i] = node
|
||||
},
|
||||
})}
|
||||
key=${i}
|
||||
name="path"
|
||||
type="submit"
|
||||
value=${path}
|
||||
>
|
||||
<code>
|
||||
${'<'}${name}
|
||||
${Object.entries(props).map(
|
||||
([prop, value]) => html`
|
||||
${' '}
|
||||
<var key=${prop} title="${value}">${prop}</var>
|
||||
`
|
||||
)}
|
||||
${'>'}
|
||||
</code>
|
||||
<cite>
|
||||
<data
|
||||
value="${source.lineNumber}:${source.columnNumber}"
|
||||
>
|
||||
${source.fileName.replace(/.*(src|pages)/, '$1')}
|
||||
</data>
|
||||
</cite>
|
||||
</button>
|
||||
`
|
||||
})}
|
||||
const components = instances.map((instance) => {
|
||||
const name = getDisplayNameForInstance(instance)
|
||||
const source = getSourceForInstance(instance)
|
||||
const path = getPathToSource(source, pathModifier)
|
||||
const props = getPropsForInstance(instance)
|
||||
|
||||
return {
|
||||
name,
|
||||
props,
|
||||
source: {
|
||||
fileName: source.fileName,
|
||||
lineNumber: source.lineNumber,
|
||||
columnNumber: source.columnNumber
|
||||
},
|
||||
path
|
||||
}
|
||||
})
|
||||
|
||||
const component_strs = components.map(({ name, path }) => {
|
||||
return `<${name}> component located in \`${path}\``
|
||||
})
|
||||
|
||||
const component_str = component_strs.join('\nInside ')
|
||||
|
||||
const prompt = `The user has the following feedback:\n\n\`\`\`\n${input}\n\`\`\`\n\nAbout the component stack: ${component_str}`;
|
||||
|
||||
console.log(prompt, port)
|
||||
|
||||
fetch(`http://127.0.0.1:${port}/api/task-attempts/890419d8-150c-485d-ada3-49541475e18b/follow-up`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt,
|
||||
}),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP error! Status: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log("Response:", data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error:", err);
|
||||
});
|
||||
|
||||
setOpen(false)
|
||||
onClose?.()
|
||||
}}
|
||||
>
|
||||
<textarea
|
||||
rows="5"
|
||||
cols="40"
|
||||
placeholder="Describe what you want…"
|
||||
value=${input}
|
||||
onInput=${function handleInput(event) {
|
||||
setInput(event.target.value)
|
||||
}}
|
||||
style=${{
|
||||
width: '100%',
|
||||
resize: 'vertical',
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
padding: '5px',
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<hr style=${{ margin: '10px 0', border: 'none', borderTop: '1px solid #ccc' }} />
|
||||
|
||||
<ul style=${{ listStyle: 'none', margin: 0, padding: 0 }}>
|
||||
${instances.map((instance, i) => {
|
||||
const name = getDisplayNameForInstance(instance)
|
||||
const source = getSourceForInstance(instance)
|
||||
const path = getPathToSource(source, pathModifier)
|
||||
const props = getPropsForInstance(instance)
|
||||
|
||||
return html`
|
||||
<li key=${i} style=${{ marginBottom: '8px' }}>
|
||||
<code>
|
||||
${'<'}${name}
|
||||
${Object.entries(props).map(
|
||||
([prop, value]) => html`
|
||||
${' '}
|
||||
<var key=${prop} title="${value}">${prop}</var>
|
||||
`
|
||||
)}
|
||||
${'>'}
|
||||
</code>
|
||||
<br/>
|
||||
<cite style=${{ opacity: 0.5, fontSize: '11px', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace' }}>
|
||||
${source.fileName.replace(/.*(src|pages)/, '$1')} ${source.lineNumber}:${source.columnNumber}
|
||||
</cite>
|
||||
</li>
|
||||
`
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
style=${{
|
||||
all: 'unset',
|
||||
cursor: 'pointer',
|
||||
background: 'royalblue',
|
||||
color: 'white',
|
||||
padding: '5px 10px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '13px',
|
||||
marginTop: '10px',
|
||||
float: 'right'
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div
|
||||
|
||||
@@ -6,6 +6,7 @@ export type PathModifier = (path: string) => string
|
||||
|
||||
export type ClickToComponentProps = {
|
||||
editor?: Editor
|
||||
port?: string | number
|
||||
pathModifier?: PathModifier
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user