mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
split apply buttons
This commit is contained in:
parent
06ce9e1017
commit
ce14986d2f
5 changed files with 91 additions and 79 deletions
|
|
@ -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} />
|
||||
</>
|
||||
}
|
||||
|
|
@ -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} />}
|
||||
/>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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` : ''
|
||||
}
|
||||
`}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
Loading…
Reference in a new issue