split apply buttons

This commit is contained in:
Andrew Pareles 2025-02-21 16:19:01 -08:00
parent 06ce9e1017
commit ce14986d2f
5 changed files with 91 additions and 79 deletions

View file

@ -0,0 +1,84 @@
import { useState, useEffect, useCallback } from 'react'
import { useAccessor } from '../util/services.js'
enum CopyButtonText {
Idle = 'Copy',
Copied = 'Copied!',
Error = 'Could not copy',
}
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CopyButton = ({ codeStr }: { codeStr: string }) => {
const accessor = useAccessor()
const metricsService = accessor.get('IMetricsService')
const clipboardService = accessor.get('IClipboardService')
const [copyButtonText, setCopyButtonText] = useState(CopyButtonText.Idle)
useEffect(() => {
if (copyButtonText === CopyButtonText.Idle) return
setTimeout(() => {
setCopyButtonText(CopyButtonText.Idle)
}, COPY_FEEDBACK_TIMEOUT)
}, [copyButtonText])
const onCopy = useCallback(() => {
clipboardService.writeText(codeStr)
.then(() => { setCopyButtonText(CopyButtonText.Copied) })
.catch(() => { setCopyButtonText(CopyButtonText.Error) })
metricsService.capture('Copy Code', { length: codeStr.length }) // capture the length only
}, [metricsService, clipboardService, codeStr])
const isSingleLine = !codeStr.includes('\n')
return <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onCopy}
>
{copyButtonText}
</button>
}
const ApplyButton = ({ codeStr }: { codeStr: string }) => {
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const metricsService = accessor.get('IMetricsService')
const onApply = useCallback(() => {
editCodeService.startApplying({
from: 'ClickApply',
type: 'searchReplace',
applyStr: codeStr,
})
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
}, [metricsService, editCodeService, codeStr])
const isSingleLine = !codeStr.includes('\n')
return <button
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onApply}
>
Apply
</button>
}
export const ApplyBlockHoverButtons = ({ codeStr }: { codeStr: string }) => {
return <>
<CopyButton codeStr={codeStr} />
<ApplyButton codeStr={codeStr} />
</>
}

View file

@ -3,22 +3,12 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import React, { JSX, useCallback, useEffect, useState } from 'react'
import React, { JSX } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
import { useAccessor, useChatThreadsState, useChatThreadsStreamState } from '../util/services.js'
import { ChatMessageLocation, } from '../../../aiRegexService.js'
import { nameToVscodeLanguage } from '../../../helpers/detectLanguage.js'
enum CopyButtonState {
Copy = 'Copy',
Copied = 'Copied!',
Error = 'Could not copy',
}
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string }
@ -29,60 +19,6 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) =>
const ApplyButtonsOnHover = ({ applyStr }: { applyStr: string }) => {
const accessor = useAccessor()
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
const editCodeService = accessor.get('IEditCodeService')
const clipboardService = accessor.get('IClipboardService')
const metricsService = accessor.get('IMetricsService')
useEffect(() => {
if (copyButtonState !== CopyButtonState.Copy) {
setTimeout(() => {
setCopyButtonState(CopyButtonState.Copy)
}, COPY_FEEDBACK_TIMEOUT)
}
}, [copyButtonState])
const onCopy = useCallback(() => {
clipboardService.writeText(applyStr)
.then(() => { setCopyButtonState(CopyButtonState.Copied) })
.catch(() => { setCopyButtonState(CopyButtonState.Error) })
metricsService.capture('Copy Code', { length: applyStr.length }) // capture the length only
}, [metricsService, clipboardService, applyStr])
const onApply = useCallback(() => {
editCodeService.startApplying({
from: 'ClickApply',
type: 'searchReplace',
applyStr,
})
metricsService.capture('Apply Code', { length: applyStr.length }) // capture the length only
}, [metricsService, editCodeService, applyStr])
const isSingleLine = !applyStr.includes('\n')
return <>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onCopy}
>
{copyButtonState}
</button>
<button
// btn btn-secondary btn-sm border text-sm border-vscode-input-border rounded
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-1 text-void-fg-1 hover:brightness-110 border border-vscode-input-border rounded`}
onClick={onApply}
>
Apply
</button>
</>
}
export const CodeSpan = ({ children, className }: { children: React.ReactNode, className?: string }) => {
return <code className={`
bg-void-bg-1
@ -108,19 +44,11 @@ const RenderToken = ({ token, nested = false, noSpace = false, chatMessageLocati
}
if (t.type === "code") {
const isCodeblockClosed = t.raw?.startsWith('```') && t.raw?.endsWith('```');
// this should never be
const applyBoxId = chatMessageLocation ? getApplyBoxId({
threadId: chatMessageLocation.threadId,
messageIdx: chatMessageLocation.messageIdx,
tokenIdx: tokenIdx,
}) : null
return <BlockCode
initValue={t.text}
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
buttonsOnHover={applyBoxId && <ApplyButtonsOnHover applyStr={t.text} />}
buttonsOnHover={<ApplyBlockHoverButtons codeStr={t.text} />}
/>
}

View file

@ -703,7 +703,7 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM
className={`
relative
${mode === 'edit' ? 'px-2 w-full max-w-full'
: role === 'user' ? `px-2 self-end w-fit max-w-full whitespace-pre-wrap` // user words should be pre
: role === 'user' ? `my-0.5 px-2 self-end w-fit max-w-full whitespace-pre-wrap` // user words should be pre
: role === 'assistant' ? `px-2 self-start w-full max-w-full` : ''
}
`}

View file

@ -545,7 +545,7 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
return {
title: providerName === 'ollama' ? 'Endpoint' :
providerName === 'vLLM' ? 'Endpoint' :
providerName === 'openAICompatible' ? 'baseURL' :// (do not include /chat/completions)
providerName === 'openAICompatible' ? 'baseURL' : // (do not include /chat/completions)
'(never)',
placeholder: providerName === 'ollama' ? defaultProviderSettings.ollama.endpoint

View file

@ -10,12 +10,12 @@ import { InternalToolInfo } from '../../common/toolsService.js';
import { addSystemMessageAndToolSupport } from './preprocessLLMMessages.js';
import { developerInfoOfModelName, developerInfoOfProviderName } from '../../common/voidSettingsTypes.js';
import { isAToolName } from './postprocessToolCalls.js';
// import { parseMaxTokensStr } from './util.js';
// developer command - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
// prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
// npm i @openrouter/ai-sdk-provider ai ollama-ai-provider
export const toOpenAITool = (toolInfo: InternalToolInfo) => {
const { name, description, params, required } = toolInfo
@ -201,7 +201,7 @@ export const sendOpenAIChat: _InternalSendLLMChatMessageFnType = ({ messages: me
// message
let newText = ''
newText += chunk.choices[0]?.delta?.content ?? ''
console.log('!!!!', chunk.choices[0]?.delta)
console.log('!!!!', JSON.stringify(chunk, null, 2))
fullText += newText;
onText({ newText, fullText });