mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
Merge remote-tracking branch 'origin/model-selection' into tools-in-plaintext
This commit is contained in:
commit
4236d920d9
33 changed files with 2872 additions and 586 deletions
21
package-lock.json
generated
21
package-lock.json
generated
|
|
@ -62,6 +62,7 @@
|
|||
"posthog-node": "^4.8.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"v8-inspect-profiler": "^0.1.1",
|
||||
"vscode-html-languageservice": "^5.3.1",
|
||||
|
|
@ -6621,6 +6622,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
|
||||
|
|
@ -18000,6 +18007,20 @@
|
|||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-tooltip": {
|
||||
"version": "5.28.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz",
|
||||
"integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.1",
|
||||
"classnames": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@
|
|||
"posthog-node": "^4.8.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"tas-client-umd": "0.2.0",
|
||||
"v8-inspect-profiler": "^0.1.1",
|
||||
"vscode-html-languageservice": "^5.3.1",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// This bootstrap-fork module handles the initialization of a forked process in VS Code.
|
||||
// It sets up logging, exception handling, and loads the ESM module system.
|
||||
|
||||
import * as performance from './vs/base/common/performance.js';
|
||||
import { removeGlobalNodeJsModuleLookupPaths, devInjectNodeModuleLookupPath } from './bootstrap-node.js';
|
||||
import { bootstrapESM } from './bootstrap-esm.js';
|
||||
|
|
|
|||
|
|
@ -121,6 +121,93 @@ export class SmartSelectController implements IEditorContribution {
|
|||
}
|
||||
this._state = this._state.map(state => state.mov(forward));
|
||||
const newSelections = this._state.map(state => Selection.fromPositions(state.ranges[state.index].getStartPosition(), state.ranges[state.index].getEndPosition()));
|
||||
|
||||
// Void changed this to skip over added whitespace when using smartSelect
|
||||
// // Store the original selections for comparison
|
||||
// const originalSelections = selections;
|
||||
|
||||
// // Keep skipping while we're only adding/removing whitespace
|
||||
// let keepSkipping = true;
|
||||
// let skipCount = 0;
|
||||
// const MAX_SKIPS = 5; // Avoid infinite loops by setting a reasonable limit
|
||||
|
||||
// while (keepSkipping && skipCount < MAX_SKIPS) {
|
||||
// keepSkipping = false; // Reset for each iteration
|
||||
|
||||
// // Check if all selections only added/removed whitespace
|
||||
// if (originalSelections.length === newSelections.length) {
|
||||
// for (let i = 0; i < originalSelections.length; i++) {
|
||||
// const oldSel = originalSelections[i];
|
||||
// const newSel = newSelections[i];
|
||||
|
||||
// if (forward) { // For expanding (^+Shift+Right)
|
||||
// // Skip if only whitespace was added
|
||||
// const oldText = model.getValueInRange(oldSel).trim();
|
||||
// const newText = model.getValueInRange(newSel).trim();
|
||||
// const onlyWhitespaceAdded = oldText === newText && oldText.length > 0;
|
||||
|
||||
// if (onlyWhitespaceAdded) {
|
||||
// console.log(`SMART SELECT - SKIPPING (EXPAND) [${skipCount + 1}]:`, {
|
||||
// reason: 'only whitespace added',
|
||||
// oldText: model.getValueInRange(oldSel),
|
||||
// newText: model.getValueInRange(newSel)
|
||||
// });
|
||||
// keepSkipping = true;
|
||||
// break;
|
||||
// }
|
||||
// } else { // For shrinking (^+Shift+Left)
|
||||
// // Skip if only whitespace was removed
|
||||
// const oldText = model.getValueInRange(oldSel).trim();
|
||||
// const newText = model.getValueInRange(newSel).trim();
|
||||
// const onlyWhitespaceRemoved = oldText === newText && newText.length > 0;
|
||||
|
||||
// if (onlyWhitespaceRemoved) {
|
||||
// console.log(`SMART SELECT - SKIPPING (SHRINK) [${skipCount + 1}]:`, {
|
||||
// reason: 'only whitespace removed',
|
||||
// oldText: model.getValueInRange(oldSel),
|
||||
// newText: model.getValueInRange(newSel)
|
||||
// });
|
||||
// keepSkipping = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // If we need to skip, move one more time
|
||||
// if (keepSkipping) {
|
||||
// skipCount++;
|
||||
|
||||
// // Try to move to the next range
|
||||
// const prevState = this._state;
|
||||
// this._state = this._state.map(state => state.mov(forward));
|
||||
|
||||
// // Check if we've reached the end of available ranges
|
||||
// const stateUnchanged = this._state.every((state, idx) =>
|
||||
// state.index === prevState[idx].index
|
||||
// );
|
||||
|
||||
// if (stateUnchanged) {
|
||||
// // We can't move any further, so stop skipping
|
||||
// keepSkipping = false;
|
||||
// } else {
|
||||
// // Update selections for the next iteration
|
||||
// newSelections = this._state.map(state => Selection.fromPositions(
|
||||
// state.ranges[state.index].getStartPosition(),
|
||||
// state.ranges[state.index].getEndPosition()
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Print AFTER selection (before actually setting it)
|
||||
// console.log('SMART SELECT - AFTER:', newSelections.map(s => {
|
||||
// return {
|
||||
// range: `(${s.startLineNumber},${s.startColumn}) -> (${s.endLineNumber},${s.endColumn})`,
|
||||
// text: model.getValueInRange(s)
|
||||
// };
|
||||
// }));
|
||||
|
||||
this._ignoreSelection = true;
|
||||
try {
|
||||
this._editor.setSelections(newSelections);
|
||||
|
|
|
|||
|
|
@ -612,7 +612,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
|
|||
'description': localize('workbench.hover.delay', "Controls the delay in milliseconds after which the hover is shown for workbench items (ex. some extension provided tree view items). Already visible items may require a refresh before reflecting this setting change."),
|
||||
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
|
||||
// On Mac, the delay is 1500.
|
||||
'default': isMacintosh ? 1500 : 500,
|
||||
'default': 300, // Void changed this from isMacintosh ? 1500 : 500,
|
||||
'minimum': 0
|
||||
},
|
||||
'workbench.reduceMotion': {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,21 @@ registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true);
|
|||
|
||||
const numLinesOfStr = (str: string) => str.split('\n').length
|
||||
|
||||
|
||||
export const getLengthOfTextPx = ({ tabWidth, spaceWidth, content }: { tabWidth: number, spaceWidth: number, content: string }) => {
|
||||
let lengthOfTextPx = 0;
|
||||
for (const char of content) {
|
||||
if (char === '\t') {
|
||||
lengthOfTextPx += tabWidth
|
||||
} else {
|
||||
lengthOfTextPx += spaceWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return lengthOfTextPx
|
||||
}
|
||||
|
||||
|
||||
const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number => {
|
||||
|
||||
const model = editor.getModel();
|
||||
|
|
@ -94,16 +109,14 @@ const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number
|
|||
const spaceWidth = editor.getOption(EditorOption.fontInfo).spaceWidth;
|
||||
const tabWidth = numSpacesInTab * spaceWidth;
|
||||
|
||||
let paddingLeft = 0;
|
||||
for (const char of leadingWhitespace) {
|
||||
if (char === '\t') {
|
||||
paddingLeft += tabWidth
|
||||
} else if (char === ' ') {
|
||||
paddingLeft += spaceWidth;
|
||||
}
|
||||
}
|
||||
const leftWhitespacePx = getLengthOfTextPx({
|
||||
tabWidth,
|
||||
spaceWidth,
|
||||
content: leadingWhitespace
|
||||
});
|
||||
|
||||
return paddingLeft;
|
||||
|
||||
return leftWhitespacePx;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1584,7 +1597,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
|
||||
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
|
||||
|
||||
const N_RETRIES = 5
|
||||
const N_RETRIES = 2
|
||||
|
||||
// allowed to throw errors - this is called inside a promise that handles everything
|
||||
const runSearchReplace = async () => {
|
||||
|
|
|
|||
|
|
@ -76,93 +76,107 @@
|
|||
opacity: 80%;
|
||||
}
|
||||
|
||||
/* styles for all containers used by void */
|
||||
.void-scope {
|
||||
--scrollbar-vertical-width: 8px;
|
||||
--scrollbar-horizontal-height: 6px;
|
||||
}
|
||||
|
||||
/* Target both void-scope and all its descendants with scrollbars */
|
||||
.void-scope,
|
||||
.void-scope * {
|
||||
scrollbar-width: thin !important;
|
||||
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
|
||||
}
|
||||
|
||||
.void-scope::-webkit-scrollbar,
|
||||
.void-scope *::-webkit-scrollbar {
|
||||
width: var(--scrollbar-vertical-width) !important;
|
||||
height: var(--scrollbar-horizontal-height) !important;
|
||||
background-color: var(--void-bg-3) !important;
|
||||
}
|
||||
|
||||
.void-scope::-webkit-scrollbar-thumb,
|
||||
.void-scope *::-webkit-scrollbar-thumb {
|
||||
background-color: var(--void-bg-1) !important;
|
||||
border-radius: 4px !important;
|
||||
border: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.void-scope::-webkit-scrollbar-thumb:hover,
|
||||
.void-scope *::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--void-bg-1) !important;
|
||||
filter: brightness(1.1) !important;
|
||||
}
|
||||
|
||||
.void-scope::-webkit-scrollbar-thumb:active,
|
||||
.void-scope *::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--void-bg-1) !important;
|
||||
filter: brightness(1.2) !important;
|
||||
}
|
||||
|
||||
.void-scope::-webkit-scrollbar-track,
|
||||
.void-scope *::-webkit-scrollbar-track {
|
||||
background-color: var(--void-bg-3) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.void-scope::-webkit-scrollbar-corner,
|
||||
.void-scope *::-webkit-scrollbar-corner {
|
||||
background-color: var(--void-bg-3) !important;
|
||||
}
|
||||
|
||||
/* Add void-scrollable-element styles to match */
|
||||
.void-scrollable-element {
|
||||
background-color: var(--vscode-editor-background);
|
||||
--scrollbar-vertical-width: 14px;
|
||||
--scrollbar-horizontal-height: 6px;
|
||||
overflow: auto; /* Ensure scrollbars are shown when needed */
|
||||
}
|
||||
|
||||
.void-scrollable-element,
|
||||
.void-scrollable-element * {
|
||||
scrollbar-width: thin !important; /* For Firefox */
|
||||
scrollbar-color: var(--void-bg-1) var(--void-bg-3) !important; /* For Firefox */
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar,
|
||||
.void-scrollable-element *::-webkit-scrollbar {
|
||||
width: 14px !important;
|
||||
height: 4px !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-track,
|
||||
.void-scrollable-element *::-webkit-scrollbar-track {
|
||||
background: transparent !important;
|
||||
width: var(--scrollbar-vertical-width) !important;
|
||||
height: var(--scrollbar-horizontal-height) !important;
|
||||
background-color: var(--void-bg-3) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element *::-webkit-scrollbar-thumb {
|
||||
background-color: transparent !important;
|
||||
border-radius: 0px !important;
|
||||
background-color: var(--void-bg-1) !important;
|
||||
border-radius: 4px !important;
|
||||
border: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-thumb:hover,
|
||||
.void-scrollable-element *::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--vscode-scrollbarSlider-hoverBackground) !important;
|
||||
background-color: var(--void-bg-1) !important;
|
||||
filter: brightness(1.1) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-thumb:active,
|
||||
.void-scrollable-element *::-webkit-scrollbar-thumb:active {
|
||||
background-color: var(--vscode-scrollbarSlider-activeBackground) !important;
|
||||
background-color: var(--void-bg-1) !important;
|
||||
filter: brightness(1.2) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-track,
|
||||
.void-scrollable-element *::-webkit-scrollbar-track {
|
||||
background-color: var(--void-bg-3) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element::-webkit-scrollbar-corner,
|
||||
.void-scrollable-element *::-webkit-scrollbar-corner {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-0::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-0 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 0%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-1::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-1 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 10%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-2::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-2 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 20%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-3::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-3 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 30%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-4::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-4 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 40%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-5::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-5 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 50%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-6::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-6 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 60%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-7::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-7 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 70%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-8::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-8 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 80%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-9::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-9 *::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--vscode-scrollbarSlider-background) 90%, transparent) !important;
|
||||
}
|
||||
|
||||
.void-scrollable-element.show-scrollbar-10::-webkit-scrollbar-thumb,
|
||||
.void-scrollable-element.show-scrollbar-10 *::-webkit-scrollbar-thumb {
|
||||
background-color: var(--vscode-scrollbarSlider-background) !important;
|
||||
background-color: var(--void-bg-3) !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function saveStylesFile() {
|
|||
} catch (err) {
|
||||
console.error('[scope-tailwind] Error saving styles.css:', err);
|
||||
}
|
||||
}, 4000);
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { URI } from '../../../../../../../base/common/uri.js'
|
|||
import { FileSymlink, LucideIcon, RotateCw, Terminal } from 'lucide-react'
|
||||
import { Check, X, Square, Copy, Play, } from 'lucide-react'
|
||||
import { getBasename, ListableToolItem, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { PlacesType, VariantType } from 'react-tooltip'
|
||||
|
||||
enum CopyButtonText {
|
||||
Idle = 'Copy',
|
||||
|
|
@ -20,30 +21,28 @@ enum CopyButtonText {
|
|||
|
||||
|
||||
type IconButtonProps = {
|
||||
onClick: () => void;
|
||||
Icon: LucideIcon
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const IconShell1 = ({ onClick, Icon, disabled, className }: IconButtonProps) => (
|
||||
export const IconShell1 = ({ onClick, Icon, disabled, className, ...props }: IconButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>) => (
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClick?.();
|
||||
onClick?.(e);
|
||||
}}
|
||||
// border border-void-border-1 rounded
|
||||
className={`
|
||||
size-[22px]
|
||||
p-[4px]
|
||||
size-[18px]
|
||||
p-[2px]
|
||||
flex items-center justify-center
|
||||
text-sm bg-void-bg-3 text-void-fg-1
|
||||
text-sm bg-void-bg-3 text-void-fg-3
|
||||
hover:brightness-110
|
||||
border border-void-border-1 rounded
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
>
|
||||
<Icon />
|
||||
</button>
|
||||
|
|
@ -94,13 +93,14 @@ export const CopyButton = ({ codeStr }: { codeStr: string }) => {
|
|||
return <IconShell1
|
||||
Icon={copyButtonText === CopyButtonText.Copied ? Check : copyButtonText === CopyButtonText.Error ? X : Copy}
|
||||
onClick={onCopy}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Copy' })}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => {
|
||||
export const JumpToFileButton = ({ uri, ...props }: { uri: URI | 'current' } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
|
|
@ -110,6 +110,8 @@ export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => {
|
|||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', uri, { preview: true })
|
||||
}}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Go to file' })}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return jumpToFileButton
|
||||
|
|
@ -122,7 +124,6 @@ export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => {
|
|||
<IconShell1
|
||||
Icon={Terminal}
|
||||
onClick={onClick}
|
||||
className="text-void-fg-1"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -163,10 +164,11 @@ export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, u
|
|||
rerender(c => c + 1)
|
||||
console.log('rerendering....')
|
||||
}
|
||||
}, [applyBoxId, applyBoxId, uri]))
|
||||
}, [applyBoxId, uri]))
|
||||
|
||||
const currStreamState = getStreamState()
|
||||
|
||||
|
||||
return {
|
||||
getStreamState,
|
||||
isDisabled,
|
||||
|
|
@ -175,22 +177,61 @@ export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, u
|
|||
}
|
||||
|
||||
|
||||
export const StatusIndicatorHTML = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => {
|
||||
type IndicatorColor = 'green' | 'orange' | 'dark' | 'yellow' | null
|
||||
export const StatusIndicator = ({ indicatorColor, title, className, ...props }: { indicatorColor: IndicatorColor, title?: React.ReactNode, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||
return (
|
||||
<div className={`flex flex-row text-void-fg-3 text-xs items-center gap-1.5 ${className}`} {...props}>
|
||||
{title && <span className='opacity-80'>{title}</span>}
|
||||
<div
|
||||
className={` size-1.5 rounded-full border
|
||||
${indicatorColor === 'dark' ? 'bg-void-bg-3 border-void-border-1' :
|
||||
indicatorColor === 'orange' ? 'bg-orange-500 border-orange-500 shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]' :
|
||||
indicatorColor === 'green' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
|
||||
indicatorColor === 'yellow' ? 'bg-yellow-500 border-yellow-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
|
||||
'bg-void-border-1 border-void-border-1'
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const tooltipPropsForApplyBlock = ({ tooltipName, color = undefined, position = 'top', offset = undefined }: { tooltipName: string, color?: IndicatorColor, position?: PlacesType, offset?: number }) => ({
|
||||
'data-tooltip-id': color === 'orange' ? `void-tooltip-orange` : color === 'green' ? 'void-tooltip-green' : 'void-tooltip',
|
||||
'data-tooltip-place': position as PlacesType,
|
||||
'data-tooltip-content': `${tooltipName}`,
|
||||
'data-tooltip-offset': offset,
|
||||
})
|
||||
|
||||
|
||||
export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
|
||||
return <div className='flex flex-row items-center min-h-4 max-h-4 min-w-4 max-w-4'>
|
||||
<div
|
||||
className={` size-1.5 rounded-full border
|
||||
${currStreamState === 'idle-no-changes' ? 'bg-void-bg-3 border-void-border-1' :
|
||||
currStreamState === 'streaming' ? 'bg-orange-500 border-orange-500 shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]' :
|
||||
currStreamState === 'idle-has-changes' ? 'bg-green-500 border-green-500 shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]' :
|
||||
'bg-void-border-1 border-void-border-1'
|
||||
}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
const color = (
|
||||
currStreamState === 'idle-no-changes' ? 'dark' :
|
||||
currStreamState === 'streaming' ? 'orange' :
|
||||
currStreamState === 'idle-has-changes' ? 'green' :
|
||||
null
|
||||
)
|
||||
|
||||
const tooltipName = (
|
||||
currStreamState === 'idle-no-changes' ? 'Done' :
|
||||
currStreamState === 'streaming' ? 'Applying' :
|
||||
currStreamState === 'idle-has-changes' ? 'Done' : // also 'Done'? 'Applied' looked bad
|
||||
''
|
||||
)
|
||||
|
||||
const statusIndicatorHTML = <StatusIndicator
|
||||
key={currStreamState}
|
||||
className='mx-2'
|
||||
indicatorColor={color}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName, color, position: 'top', offset: 12 })}
|
||||
/>
|
||||
return statusIndicatorHTML
|
||||
}
|
||||
|
||||
|
||||
export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { codeStr: string, applyBoxId: string, reapplyIcon: boolean, uri: URI | 'current' }) => {
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
|
|
@ -254,11 +295,22 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { co
|
|||
|
||||
|
||||
if (currStreamState === 'streaming') {
|
||||
return <IconShell1 Icon={Square} onClick={onInterrupt} />
|
||||
return <IconShell1
|
||||
|
||||
Icon={Square}
|
||||
onClick={onInterrupt}
|
||||
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })}
|
||||
/>
|
||||
}
|
||||
|
||||
if (currStreamState === 'idle-no-changes') {
|
||||
return <IconShell1 Icon={reapplyIcon ? RotateCw : Play} onClick={onClickSubmit} />
|
||||
|
||||
return <IconShell1
|
||||
Icon={reapplyIcon ? RotateCw : Play}
|
||||
onClick={onClickSubmit}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: reapplyIcon ? 'Reapply' : 'Apply' })}
|
||||
/>
|
||||
}
|
||||
|
||||
if (currStreamState === 'idle-has-changes') {
|
||||
|
|
@ -270,19 +322,18 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { co
|
|||
<IconShell1
|
||||
Icon={X}
|
||||
onClick={onReject}
|
||||
className="text-red-600"
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Reject file' })}
|
||||
/>
|
||||
<IconShell1
|
||||
Icon={Check}
|
||||
onClick={onAccept}
|
||||
className="text-green-600"
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Accept file' })}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const BlockCodeApplyWrapper = ({
|
||||
children,
|
||||
initValue,
|
||||
|
|
@ -317,7 +368,7 @@ export const BlockCodeApplyWrapper = ({
|
|||
{/* header */}
|
||||
<div className=" select-none flex justify-between items-center py-1 px-2 border-b border-void-border-3 cursor-default">
|
||||
<div className="flex items-center">
|
||||
<StatusIndicatorHTML uri={uri} applyBoxId={applyBoxId} />
|
||||
<StatusIndicatorForApplyButton uri={uri} applyBoxId={applyBoxId} />
|
||||
<span className="text-[13px] font-light text-void-fg-3">
|
||||
{name}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { QuickEditPropsType } from '../../../quickEditActions.js';
|
|||
import { ButtonStop, ButtonSubmit, IconX, VoidChatArea } from '../sidebar-tsx/SidebarChat.js';
|
||||
import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
|
||||
import { useRefState } from '../util/helpers.js';
|
||||
import { useScrollbarStyles } from '../util/useScrollbarStyles.js';
|
||||
import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
|
||||
|
||||
export const QuickEditChat = ({
|
||||
|
|
@ -89,8 +88,6 @@ export const QuickEditChat = ({
|
|||
editCodeService.removeCtrlKZone({ diffareaid })
|
||||
}, [editCodeService, diffareaid])
|
||||
|
||||
useScrollbarStyles(sizerRef)
|
||||
|
||||
const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_K_ACTION_ID)?.getLabel()
|
||||
|
||||
const chatAreaRef = useRef<HTMLDivElement | null>(null)
|
||||
|
|
|
|||
|
|
@ -15,17 +15,18 @@ import { ErrorDisplay } from './ErrorDisplay.js';
|
|||
import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch } from '../util/inputs.js';
|
||||
import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js';
|
||||
import { SidebarThreadSelector } from './SidebarThreadSelector.js';
|
||||
import { useScrollbarStyles } from '../util/useScrollbarStyles.js';
|
||||
import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
|
||||
import { ChatMode, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
|
||||
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
|
||||
import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js';
|
||||
import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, Undo, Undo2, X } from 'lucide-react';
|
||||
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X } from 'lucide-react';
|
||||
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js';
|
||||
import { ToolCallParams } from '../../../../common/toolsServiceTypes.js';
|
||||
import { ApplyButtonsHTML, CopyButton, JumpToFileButton, JumpToTerminalButton, StatusIndicatorHTML, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { ToolCallParams, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js';
|
||||
import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { IsRunningType } from '../../../chatThreadService.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { PlacesType } from 'react-tooltip';
|
||||
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
||||
|
||||
|
||||
|
|
@ -351,7 +352,7 @@ export const VoidChatArea: React.FC<VoidChatAreaProps> = ({
|
|||
<div className='flex flex-col gap-y-1'>
|
||||
<ReasoningOptionSlider featureName={featureName} />
|
||||
|
||||
<div className='flex items-center flex-wrap gap-x-2 gap-y-1'>
|
||||
<div className='flex items-center flex-wrap gap-x-2 gap-y-1 text-nowrap flex-nowrap'>
|
||||
{featureName === 'Chat' && <ChatModeDropdown className='text-xs text-void-fg-3 bg-void-bg-1 border border-void-border-2 rounded py-0.5 px-1' />}
|
||||
<ModelDropdown featureName={featureName} className='text-xs text-void-fg-3 bg-void-bg-1 rounded' />
|
||||
</div>
|
||||
|
|
@ -392,6 +393,9 @@ export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Re
|
|||
${disabled ? 'bg-vscode-disabled-fg cursor-default' : 'bg-white cursor-pointer'}
|
||||
${className}
|
||||
`}
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-content={'Send'}
|
||||
// data-tooltip-place='left'
|
||||
{...props}
|
||||
>
|
||||
<IconArrowUp size={DEFAULT_BUTTON_SIZE} className="stroke-[2] p-[2px]" />
|
||||
|
|
@ -681,23 +685,26 @@ const ToolHeaderWrapper = ({
|
|||
return (<div className=''>
|
||||
<div className="w-full border border-void-border-3 rounded px-2 py-1 bg-void-bg-3 overflow-hidden ">
|
||||
{/* header */}
|
||||
<div
|
||||
className={`select-none flex items-center min-h-[24px] ${isClickable ? 'cursor-pointer' : ''} ${!isDropdown ? 'mx-1' : ''}`}
|
||||
onClick={() => {
|
||||
if (isDropdown) { setIsOpen(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
}}
|
||||
>
|
||||
{isDropdown && (
|
||||
<ChevronRight
|
||||
className={`text-void-fg-3 mr-0.5 h-4 w-4 flex-shrink-0 transition-transform duration-100 ease-[cubic-bezier(0.4,0,0.2,1)] ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
)}
|
||||
<div className={`select-none flex items-center min-h-[24px] ${!isDropdown ? 'mx-1' : ''}`}>
|
||||
<div className={`flex items-center w-full gap-x-2 overflow-hidden justify-between ${isRejected ? 'line-through' : ''}`}>
|
||||
{/* left */}
|
||||
<div className={`flex items-center gap-x-2 min-w-0 overflow-hidden ${isClickable ? 'hover:brightness-125 transition-all duration-150' : ''}`}>
|
||||
<div className={`
|
||||
flex items-center min-w-0 overflow-hidden grow
|
||||
${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (isDropdown) { setIsOpen(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
}}
|
||||
>
|
||||
{isDropdown && (<ChevronRight
|
||||
className={`
|
||||
text-void-fg-3 mr-0.5 h-4 w-4 flex-shrink-0 transition-transform duration-100 ease-[cubic-bezier(0.4,0,0.2,1)]
|
||||
${isExpanded ? 'rotate-90' : ''}
|
||||
`}
|
||||
/>)}
|
||||
<span className="text-void-fg-3 flex-shrink-0">{title}</span>
|
||||
<span className="text-void-fg-4 text-xs italic truncate">{desc1}</span>
|
||||
<span className="text-void-fg-4 text-xs italic truncate ml-2">{desc1}</span>
|
||||
</div>
|
||||
|
||||
{/* right */}
|
||||
|
|
@ -953,25 +960,32 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, curr
|
|||
</div>
|
||||
|
||||
|
||||
<EditSymbol
|
||||
size={18}
|
||||
className={`
|
||||
absolute -top-1 -right-1
|
||||
translate-x-0 -translate-y-0
|
||||
cursor-pointer z-1
|
||||
p-[2px]
|
||||
bg-void-bg-1 border border-void-border-1 rounded-md
|
||||
transition-opacity duration-200 ease-in-out
|
||||
${isHovered || (isFocused && mode === 'edit') ? 'opacity-100' : 'opacity-0'}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (mode === 'display') {
|
||||
onOpenEdit()
|
||||
} else if (mode === 'edit') {
|
||||
onCloseEdit()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute -top-1 -right-1 translate-x-0 -translate-y-0 z-1"
|
||||
// data-tooltip-id='void-tooltip'
|
||||
// data-tooltip-content='Edit message'
|
||||
// data-tooltip-place='left'
|
||||
>
|
||||
<EditSymbol
|
||||
size={18}
|
||||
className={`
|
||||
cursor-pointer
|
||||
p-[2px]
|
||||
bg-void-bg-1 border border-void-border-1 rounded-md
|
||||
transition-opacity duration-200 ease-in-out
|
||||
${isHovered || (isFocused && mode === 'edit') ? 'opacity-100' : 'opacity-0'}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (mode === 'display') {
|
||||
onOpenEdit()
|
||||
} else if (mode === 'edit') {
|
||||
onCloseEdit()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -1024,6 +1038,7 @@ const SmallProseWrapper = ({ children }: { children: React.ReactNode }) => {
|
|||
prose-blockquote:pl-2
|
||||
prose-blockquote:my-2
|
||||
|
||||
prose-code:text-void-fg-3
|
||||
prose-code:text-[12px]
|
||||
prose-code:before:content-none
|
||||
prose-code:after:content-none
|
||||
|
|
@ -1237,7 +1252,7 @@ const ToolRequestAcceptRejectButtons = () => {
|
|||
<button
|
||||
onClick={onAccept}
|
||||
className={`
|
||||
px-4 py-1.5
|
||||
px-2 py-1
|
||||
bg-[var(--vscode-button-background)]
|
||||
text-[var(--vscode-button-foreground)]
|
||||
hover:bg-[var(--vscode-button-hoverBackground)]
|
||||
|
|
@ -1253,7 +1268,7 @@ const ToolRequestAcceptRejectButtons = () => {
|
|||
<button
|
||||
onClick={onReject}
|
||||
className={`
|
||||
px-4 py-1.5
|
||||
px-2 py-1
|
||||
bg-[var(--vscode-button-secondaryBackground)]
|
||||
text-[var(--vscode-button-secondaryForeground)]
|
||||
hover:bg-[var(--vscode-button-secondaryHoverBackground)]
|
||||
|
|
@ -1268,7 +1283,7 @@ const ToolRequestAcceptRejectButtons = () => {
|
|||
const autoApproveToggle = (
|
||||
<div className="flex items-center ml-2 gap-x-1">
|
||||
<VoidSwitch
|
||||
size="xs"
|
||||
size="xxs"
|
||||
value={voidSettingsState.globalSettings.autoApprove}
|
||||
onChange={onToggleAutoApprove}
|
||||
/>
|
||||
|
|
@ -1291,7 +1306,7 @@ export const ToolChildrenWrapper = ({ children, className }: { children: React.R
|
|||
</div>
|
||||
}
|
||||
export const CodeChildren = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className='bg-void-bg-3 p-1 rounded-sm font-mono overflow-auto text-sm'>
|
||||
return <div className='bg-void-bg-3 p-1 rounded-sm overflow-auto text-sm'>
|
||||
<div className='!select-text cursor-auto'>
|
||||
{children}
|
||||
</div>
|
||||
|
|
@ -1325,7 +1340,9 @@ const EditToolChildren = ({ uri, changeDescription }: { uri: URI, changeDescript
|
|||
const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: string, uri: URI, codeStr: string }) => {
|
||||
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
|
||||
return <div className='flex items-center gap-1'>
|
||||
<StatusIndicatorHTML applyBoxId={applyBoxId} uri={uri} />
|
||||
|
||||
|
||||
<StatusIndicatorForApplyButton applyBoxId={applyBoxId} uri={uri} />
|
||||
<JumpToFileButton uri={uri} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} />}
|
||||
<ApplyButtonsHTML applyBoxId={applyBoxId} uri={uri} codeStr={codeStr} reapplyIcon={true} />
|
||||
|
|
@ -1770,18 +1787,18 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
resolveReason.type === 'toofull' ? `\n(truncated)`
|
||||
: null
|
||||
|
||||
componentParams.children = <ToolChildrenWrapper className='font-mono whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
componentParams.children = <ToolChildrenWrapper className='whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
|
||||
<div className='!select-text cursor-auto'>
|
||||
<div>
|
||||
<span>{`Ran command: `}</span>
|
||||
<span className="text-void-fg-1">{command}</span>
|
||||
<span className="text-void-fg-1 font-sans">{`Ran command: `}</span>
|
||||
<span className="font-mono">{command}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{resolveReason.type === 'bgtask' ? 'Result so far:\n' : null}</span>
|
||||
<span>{`Result: `}</span>
|
||||
<span className="text-void-fg-1">{terminalResult}</span>
|
||||
<span className="text-void-fg-1">{additionalDetailsStr}</span>
|
||||
<span className="text-void-fg-1 font-mono">{terminalResult}</span>
|
||||
<span className="text-void-fg-1 font-mono">{additionalDetailsStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ToolChildrenWrapper>
|
||||
|
|
@ -1950,28 +1967,291 @@ const ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, mes
|
|||
|
||||
|
||||
|
||||
export const AcceptAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => (
|
||||
<button
|
||||
className={`
|
||||
px-1 py-0.5
|
||||
flex items-center gap-1
|
||||
text-white text-[11px] text-nowrap
|
||||
rounded-md
|
||||
cursor-pointer
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: acceptAllBg,
|
||||
border: acceptBorder,
|
||||
}}
|
||||
type='button'
|
||||
onClick={onClick}
|
||||
>
|
||||
{text ? <span>{text}</span> : <Check size={16} />}
|
||||
</button>
|
||||
)
|
||||
|
||||
export const RejectAllButtonWrapper = ({ text, onClick, className }: { text: string, onClick: () => void, className?: string }) => (
|
||||
<button
|
||||
className={`
|
||||
px-1 py-0.5
|
||||
flex items-center gap-1
|
||||
text-white text-[11px] text-nowrap
|
||||
rounded-md
|
||||
cursor-pointer
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: rejectAllBg,
|
||||
border: rejectBorder,
|
||||
}}
|
||||
type='button'
|
||||
onClick={onClick}
|
||||
>
|
||||
{text ? <span>{text}</span> : <X size={16} />}
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
|
||||
const CommandBarInChat = () => {
|
||||
const { state: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const { stateOfURI: commandBarStateOfURI, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
|
||||
const numFilesChanged = sortedCommandBarURIs.length
|
||||
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const chatThreadsState = useChatThreadsState()
|
||||
const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId)
|
||||
|
||||
if (!sortedCommandBarURIs || sortedCommandBarURIs.length === 0) {
|
||||
return null
|
||||
}
|
||||
const [fileDetailsOpenedState, setFileDetailsOpenedState] = useState<'auto-opened' | 'auto-closed' | 'user-opened' | 'user-closed'>('auto-closed');
|
||||
const isFileDetailsOpened = fileDetailsOpenedState === 'auto-opened' || fileDetailsOpenedState === 'user-opened';
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// close the file details if there are no files
|
||||
// this converts 'user-closed' to 'auto-closed'
|
||||
if (numFilesChanged === 0) {
|
||||
setFileDetailsOpenedState('auto-closed')
|
||||
}
|
||||
// open the file details if it hasnt been closed
|
||||
if (numFilesChanged > 0 && fileDetailsOpenedState !== 'user-closed') {
|
||||
setFileDetailsOpenedState('auto-opened')
|
||||
}
|
||||
}, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged])
|
||||
|
||||
|
||||
const isFinishedMakingThreadChanges = numFilesChanged !== 0 && (chatThreadsStreamState ? !chatThreadsStreamState.isRunning : true)
|
||||
|
||||
// ======== status of agent ========
|
||||
// This icon answers the question "is the LLM doing work on this thread?"
|
||||
// assume it is single threaded for now
|
||||
// green = Running
|
||||
// orange = Requires action
|
||||
// dark = Done
|
||||
|
||||
const threadStatus = (
|
||||
chatThreadsStreamState?.isRunning === 'awaiting_user' ? { title: 'Needs Approval', color: 'yellow', } as const
|
||||
: chatThreadsStreamState?.isRunning ? { title: 'Running', color: 'orange', } as const
|
||||
: { title: 'Done', color: 'dark', } as const
|
||||
)
|
||||
|
||||
|
||||
const threadStatusHTML = <StatusIndicator className='mx-1' indicatorColor={threadStatus.color} title={threadStatus.title} />
|
||||
|
||||
|
||||
// ======== info about changes ========
|
||||
// num files changed
|
||||
// acceptall + rejectall
|
||||
// popup info about each change (each with num changes + acceptall + rejectall of their own)
|
||||
|
||||
const numFilesChangedStr = numFilesChanged === 0 ? 'No files with changes'
|
||||
: `${sortedCommandBarURIs.length} file${numFilesChanged === 1 ? '' : 's'} with changes`
|
||||
|
||||
|
||||
|
||||
|
||||
const acceptRejectAllButtons = <div
|
||||
// do this with opacity so that the height remains the same at all times
|
||||
className={`flex items-center gap-0.5
|
||||
${isFinishedMakingThreadChanges ? '' : 'opacity-0 pointer-events-none'}`
|
||||
}
|
||||
>
|
||||
<IconShell1 // RejectAllButtonWrapper
|
||||
// text="Reject All"
|
||||
// className="text-xs"
|
||||
Icon={X}
|
||||
onClick={() => {
|
||||
sortedCommandBarURIs.forEach(uri => {
|
||||
editCodeService.acceptOrRejectAllDiffAreas({
|
||||
uri,
|
||||
removeCtrlKs: true,
|
||||
behavior: "reject",
|
||||
_addToHistory: true,
|
||||
});
|
||||
});
|
||||
}}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='top'
|
||||
data-tooltip-content='Reject all'
|
||||
/>
|
||||
|
||||
<IconShell1 // AcceptAllButtonWrapper
|
||||
// text="Accept All"
|
||||
// className="text-xs"
|
||||
Icon={Check}
|
||||
onClick={() => {
|
||||
sortedCommandBarURIs.forEach(uri => {
|
||||
editCodeService.acceptOrRejectAllDiffAreas({
|
||||
uri,
|
||||
removeCtrlKs: true,
|
||||
behavior: "accept",
|
||||
_addToHistory: true,
|
||||
});
|
||||
});
|
||||
}}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='top'
|
||||
data-tooltip-content='Accept all'
|
||||
/>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
// !select-text cursor-auto
|
||||
const fileDetailsContent = <div className="px-2 gap-1 w-full">
|
||||
{sortedCommandBarURIs.map((uri, i) => {
|
||||
const basename = getBasename(uri.fsPath)
|
||||
|
||||
const { sortedDiffIds, isStreaming } = commandBarStateOfURI[uri.fsPath] ?? {}
|
||||
const isFinishedMakingFileChanges = !isStreaming
|
||||
|
||||
const numDiffs = sortedDiffIds?.length || 0
|
||||
|
||||
const fileStatus = (isFinishedMakingFileChanges
|
||||
? { title: 'Done', color: 'dark', } as const
|
||||
: { title: 'Running', color: 'orange', } as const
|
||||
)
|
||||
|
||||
const fileNameHTML = <div
|
||||
className="flex items-center gap-1.5 text-void-fg-3 hover:brightness-125 transition-all duration-200 cursor-pointer"
|
||||
onClick={() => commandService.executeCommand('vscode.open', uri, { preview: true })}
|
||||
>
|
||||
{/* <FileIcon size={14} className="text-void-fg-3" /> */}
|
||||
<span className="text-void-fg-3">{basename}</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
const detailsContent = <div className='flex px-4'>
|
||||
<span className="text-void-fg-3 opacity-80">{numDiffs} diff{numDiffs !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
|
||||
const acceptRejectButtons = <div
|
||||
// do this with opacity so that the height remains the same at all times
|
||||
className={`flex items-center gap-0.5
|
||||
${isFinishedMakingThreadChanges ? '' : 'opacity-0 pointer-events-none'}
|
||||
`}
|
||||
>
|
||||
<JumpToFileButton
|
||||
uri={uri}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='top'
|
||||
data-tooltip-content='Go to file'
|
||||
/>
|
||||
<IconShell1 // RejectAllButtonWrapper
|
||||
Icon={X}
|
||||
onClick={() => { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "reject", _addToHistory: true, }); }}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='top'
|
||||
data-tooltip-content='Reject file'
|
||||
|
||||
/>
|
||||
<IconShell1 // AcceptAllButtonWrapper
|
||||
Icon={Check}
|
||||
onClick={() => { editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "accept", _addToHistory: true, }); }}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='top'
|
||||
data-tooltip-content='Accept file'
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
const fileStatusHTML = <StatusIndicator className='mx-1' indicatorColor={fileStatus.color} title={fileStatus.title} />
|
||||
|
||||
return (
|
||||
// name, details
|
||||
<div key={i} className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
{fileNameHTML}
|
||||
{detailsContent}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{acceptRejectButtons}
|
||||
{fileStatusHTML}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
const fileDetailsButton = (
|
||||
<button
|
||||
className={`flex items-center gap-1 rounded ${numFilesChanged === 0 ? 'cursor-pointer' : 'cursor-pointer hover:brightness-125 transition-all duration-200'}`}
|
||||
onClick={() => isFileDetailsOpened ? setFileDetailsOpenedState('user-closed') : setFileDetailsOpenedState('user-opened')}
|
||||
type='button'
|
||||
disabled={numFilesChanged === 0}
|
||||
>
|
||||
<svg
|
||||
className="transition-transform duration-200 size-3.5"
|
||||
style={{
|
||||
transform: isFileDetailsOpened ? 'rotate(0deg)' : 'rotate(180deg)',
|
||||
transition: 'transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1)'
|
||||
}}
|
||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="18 15 12 9 6 15"></polyline>
|
||||
</svg>
|
||||
{numFilesChangedStr}
|
||||
</button>
|
||||
)
|
||||
|
||||
return (
|
||||
<SimplifiedToolHeader title={'Changes'}>
|
||||
{sortedCommandBarURIs.map((uri, i) => (
|
||||
<ListableToolItem
|
||||
key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', uri, { preview: true }) }}
|
||||
/>
|
||||
))}
|
||||
</SimplifiedToolHeader>
|
||||
<>
|
||||
{/* file details */}
|
||||
<div className='px-2'>
|
||||
<div
|
||||
className={`
|
||||
select-none
|
||||
flex w-full rounded-t-lg bg-void-bg-3
|
||||
text-void-fg-3 text-xs text-nowrap
|
||||
|
||||
overflow-hidden transition-all duration-200 ease-in-out
|
||||
${isFileDetailsOpened ? 'max-h-24' : 'max-h-0'}
|
||||
`}
|
||||
>
|
||||
{fileDetailsContent}
|
||||
</div>
|
||||
</div>
|
||||
{/* main content */}
|
||||
<div
|
||||
className={`
|
||||
select-none
|
||||
flex w-full rounded-t-lg bg-void-bg-3
|
||||
text-void-fg-3 text-xs text-nowrap
|
||||
border-t border-l border-r border-zinc-300/10
|
||||
|
||||
px-2 py-1
|
||||
justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
{fileDetailsButton}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
{acceptRejectAllButtons}
|
||||
{threadStatusHTML}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -2030,8 +2310,6 @@ export const SidebarChat = () => {
|
|||
const sidebarRef = useRef<HTMLDivElement>(null)
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useScrollbarStyles(sidebarRef)
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
|
||||
if (isDisabled) return
|
||||
|
|
@ -2167,33 +2445,40 @@ export const SidebarChat = () => {
|
|||
}
|
||||
}, [onSubmit, onAbort, isRunning])
|
||||
|
||||
const inputForm = <div
|
||||
key={'input' + chatThreadsState.currentThreadId}
|
||||
className='px-2 pb-2'>
|
||||
<VoidChatArea
|
||||
featureName='Chat'
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onAbort}
|
||||
isStreaming={!!isRunning}
|
||||
isDisabled={isDisabled}
|
||||
showSelections={true}
|
||||
showProspectiveSelections={previousMessagesHTML.length === 0}
|
||||
selections={selections}
|
||||
setSelections={setSelections}
|
||||
onClickAnywhere={() => { textAreaRef.current?.focus() }}
|
||||
const inputForm = <div key={'input' + chatThreadsState.currentThreadId}>
|
||||
<div className='px-4'>
|
||||
{previousMessages.length > 0 &&
|
||||
<CommandBarInChat />
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className='px-2 pb-2'
|
||||
>
|
||||
<VoidInputBox2
|
||||
className={`min-h-[81px] px-0.5 py-0.5`}
|
||||
placeholder={`${keybindingString ? `${keybindingString} to add a file. ` : ''}Enter instructions...`}
|
||||
onChangeText={onChangeText}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={() => { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }}
|
||||
ref={textAreaRef}
|
||||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
<VoidChatArea
|
||||
featureName='Chat'
|
||||
onSubmit={onSubmit}
|
||||
onAbort={onAbort}
|
||||
isStreaming={!!isRunning}
|
||||
isDisabled={isDisabled}
|
||||
showSelections={true}
|
||||
showProspectiveSelections={previousMessagesHTML.length === 0}
|
||||
selections={selections}
|
||||
setSelections={setSelections}
|
||||
onClickAnywhere={() => { textAreaRef.current?.focus() }}
|
||||
>
|
||||
<VoidInputBox2
|
||||
className={`min-h-[81px] px-0.5 py-0.5`}
|
||||
placeholder={`${keybindingString ? `${keybindingString} to add a file. ` : ''}Enter instructions...`}
|
||||
onChangeText={onChangeText}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={() => { chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) }}
|
||||
ref={textAreaRef}
|
||||
fnsRef={textAreaFnsRef}
|
||||
multiline={true}
|
||||
/>
|
||||
|
||||
</VoidChatArea>
|
||||
</VoidChatArea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -154,12 +154,13 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
|
|||
})
|
||||
|
||||
|
||||
export const VoidSimpleInputBox = ({ value, onChangeValue, placeholder, className, disabled, passwordBlur, ...inputProps }: {
|
||||
export const VoidSimpleInputBox = ({ value, onChangeValue, placeholder, className, disabled, passwordBlur, compact, ...inputProps }: {
|
||||
value: string;
|
||||
onChangeValue: (value: string) => void;
|
||||
placeholder: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
compact?: boolean;
|
||||
passwordBlur?: boolean;
|
||||
} & React.InputHTMLAttributes<HTMLInputElement>) => {
|
||||
|
||||
|
|
@ -169,7 +170,11 @@ export const VoidSimpleInputBox = ({ value, onChangeValue, placeholder, classNam
|
|||
onChange={(e) => onChangeValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className={`w-full resize-none text-void-fg-1 placeholder:text-void-fg-3 px-2 py-1 rounded-sm
|
||||
// className='max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root'
|
||||
// className={`w-full resize-none text-void-fg-1 placeholder:text-void-fg-3 px-2 py-1 rounded-sm
|
||||
className={`w-full resize-none bg-void-bg-1 text-void-fg-1 placeholder:text-void-fg-3 border border-void-border-2 focus:border-void-border-1
|
||||
${compact ? 'py-1 px-2' : 'py-2 px-4 '}
|
||||
rounded
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
||||
${className}`}
|
||||
style={{
|
||||
|
|
@ -636,7 +641,7 @@ export const VoidCustomDropdownBox = <T extends NonNullable<any>>({
|
|||
className="flex items-center h-4 bg-transparent whitespace-nowrap hover:brightness-90 w-full"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<span className={`max-w-[120px] truncate ${arrowTouchesText ? 'mr-1' : ''}`}>
|
||||
<span className={`truncate ${arrowTouchesText ? 'mr-1' : ''}`}>
|
||||
{getOptionDisplayName(selectedOption)}
|
||||
</span>
|
||||
<svg
|
||||
|
|
@ -955,9 +960,9 @@ export const BlockCode = ({ initValue, language, maxHeight, showScrollbars }: Bl
|
|||
}
|
||||
|
||||
|
||||
export const VoidButton = ({ children, disabled, onClick }: { children: React.ReactNode; disabled?: boolean; onClick: () => void }) => {
|
||||
export const VoidButtonBgDarken = ({ children, disabled, onClick }: { children: React.ReactNode; disabled?: boolean; onClick: () => void }) => {
|
||||
return <button disabled={disabled}
|
||||
className='px-3 py-1 bg-black/10 dark:bg-white/10 rounded-sm overflow-hidden whitespace-nowrap'
|
||||
className='px-3 py-1 bg-black/10 dark:bg-white/10 rounded-sm overflow-hidden whitespace-nowrap flex items-center justify-center'
|
||||
onClick={onClick}
|
||||
>{children}</button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,9 +350,9 @@ export const useCommandBarURIListener = (listener: (uri: URI) => void) => {
|
|||
export const useCommandBarState = () => {
|
||||
const accessor = useAccessor()
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const [s, ss] = useState({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
const [s, ss] = useState({ stateOfURI: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
const listener = useCallback(() => {
|
||||
ss({ state: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
ss({ stateOfURI: commandBarService.stateOfURI, sortedURIs: commandBarService.sortedURIs });
|
||||
}, [commandBarService])
|
||||
useCommandBarURIListener(listener)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,128 +1,130 @@
|
|||
import { useEffect } from 'react';
|
||||
// Get rid of this as it was causing lag
|
||||
|
||||
export const useScrollbarStyles = (containerRef: React.MutableRefObject<HTMLDivElement | null>) => {
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
// import { useEffect } from 'react';
|
||||
|
||||
// Create selector for specific overflow classes
|
||||
const overflowSelector = [
|
||||
'[class*="overflow-auto"]',
|
||||
'[class*="overflow-x-auto"]',
|
||||
'[class*="overflow-y-auto"]'
|
||||
].join(',');
|
||||
// export const useScrollbarStyles = (containerRef: React.RefObject<HTMLDivElement | null>) => {
|
||||
// useEffect(() => {
|
||||
// if (!containerRef.current) return;
|
||||
|
||||
// Function to initialize scrollbar styles for elements
|
||||
const initializeScrollbarStyles = () => {
|
||||
// Get all matching elements within the container, including the container itself
|
||||
const scrollElements = [
|
||||
...(containerRef.current?.matches(overflowSelector) ? [containerRef.current] : []),
|
||||
...Array.from(containerRef.current?.querySelectorAll(overflowSelector) || [])
|
||||
];
|
||||
// // Create selector for specific overflow classes
|
||||
// const overflowSelector = [
|
||||
// '[class*="overflow-auto"]',
|
||||
// '[class*="overflow-x-auto"]',
|
||||
// '[class*="overflow-y-auto"]'
|
||||
// ].join(',');
|
||||
|
||||
// Apply basic styling to all elements
|
||||
scrollElements.forEach(element => {
|
||||
element.classList.add('void-scrollable-element');
|
||||
});
|
||||
// // Function to initialize scrollbar styles for elements
|
||||
// const initializeScrollbarStyles = () => {
|
||||
// // Get all matching elements within the container, including the container itself
|
||||
// const scrollElements = [
|
||||
// ...(containerRef.current?.matches(overflowSelector) ? [containerRef.current] : []),
|
||||
// ...Array.from(containerRef.current?.querySelectorAll(overflowSelector) || [])
|
||||
// ];
|
||||
|
||||
// Only initialize fade effects for elements that haven't been initialized yet
|
||||
scrollElements.forEach(element => {
|
||||
if (!(element as any).__scrollbarCleanup) {
|
||||
let fadeTimeout: NodeJS.Timeout | null = null;
|
||||
let fadeInterval: NodeJS.Timeout | null = null;
|
||||
// // Apply basic styling to all elements
|
||||
// scrollElements.forEach(element => {
|
||||
// element.classList.add('void-scrollable-element');
|
||||
// });
|
||||
|
||||
const fadeIn = () => {
|
||||
if (fadeInterval) clearInterval(fadeInterval);
|
||||
// // Only initialize fade effects for elements that haven't been initialized yet
|
||||
// scrollElements.forEach(element => {
|
||||
// if (!(element as any).__scrollbarCleanup) {
|
||||
// let fadeTimeout: NodeJS.Timeout | null = null;
|
||||
// let fadeInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
let step = 0;
|
||||
fadeInterval = setInterval(() => {
|
||||
if (step <= 10) {
|
||||
element.classList.remove(`show-scrollbar-${step - 1}`);
|
||||
element.classList.add(`show-scrollbar-${step}`);
|
||||
step++;
|
||||
} else {
|
||||
clearInterval(fadeInterval!);
|
||||
}
|
||||
}, 10);
|
||||
};
|
||||
// const fadeIn = () => {
|
||||
// if (fadeInterval) clearInterval(fadeInterval);
|
||||
|
||||
const fadeOut = () => {
|
||||
if (fadeInterval) clearInterval(fadeInterval);
|
||||
// let step = 0;
|
||||
// fadeInterval = setInterval(() => {
|
||||
// if (step <= 10) {
|
||||
// element.classList.remove(`show-scrollbar-${step - 1}`);
|
||||
// element.classList.add(`show-scrollbar-${step}`);
|
||||
// step++;
|
||||
// } else {
|
||||
// clearInterval(fadeInterval!);
|
||||
// }
|
||||
// }, 10);
|
||||
// };
|
||||
|
||||
let step = 10;
|
||||
fadeInterval = setInterval(() => {
|
||||
if (step >= 0) {
|
||||
element.classList.remove(`show-scrollbar-${step + 1}`);
|
||||
element.classList.add(`show-scrollbar-${step}`);
|
||||
step--;
|
||||
} else {
|
||||
clearInterval(fadeInterval!);
|
||||
}
|
||||
}, 60);
|
||||
};
|
||||
// const fadeOut = () => {
|
||||
// if (fadeInterval) clearInterval(fadeInterval);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
if (fadeTimeout) clearTimeout(fadeTimeout);
|
||||
if (fadeInterval) clearInterval(fadeInterval);
|
||||
fadeIn();
|
||||
};
|
||||
// let step = 10;
|
||||
// fadeInterval = setInterval(() => {
|
||||
// if (step >= 0) {
|
||||
// element.classList.remove(`show-scrollbar-${step + 1}`);
|
||||
// element.classList.add(`show-scrollbar-${step}`);
|
||||
// step--;
|
||||
// } else {
|
||||
// clearInterval(fadeInterval!);
|
||||
// }
|
||||
// }, 60);
|
||||
// };
|
||||
|
||||
const onMouseLeave = () => {
|
||||
if (fadeTimeout) clearTimeout(fadeTimeout);
|
||||
fadeTimeout = setTimeout(() => {
|
||||
fadeOut();
|
||||
}, 10);
|
||||
};
|
||||
// const onMouseEnter = () => {
|
||||
// if (fadeTimeout) clearTimeout(fadeTimeout);
|
||||
// if (fadeInterval) clearInterval(fadeInterval);
|
||||
// fadeIn();
|
||||
// };
|
||||
|
||||
element.addEventListener('mouseenter', onMouseEnter);
|
||||
element.addEventListener('mouseleave', onMouseLeave);
|
||||
// const onMouseLeave = () => {
|
||||
// if (fadeTimeout) clearTimeout(fadeTimeout);
|
||||
// fadeTimeout = setTimeout(() => {
|
||||
// fadeOut();
|
||||
// }, 10);
|
||||
// };
|
||||
|
||||
// Store cleanup function
|
||||
const cleanup = () => {
|
||||
element.removeEventListener('mouseenter', onMouseEnter);
|
||||
element.removeEventListener('mouseleave', onMouseLeave);
|
||||
if (fadeTimeout) clearTimeout(fadeTimeout);
|
||||
if (fadeInterval) clearInterval(fadeInterval);
|
||||
element.classList.remove('void-scrollable-element');
|
||||
// Remove any remaining show-scrollbar classes
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
element.classList.remove(`show-scrollbar-${i}`);
|
||||
}
|
||||
};
|
||||
// element.addEventListener('mouseenter', onMouseEnter);
|
||||
// element.addEventListener('mouseleave', onMouseLeave);
|
||||
|
||||
// Store the cleanup function on the element for later use
|
||||
(element as any).__scrollbarCleanup = cleanup;
|
||||
}
|
||||
});
|
||||
};
|
||||
// // Store cleanup function
|
||||
// const cleanup = () => {
|
||||
// element.removeEventListener('mouseenter', onMouseEnter);
|
||||
// element.removeEventListener('mouseleave', onMouseLeave);
|
||||
// if (fadeTimeout) clearTimeout(fadeTimeout);
|
||||
// if (fadeInterval) clearInterval(fadeInterval);
|
||||
// element.classList.remove('void-scrollable-element');
|
||||
// // Remove any remaining show-scrollbar classes
|
||||
// for (let i = 0; i <= 10; i++) {
|
||||
// element.classList.remove(`show-scrollbar-${i}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
// Initialize for the first time
|
||||
initializeScrollbarStyles();
|
||||
// // Store the cleanup function on the element for later use
|
||||
// (element as any).__scrollbarCleanup = cleanup;
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// Set up mutation observer to do the same
|
||||
const observer = new MutationObserver(() => {
|
||||
initializeScrollbarStyles();
|
||||
});
|
||||
// // Initialize for the first time
|
||||
// initializeScrollbarStyles();
|
||||
|
||||
// Start observing the container for child changes
|
||||
observer.observe(containerRef.current, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
// // Set up mutation observer to do the same
|
||||
// const observer = new MutationObserver(() => {
|
||||
// initializeScrollbarStyles();
|
||||
// });
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
// Your existing cleanup code...
|
||||
if (containerRef.current) {
|
||||
const scrollElements = [
|
||||
...(containerRef.current.matches(overflowSelector) ? [containerRef.current] : []),
|
||||
...Array.from(containerRef.current.querySelectorAll(overflowSelector))
|
||||
];
|
||||
scrollElements.forEach(element => {
|
||||
if ((element as any).__scrollbarCleanup) {
|
||||
(element as any).__scrollbarCleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [containerRef]);
|
||||
};
|
||||
// // Start observing the container for child changes
|
||||
// observer.observe(containerRef.current, {
|
||||
// childList: true,
|
||||
// subtree: true
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// observer.disconnect();
|
||||
// // Your existing cleanup code...
|
||||
// if (containerRef.current) {
|
||||
// const scrollElements = [
|
||||
// ...(containerRef.current.matches(overflowSelector) ? [containerRef.current] : []),
|
||||
// ...Array.from(containerRef.current.querySelectorAll(overflowSelector))
|
||||
// ];
|
||||
// scrollElements.forEach(element => {
|
||||
// if ((element as any).__scrollbarCleanup) {
|
||||
// (element as any).__scrollbarCleanup();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
// }, [containerRef]);
|
||||
// };
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import { useAccessor, useCommandBarState, useIsDark } from '../util/services.js'
|
|||
import '../styles.css'
|
||||
import { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { VoidCommandBarProps } from '../../../voidCommandBarService.js';
|
||||
import { AcceptAllButtonWrapper, RejectAllButtonWrapper } from '../sidebar-tsx/SidebarChat.js';
|
||||
|
||||
export const VoidCommandBarMain = ({ uri, editor }: VoidCommandBarProps) => {
|
||||
const isDark = useIsDark()
|
||||
|
|
@ -39,7 +40,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const commandBarService = accessor.get('IVoidCommandBarService')
|
||||
const voidModelService = accessor.get('IVoidModelService')
|
||||
const { state: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
|
||||
const { stateOfURI: commandBarState, sortedURIs: sortedCommandBarURIs } = useCommandBarState()
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
|
|
@ -211,38 +212,47 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
|
||||
if (!isADiffZoneInAnyFile) return null
|
||||
|
||||
const acceptAllButton = <button
|
||||
className='text-nowrap'
|
||||
// const acceptAllButton = <button
|
||||
// className='text-nowrap'
|
||||
// onClick={onAcceptAll}
|
||||
// style={{
|
||||
// backgroundColor: acceptAllBg,
|
||||
// border: acceptBorder,
|
||||
// color: buttonTextColor,
|
||||
// fontSize: buttonFontSize,
|
||||
// padding: '2px 4px',
|
||||
// borderRadius: '6px',
|
||||
// cursor: 'pointer'
|
||||
// }}
|
||||
// >
|
||||
// Accept File
|
||||
// </button>
|
||||
|
||||
// const rejectAllButton = <button
|
||||
// className='text-nowrap'
|
||||
// onClick={onRejectAll}
|
||||
// style={{
|
||||
// backgroundColor: rejectBg,
|
||||
// border: rejectBorder,
|
||||
// color: 'white',
|
||||
// fontSize: buttonFontSize,
|
||||
// padding: '2px 4px',
|
||||
// borderRadius: '6px',
|
||||
// cursor: 'pointer'
|
||||
// }}
|
||||
// >
|
||||
// Reject File
|
||||
// </button>
|
||||
|
||||
const acceptAllButton = <AcceptAllButtonWrapper
|
||||
text={'Accept File'}
|
||||
onClick={onAcceptAll}
|
||||
style={{
|
||||
backgroundColor: acceptAllBg,
|
||||
border: acceptBorder,
|
||||
color: buttonTextColor,
|
||||
fontSize: buttonFontSize,
|
||||
padding: '2px 4px',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Accept File
|
||||
</button>
|
||||
/>
|
||||
|
||||
|
||||
const rejectAllButton = <button
|
||||
className='text-nowrap'
|
||||
const rejectAllButton = <RejectAllButtonWrapper
|
||||
text={'Reject File'}
|
||||
onClick={onRejectAll}
|
||||
style={{
|
||||
backgroundColor: rejectAllBg,
|
||||
border: rejectBorder,
|
||||
color: 'white',
|
||||
fontSize: buttonFontSize,
|
||||
padding: '2px 4px',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Reject File
|
||||
</button>
|
||||
/>
|
||||
|
||||
const acceptRejectAllButtons = <div className="flex items-center gap-1 text-sm">
|
||||
{acceptAllButton}
|
||||
|
|
@ -273,7 +283,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
<div className={`${!isADiffZoneInThisFile ? 'hidden' : ''} flex items-center ${upDownDisabled ? 'opacity-50' : ''}`}>
|
||||
{upButton}
|
||||
{downButton}
|
||||
<span className="min-w-16 px-2 text-xs">
|
||||
<span className="min-w-16 px-2 text-xs leading-[1]">
|
||||
{isADiffInThisFile ?
|
||||
`Diff ${(currDiffIdx ?? 0) + 1} of ${sortedDiffIds.length}`
|
||||
: streamState === 'streaming' ?
|
||||
|
|
@ -289,7 +299,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
{/* <div className="w-px h-3 bg-void-border-3 mx-0.5 shadow-sm"></div> */}
|
||||
{rightButton}
|
||||
{/* <div className="w-px h-3 bg-void-border-3 mx-0.5 shadow-sm"></div> */}
|
||||
<span className="min-w-16 px-2 text-xs">
|
||||
<span className="min-w-16 px-2 text-xs leading-[1]">
|
||||
{currFileIdx !== null ?
|
||||
`File ${currFileIdx + 1} of ${sortedCommandBarURIs.length}`
|
||||
: `${sortedCommandBarURIs.length} file${sortedCommandBarURIs.length === 1 ? '' : 's'} changed`
|
||||
|
|
@ -299,7 +309,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
return <div className={`flex flex-col items-center gap-y-2 mx-2 pointer-events-auto`}>
|
||||
return <div className={`flex flex-col items-center gap-y-2 pointer-events-auto`}>
|
||||
{showAcceptRejectAll && acceptRejectAllButtons}
|
||||
{leftRightUpDownButtons}
|
||||
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { useAccessor, useActiveURI, useIsDark, useSettingsState } from '../util/services.js';
|
||||
|
||||
import '../styles.css'
|
||||
import { VOID_CTRL_K_ACTION_ID, VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
|
||||
import { Circle, MoreVertical } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { VoidSelectionHelperProps } from '../../../../../../contrib/void/browser/voidSelectionHelperWidget.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
|
||||
|
||||
|
||||
export const VoidSelectionHelperMain = (props: VoidSelectionHelperProps) => {
|
||||
|
||||
const isDark = useIsDark()
|
||||
|
||||
return <div
|
||||
className={`@@void-scope ${isDark ? 'dark' : ''}`}
|
||||
>
|
||||
<VoidSelectionHelper {...props} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
const VoidSelectionHelper = ({ rerenderKey }: VoidSelectionHelperProps) => {
|
||||
|
||||
|
||||
const accessor = useAccessor()
|
||||
const keybindingService = accessor.get('IKeybindingService')
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
const ctrlLKeybind = keybindingService.lookupKeybinding(VOID_CTRL_L_ACTION_ID)
|
||||
const ctrlKKeybind = keybindingService.lookupKeybinding(VOID_CTRL_K_ACTION_ID)
|
||||
|
||||
const dividerHTML = <div className='w-[0.5px] bg-void-border-3'></div>
|
||||
|
||||
const [reactRerenderCount, setReactRerenderKey] = useState(rerenderKey)
|
||||
const [clickState, setClickState] = useState<'init' | 'clickedOption' | 'clickedMore'>('init')
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = commandService.onWillExecuteCommand(e => {
|
||||
if (e.commandId === VOID_CTRL_L_ACTION_ID || e.commandId === VOID_CTRL_K_ACTION_ID) {
|
||||
setClickState('clickedOption')
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [commandService, setClickState]);
|
||||
|
||||
|
||||
// rerender when the key changes
|
||||
if (reactRerenderCount !== rerenderKey) {
|
||||
setReactRerenderKey(rerenderKey)
|
||||
setClickState('init')
|
||||
}
|
||||
// useEffect(() => {
|
||||
// }, [rerenderKey, reactRerenderCount, setReactRerenderKey, setClickState])
|
||||
|
||||
// if the user selected an option, close
|
||||
|
||||
|
||||
if (clickState === 'clickedOption') {
|
||||
return null
|
||||
}
|
||||
|
||||
const defaultHTML = <>
|
||||
{ctrlLKeybind &&
|
||||
<div
|
||||
className='
|
||||
flex items-center px-2 py-1.5
|
||||
cursor-pointer
|
||||
'
|
||||
onClick={() => {
|
||||
commandService.executeCommand(VOID_CTRL_L_ACTION_ID)
|
||||
setClickState('clickedOption');
|
||||
}}
|
||||
>
|
||||
<span>Add to Chat</span>
|
||||
<span className='ml-1 px-1 rounded bg-[var(--vscode-keybindingLabel-background)] text-[var(--vscode-keybindingLabel-foreground)] border border-[var(--vscode-keybindingLabel-border)]'>
|
||||
{ctrlLKeybind.getLabel()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
{ctrlLKeybind && ctrlKKeybind &&
|
||||
dividerHTML
|
||||
}
|
||||
{ctrlKKeybind &&
|
||||
<div
|
||||
className='
|
||||
flex items-center px-2 py-1.5
|
||||
cursor-pointer
|
||||
'
|
||||
onClick={() => {
|
||||
commandService.executeCommand(VOID_CTRL_K_ACTION_ID)
|
||||
setClickState('clickedOption');
|
||||
}}
|
||||
>
|
||||
<span className='ml-1'>Edit Inline</span>
|
||||
<span className='ml-1 px-1 rounded bg-[var(--vscode-keybindingLabel-background)] text-[var(--vscode-keybindingLabel-foreground)] border border-[var(--vscode-keybindingLabel-border)]'>
|
||||
{ctrlKKeybind.getLabel()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
{dividerHTML}
|
||||
|
||||
<div
|
||||
className='
|
||||
flex items-center px-0.5
|
||||
cursor-pointer
|
||||
'
|
||||
onClick={() => {
|
||||
setClickState('clickedMore');
|
||||
}}
|
||||
>
|
||||
<MoreVertical className="w-4" />
|
||||
</div>
|
||||
</>
|
||||
|
||||
|
||||
const moreOptionsHTML = <>
|
||||
<div
|
||||
className='
|
||||
flex items-center px-2 py-1.5
|
||||
cursor-pointer
|
||||
'
|
||||
onClick={() => {
|
||||
commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID);
|
||||
setClickState('clickedOption');
|
||||
}}
|
||||
>
|
||||
Disable Suggestions?
|
||||
</div>
|
||||
|
||||
{dividerHTML}
|
||||
|
||||
<div
|
||||
className='
|
||||
flex items-center px-0.5
|
||||
cursor-pointer
|
||||
'
|
||||
onClick={() => {
|
||||
setClickState('init');
|
||||
}}
|
||||
>
|
||||
<MoreVertical className="w-4" />
|
||||
</div>
|
||||
</>
|
||||
|
||||
return <div className='
|
||||
pointer-events-auto select-none
|
||||
z-[1000]
|
||||
rounded-sm shadow-md flex flex-nowrap text-nowrap
|
||||
border border-void-border-3 bg-void-bg-2
|
||||
transition-all duration-200
|
||||
'>
|
||||
{clickState === 'init' ? defaultHTML
|
||||
: clickState === 'clickedMore' ? moreOptionsHTML
|
||||
: <></>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -5,5 +5,9 @@
|
|||
|
||||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
import { VoidCommandBarMain } from './VoidCommandBar.js'
|
||||
import { VoidSelectionHelperMain } from './VoidSelectionHelper.js'
|
||||
|
||||
export const mountVoidCommandBar = mountFnGenerator(VoidCommandBarMain)
|
||||
|
||||
export const mountVoidSelectionHelper = mountFnGenerator(VoidSelectionHelperMain)
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,97 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import 'react-tooltip/dist/react-tooltip.css';
|
||||
import { useIsDark } from '../util/services.js';
|
||||
|
||||
/**
|
||||
* Creates a configured global tooltip component with consistent styling
|
||||
* To use:
|
||||
* 1. Mount a Tooltip with some id eg id='void-tooltip'
|
||||
* 2. Add data-tooltip-id="void-tooltip" and data-tooltip-content="Your tooltip text" to any element
|
||||
*/
|
||||
export const VoidTooltip = () => {
|
||||
|
||||
|
||||
const isDark = useIsDark()
|
||||
|
||||
return (
|
||||
|
||||
// use native colors so we don't have to worry about @@void-scope styles
|
||||
// --void-bg-1: var(--vscode-input-background);
|
||||
// --void-bg-1-alt: var(--vscode-badge-background);
|
||||
// --void-bg-2: var(--vscode-sideBar-background);
|
||||
// --void-bg-2-alt: color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%);
|
||||
// --void-bg-3: var(--vscode-editor-background);
|
||||
|
||||
// --void-fg-0: color-mix(in srgb, var(--vscode-tab-activeForeground) 90%, black 10%);
|
||||
// --void-fg-1: var(--vscode-editor-foreground);
|
||||
// --void-fg-2: var(--vscode-input-foreground);
|
||||
// --void-fg-3: var(--vscode-input-placeholderForeground);
|
||||
// /* --void-fg-4: var(--vscode-tab-inactiveForeground); */
|
||||
// --void-fg-4: var(--vscode-list-deemphasizedForeground);
|
||||
|
||||
// --void-warning: var(--vscode-charts-yellow);
|
||||
|
||||
// --void-border-1: var(--vscode-commandCenter-activeBorder);
|
||||
// --void-border-2: var(--vscode-commandCenter-border);
|
||||
// --void-border-3: var(--vscode-commandCenter-inactiveBorder);
|
||||
// --void-border-4: var(--vscode-editorGroup-border);
|
||||
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
#void-tooltip, #void-tooltip-orange, #void-tooltip-green {
|
||||
font-size: 12px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 6px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#void-tooltip {
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
}
|
||||
|
||||
#void-tooltip-orange {
|
||||
background-color: #F6762A;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#void-tooltip-green {
|
||||
background-color: #228B22;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.react-tooltip-arrow {
|
||||
z-index: -1 !important; /* Keep arrow behind content (somehow this isnt done automatically) */
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
|
||||
<Tooltip
|
||||
id="void-tooltip"
|
||||
// border='1px solid var(--vscode-editorGroup-border)'
|
||||
border='1px solid rgba(100,100,100,.2)'
|
||||
opacity={1}
|
||||
delayShow={50}
|
||||
/>
|
||||
<Tooltip
|
||||
id="void-tooltip-orange"
|
||||
border='1px solid rgba(200,200,200,.3)'
|
||||
opacity={1}
|
||||
delayShow={50}
|
||||
/>
|
||||
<Tooltip
|
||||
id="void-tooltip-green"
|
||||
border='1px solid rgba(200,200,200,.3)'
|
||||
opacity={1}
|
||||
delayShow={50}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mountFnGenerator } from '../util/mountFnGenerator.js'
|
||||
import { VoidTooltip } from './VoidTooltip.js'
|
||||
|
||||
export const mountVoidTooltip = mountFnGenerator(VoidTooltip)
|
||||
|
|
@ -7,9 +7,10 @@ import { defineConfig } from 'tsup'
|
|||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
'./src2/void-command-bar-tsx/index.tsx',
|
||||
'./src2/void-editor-widgets-tsx/index.tsx',
|
||||
'./src2/sidebar-tsx/index.tsx',
|
||||
'./src2/void-settings-tsx/index.tsx',
|
||||
'./src2/void-tooltip/index.tsx',
|
||||
'./src2/quick-edit-tsx/index.tsx',
|
||||
'./src2/diff/index.tsx',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ registerAction2(class extends Action2 {
|
|||
super({
|
||||
id: VOID_CTRL_L_ACTION_ID,
|
||||
f1: true,
|
||||
title: localize2('voidCtrlL', 'Void: Add Select to Chat'),
|
||||
title: localize2('voidCtrlL', 'Void: Add Selection to Chat'),
|
||||
keybinding: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyL,
|
||||
weight: KeybindingWeight.VoidExtension
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export const VOID_VIEW_ID = VOID_VIEW_CONTAINER_ID
|
|||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
const container = viewContainerRegistry.registerViewContainer({
|
||||
id: VOID_VIEW_CONTAINER_ID,
|
||||
title: nls.localize2('voidContainer', 'Void Chat'), // this is used to say "Void" (Ctrl + L)
|
||||
title: nls.localize2('voidContainer', 'Chat'), // this is used to say "Void" (Ctrl + L)
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VOID_VIEW_CONTAINER_ID, {
|
||||
mergeViewWithContainerWhenSingleView: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
|
|
|
|||
55
src/vs/workbench/contrib/void/browser/tooltipService.ts
Normal file
55
src/vs/workbench/contrib/void/browser/tooltipService.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
|
||||
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { mountVoidTooltip } from './react/out/void-tooltip/index.js';
|
||||
import { h, getActiveWindow } from '../../../../base/browser/dom.js';
|
||||
|
||||
// Tooltip contribution that mounts the component at startup
|
||||
export class TooltipContribution extends Disposable implements IWorkbenchContribution {
|
||||
static readonly ID = 'workbench.contrib.voidTooltip';
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this.initializeTooltip();
|
||||
}
|
||||
|
||||
private initializeTooltip(): void {
|
||||
// Get the active window reference for multi-window support
|
||||
const targetWindow = getActiveWindow();
|
||||
|
||||
// Find the monaco-workbench element using the proper window reference
|
||||
const workbench = targetWindow.document.querySelector('.monaco-workbench');
|
||||
|
||||
if (workbench) {
|
||||
// Create a container element for the tooltip using h function
|
||||
const tooltipContainer = h('div.void-tooltip-container').root;
|
||||
workbench.appendChild(tooltipContainer);
|
||||
|
||||
// Mount the React component
|
||||
this.instantiationService.invokeFunction((accessor: ServicesAccessor) => {
|
||||
const result = mountVoidTooltip(tooltipContainer, accessor);
|
||||
if (result && typeof result.dispose === 'function') {
|
||||
this._register(toDisposable(result.dispose));
|
||||
}
|
||||
});
|
||||
|
||||
// Register cleanup for the DOM element
|
||||
this._register(toDisposable(() => {
|
||||
if (tooltipContainer.parentElement) {
|
||||
tooltipContainer.parentElement.removeChild(tooltipContainer);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the contribution to be initialized during the AfterRestored phase
|
||||
registerWorkbenchContribution2(TooltipContribution.ID, TooltipContribution, WorkbenchPhase.AfterRestored);
|
||||
|
|
@ -46,6 +46,12 @@ import './metricsPollService.js'
|
|||
// helper services
|
||||
import './helperServices/consistentItemService.js'
|
||||
|
||||
// register selection helper
|
||||
import './voidSelectionHelperWidget.js'
|
||||
|
||||
// register tooltip service
|
||||
import './tooltipService.js'
|
||||
|
||||
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
|
||||
|
||||
// llmMessage
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { Widget } from '../../../../base/browser/ui/widget.js';
|
|||
import { IOverlayWidget, ICodeEditor, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { mountVoidCommandBar } from './react/out/void-command-bar-tsx/index.js'
|
||||
import { mountVoidCommandBar } from './react/out/void-editor-widgets-tsx/index.js'
|
||||
import { deepClone } from '../../../../base/common/objects.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,282 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from '../../../../editor/browser/editorBrowser.js';
|
||||
import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { ICursorSelectionChangedEvent } from '../../../../editor/common/cursorEvents.js';
|
||||
import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
|
||||
import { Selection } from '../../../../editor/common/core/selection.js';
|
||||
import { RunOnceScheduler } from '../../../../base/common/async.js';
|
||||
import * as dom from '../../../../base/browser/dom.js';
|
||||
import { mountVoidSelectionHelper } from './react/out/void-editor-widgets-tsx/index.js';
|
||||
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
|
||||
import { getLengthOfTextPx } from './editCodeService.js';
|
||||
|
||||
|
||||
const minDistanceFromRightPx = 400;
|
||||
const minLeftPx = 60;
|
||||
|
||||
|
||||
export type VoidSelectionHelperProps = {
|
||||
rerenderKey: number // alternates between 0 and 1
|
||||
}
|
||||
|
||||
|
||||
export class SelectionHelperContribution extends Disposable implements IEditorContribution, IOverlayWidget {
|
||||
public static readonly ID = 'editor.contrib.voidSelectionHelper';
|
||||
// react
|
||||
private _rootHTML: HTMLElement;
|
||||
private _rerender: (props?: any) => void = () => { };
|
||||
private _rerenderKey: number = 0;
|
||||
private _reactComponentDisposable: IDisposable | null = null;
|
||||
|
||||
// internal
|
||||
private _isVisible = false;
|
||||
private _showScheduler: RunOnceScheduler;
|
||||
private _lastSelection: Selection | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IVoidSettingsService private readonly _voidSettingsService: IVoidSettingsService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Create the container element for React component
|
||||
const { root, content } = dom.h('div@root', [
|
||||
dom.h('div@content', [])
|
||||
]);
|
||||
|
||||
// Set styles for container
|
||||
root.style.position = 'absolute';
|
||||
root.style.display = 'none'; // Start hidden
|
||||
root.style.pointerEvents = 'none';
|
||||
root.style.marginLeft = '16px';
|
||||
|
||||
// Initialize React component
|
||||
this._instantiationService.invokeFunction(accessor => {
|
||||
if (this._reactComponentDisposable) {
|
||||
this._reactComponentDisposable.dispose();
|
||||
}
|
||||
const res = mountVoidSelectionHelper(content, accessor);
|
||||
if (!res) return;
|
||||
|
||||
this._reactComponentDisposable = res;
|
||||
this._rerender = res.rerender;
|
||||
|
||||
this._register(this._reactComponentDisposable);
|
||||
|
||||
|
||||
});
|
||||
|
||||
this._rootHTML = root;
|
||||
|
||||
// Register as overlay widget
|
||||
this._editor.addOverlayWidget(this);
|
||||
|
||||
// Use scheduler to debounce showing widget
|
||||
this._showScheduler = new RunOnceScheduler(() => {
|
||||
if (this._lastSelection) {
|
||||
this._showHelperForSelection(this._lastSelection);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// Register event listeners
|
||||
this._register(this._editor.onDidChangeCursorSelection(e => this._onSelectionChange(e)));
|
||||
|
||||
// Add a flag to track if mouse is over the widget
|
||||
let isMouseOverWidget = false;
|
||||
this._rootHTML.addEventListener('mouseenter', () => {
|
||||
isMouseOverWidget = true;
|
||||
});
|
||||
this._rootHTML.addEventListener('mouseleave', () => {
|
||||
isMouseOverWidget = false;
|
||||
});
|
||||
|
||||
// Only hide helper when text editor loses focus and mouse is not over the widget
|
||||
this._register(this._editor.onDidBlurEditorText(() => {
|
||||
if (!isMouseOverWidget) {
|
||||
this._hideHelper();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._editor.onDidScrollChange(() => this._updatePositionIfVisible()));
|
||||
this._register(this._editor.onDidLayoutChange(() => this._updatePositionIfVisible()));
|
||||
}
|
||||
|
||||
// IOverlayWidget implementation
|
||||
public getId(): string {
|
||||
return SelectionHelperContribution.ID;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._rootHTML;
|
||||
}
|
||||
|
||||
public getPosition(): IOverlayWidgetPosition | null {
|
||||
return null; // We position manually
|
||||
}
|
||||
|
||||
private _onSelectionChange(e: ICursorSelectionChangedEvent): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._editor.getModel().uri.scheme !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = this._editor.getSelection();
|
||||
|
||||
if (!selection || selection.isEmpty()) {
|
||||
this._hideHelper();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selection text to check if it's worth showing the helper
|
||||
const text = this._editor.getModel()!.getValueInRange(selection);
|
||||
if (text.length < 3) {
|
||||
this._hideHelper();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store selection
|
||||
this._lastSelection = new Selection(
|
||||
selection.startLineNumber,
|
||||
selection.startColumn,
|
||||
selection.endLineNumber,
|
||||
selection.endColumn
|
||||
);
|
||||
|
||||
this._showScheduler.schedule();
|
||||
}
|
||||
|
||||
// Update the _showHelperForSelection method to work with the React component
|
||||
private _showHelperForSelection(selection: Selection): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel()!;
|
||||
|
||||
// get the longest length of the nearest neighbors of the target
|
||||
const { tabSize: numSpacesInTab } = model.getFormattingOptions();
|
||||
const spaceWidth = this._editor.getOption(EditorOption.fontInfo).spaceWidth;
|
||||
const tabWidth = numSpacesInTab * spaceWidth;
|
||||
const numLinesModel = model.getLineCount()
|
||||
|
||||
// Calculate right edge of visible editor area
|
||||
const editorWidthPx = this._editor.getLayoutInfo().width;
|
||||
const maxLeftPx = editorWidthPx - minDistanceFromRightPx
|
||||
|
||||
// returns the position where the box should go on the targetLine
|
||||
const getBoxPosition = (targetLine: number): { top: number, left: number } => {
|
||||
|
||||
const targetPosition = this._editor.getScrolledVisiblePosition({ lineNumber: targetLine, column: 1 }) ?? { left: 0, top: 0 };
|
||||
|
||||
const { top: targetTop, left: targetLeft } = targetPosition
|
||||
|
||||
let targetWidth = 0;
|
||||
for (let i = targetLine; i <= targetLine + 1; i++) {
|
||||
|
||||
// if not in range, continue
|
||||
if (!(i >= 1) || !(i <= numLinesModel)) continue;
|
||||
|
||||
const content = model.getLineContent(i);
|
||||
const currWidth = getLengthOfTextPx({
|
||||
tabWidth,
|
||||
spaceWidth,
|
||||
content
|
||||
})
|
||||
|
||||
targetWidth = Math.max(targetWidth, currWidth);
|
||||
}
|
||||
|
||||
return {
|
||||
top: targetTop,
|
||||
left: targetLeft + targetWidth,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Calculate the middle line of the selection
|
||||
const startLine = selection.startLineNumber;
|
||||
const endLine = selection.endLineNumber;
|
||||
// const middleLine = Math.floor(startLine + (endLine - startLine) / 2);
|
||||
const targetLine = endLine - startLine + 1 <= 2 ? startLine : startLine + 2;
|
||||
|
||||
let boxPos = getBoxPosition(targetLine);
|
||||
|
||||
// if the position of the box is too far to the right, keep searching for a good position
|
||||
const lineDeltasToTry = [-1, -2, -3, 1, 2, 3];
|
||||
|
||||
if (boxPos.left > maxLeftPx) {
|
||||
for (const lineDelta of lineDeltasToTry) {
|
||||
|
||||
boxPos = getBoxPosition(targetLine + lineDelta);
|
||||
if (boxPos.left <= maxLeftPx) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boxPos.left > maxLeftPx) { // if still not found, make it 2 lines before
|
||||
boxPos = getBoxPosition(targetLine - 2)
|
||||
}
|
||||
|
||||
|
||||
// Position the helper element at the end of the middle line but ensure it's visible
|
||||
const xPosition = Math.max(Math.min(boxPos.left, maxLeftPx), minLeftPx);
|
||||
const yPosition = boxPos.top;
|
||||
|
||||
// Update the React component position
|
||||
this._rootHTML.style.left = `${xPosition}px`;
|
||||
this._rootHTML.style.top = `${yPosition}px`;
|
||||
this._rootHTML.style.display = 'flex'; // Show the container
|
||||
|
||||
this._isVisible = true;
|
||||
|
||||
// rerender
|
||||
const enabled = this._voidSettingsService.state.globalSettings.showInlineSuggestions
|
||||
&& this._editor.hasTextFocus() // needed since VS Code counts unfocused selections as selections, which causes this to rerender when it shouldnt (bad ux)
|
||||
|
||||
if (enabled) {
|
||||
this._rerender({ rerenderKey: this._rerenderKey } satisfies VoidSelectionHelperProps)
|
||||
this._rerenderKey = (this._rerenderKey + 1) % 2;
|
||||
// this._reactComponentRerender();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _hideHelper(): void {
|
||||
this._rootHTML.style.display = 'none';
|
||||
this._isVisible = false;
|
||||
this._lastSelection = null;
|
||||
}
|
||||
|
||||
private _updatePositionIfVisible(): void {
|
||||
if (!this._isVisible || !this._lastSelection || !this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showHelperForSelection(this._lastSelection);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._hideHelper();
|
||||
if (this._reactComponentDisposable) {
|
||||
this._reactComponentDisposable.dispose();
|
||||
}
|
||||
this._editor.removeOverlayWidget(this);
|
||||
this._showScheduler.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Register the contribution
|
||||
registerEditorContribution(SelectionHelperContribution.ID, SelectionHelperContribution, EditorContributionInstantiation.Eager);
|
||||
|
|
@ -6,6 +6,46 @@
|
|||
import { FeatureName, ModelSelectionOptions, ProviderName } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const defaultProviderSettings = {
|
||||
anthropic: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAI: {
|
||||
apiKey: '',
|
||||
},
|
||||
deepseek: {
|
||||
apiKey: '',
|
||||
},
|
||||
ollama: {
|
||||
endpoint: 'http://127.0.0.1:11434',
|
||||
},
|
||||
vLLM: {
|
||||
endpoint: 'http://localhost:8000',
|
||||
},
|
||||
openRouter: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAICompatible: {
|
||||
endpoint: '',
|
||||
apiKey: '',
|
||||
},
|
||||
gemini: {
|
||||
apiKey: '',
|
||||
},
|
||||
groq: {
|
||||
apiKey: '',
|
||||
},
|
||||
xAI: {
|
||||
apiKey: ''
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
|
||||
|
||||
export const defaultModelsOfProvider = {
|
||||
openAI: [ // https://platform.openai.com/docs/models/gp
|
||||
'o3-mini',
|
||||
|
|
@ -68,19 +108,22 @@ export const defaultModelsOfProvider = {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type ModelOptions = {
|
||||
export type VoidStaticModelInfo = { // not stateful
|
||||
contextWindow: number; // input tokens
|
||||
maxOutputTokens: number | null; // output tokens, defaults to 4092
|
||||
cost: { // <-- UNUSED
|
||||
cost: { // <-- UNUSED
|
||||
input: number;
|
||||
output: number;
|
||||
cache_read?: number;
|
||||
cache_write?: number;
|
||||
}
|
||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated';
|
||||
|
||||
downloadable: false | {
|
||||
sizeGb: number | 'not-known'
|
||||
}
|
||||
|
||||
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special parameter
|
||||
// supportsTools: false | 'TODO-yes-but-we-handle-it-manually' | 'anthropic-style' | 'openai-style';
|
||||
supportsFIM: boolean;
|
||||
|
||||
reasoningCapabilities: false | {
|
||||
|
|
@ -108,18 +151,19 @@ type ProviderReasoningIOSettings = {
|
|||
| { nameOfFieldInDelta?: undefined, needsManualParse?: true, };
|
||||
}
|
||||
|
||||
type ProviderSettings = {
|
||||
type VoidStaticProviderInfo = { // doesn't change (not stateful)
|
||||
providerReasoningIOSettings?: ProviderReasoningIOSettings; // input/output settings around thinking (allowed to be empty) - only applied if the model supports reasoning output
|
||||
modelOptions: { [key: string]: ModelOptions };
|
||||
modelOptionsFallback: (modelName: string) => (ModelOptions & { modelName: string }) | null;
|
||||
modelOptions: { [key: string]: VoidStaticModelInfo };
|
||||
modelOptionsFallback: (modelName: string, fallbackKnownValues?: Partial<VoidStaticModelInfo>) => (VoidStaticModelInfo & { modelName: string }) | null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const modelOptionsDefaults: ModelOptions = {
|
||||
const modelOptionsDefaults: VoidStaticModelInfo = {
|
||||
contextWindow: 32_000,
|
||||
maxOutputTokens: 4_096,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
supportsSystemMessage: false,
|
||||
supportsFIM: false,
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -242,21 +286,24 @@ const openSourceModelOptions_assumingOAICompat = {
|
|||
contextWindow: 128_000, maxOutputTokens: 8_192,
|
||||
|
||||
},
|
||||
} as const satisfies { [s: string]: Omit<ModelOptions, 'cost'> }
|
||||
} as const satisfies { [s: string]: Partial<VoidStaticModelInfo> }
|
||||
|
||||
|
||||
|
||||
|
||||
const extensiveModelFallback: ProviderSettings['modelOptionsFallback'] = (modelName) => {
|
||||
const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (modelName, fallbackKnownValues) => {
|
||||
|
||||
|
||||
const lower = modelName.toLowerCase()
|
||||
|
||||
const toFallback = (opts: Omit<ModelOptions, 'cost'>): ModelOptions & { modelName: string } => {
|
||||
const toFallback = (opts: Omit<VoidStaticModelInfo, 'cost' | 'downloadable'>): VoidStaticModelInfo & { modelName: string } => {
|
||||
return {
|
||||
modelName,
|
||||
...opts,
|
||||
supportsSystemMessage: opts.supportsSystemMessage ? 'system-role' : false,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
...fallbackKnownValues
|
||||
}
|
||||
}
|
||||
if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower))
|
||||
|
|
@ -313,6 +360,7 @@ const anthropicModelOptions = {
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'separated',
|
||||
reasoningCapabilities: {
|
||||
|
|
@ -327,6 +375,7 @@ const anthropicModelOptions = {
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'separated',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -335,6 +384,7 @@ const anthropicModelOptions = {
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 0.80, cache_read: 0.08, cache_write: 1.00, output: 4.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'separated',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -343,20 +393,22 @@ const anthropicModelOptions = {
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: 4_096,
|
||||
cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 75.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'separated',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'claude-3-sonnet-20240229': { // no point of using this, but including this for people who put it in
|
||||
contextWindow: 200_000, cost: { input: 3.00, output: 15.00 },
|
||||
downloadable: false,
|
||||
maxOutputTokens: 4_096,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'separated',
|
||||
reasoningCapabilities: false,
|
||||
}
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
const anthropicSettings: ProviderSettings = {
|
||||
const anthropicSettings: VoidStaticProviderInfo = {
|
||||
providerReasoningIOSettings: {
|
||||
input: {
|
||||
includeInPayload: (reasoningInfo) => {
|
||||
|
|
@ -388,6 +440,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: 100_000,
|
||||
cost: { input: 15.00, cache_read: 7.50, output: 60.00, },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'developer-role',
|
||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false }, // it doesn't actually output reasoning, but our logic is fine with it
|
||||
|
|
@ -396,6 +449,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: 100_000,
|
||||
cost: { input: 1.10, cache_read: 0.55, output: 4.40, },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'developer-role',
|
||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false },
|
||||
|
|
@ -404,6 +458,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: 16_384,
|
||||
cost: { input: 2.50, cache_read: 1.25, output: 10.00, },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -412,6 +467,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: 65_536,
|
||||
cost: { input: 1.10, cache_read: 0.55, output: 4.40, },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: false, // does not support any system
|
||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false },
|
||||
|
|
@ -420,14 +476,15 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: 16_384,
|
||||
cost: { input: 0.15, cache_read: 0.075, output: 0.60, },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role', // ??
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
|
||||
const openAISettings: ProviderSettings = {
|
||||
const openAISettings: VoidStaticProviderInfo = {
|
||||
modelOptions: openAIModelOptions,
|
||||
modelOptionsFallback: (modelName) => {
|
||||
const lower = modelName.toLowerCase()
|
||||
|
|
@ -446,13 +503,14 @@ const xAIModelOptions = {
|
|||
contextWindow: 131_072,
|
||||
maxOutputTokens: null, // 131_072,
|
||||
cost: { input: 2.00, output: 10.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
const xAISettings: ProviderSettings = {
|
||||
const xAISettings: VoidStaticProviderInfo = {
|
||||
modelOptions: xAIModelOptions,
|
||||
modelOptionsFallback: (modelName) => {
|
||||
const lower = modelName.toLowerCase()
|
||||
|
|
@ -470,6 +528,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -478,6 +537,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192, // 8_192,
|
||||
cost: { input: 0.10, output: 0.40 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -486,6 +546,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192, // 8_192,
|
||||
cost: { input: 0.075, output: 0.30 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -494,6 +555,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192, // 8_192,
|
||||
cost: { input: 0.075, output: 0.30 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -502,6 +564,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
contextWindow: 2_097_152,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 1.25, output: 5.00 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -510,13 +573,14 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 0.0375, output: 0.15 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
const geminiSettings: ProviderSettings = {
|
||||
const geminiSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: geminiModelOptions,
|
||||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
|
@ -530,17 +594,19 @@ const deepseekModelOptions = {
|
|||
contextWindow: 64_000, // https://api-docs.deepseek.com/quick_start/pricing
|
||||
maxOutputTokens: 8_000, // 8_000,
|
||||
cost: { cache_read: .07, input: .27, output: 1.10, },
|
||||
downloadable: false,
|
||||
},
|
||||
'deepseek-reasoner': {
|
||||
...openSourceModelOptions_assumingOAICompat.deepseekCoderV2,
|
||||
contextWindow: 64_000,
|
||||
maxOutputTokens: 8_000, // 8_000,
|
||||
cost: { cache_read: .14, input: .55, output: 2.19, },
|
||||
downloadable: false,
|
||||
},
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
|
||||
const deepseekSettings: ProviderSettings = {
|
||||
const deepseekSettings: VoidStaticProviderInfo = {
|
||||
modelOptions: deepseekModelOptions,
|
||||
providerReasoningIOSettings: {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://api-docs.deepseek.com/guides/reasoning_model
|
||||
|
|
@ -555,6 +621,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: 32_768, // 32_768,
|
||||
cost: { input: 0.59, output: 0.79 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -563,6 +630,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: 8_192,
|
||||
cost: { input: 0.05, output: 0.08 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -571,6 +639,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: null, // not specified?
|
||||
cost: { input: 0.79, output: 0.79 },
|
||||
downloadable: false,
|
||||
supportsFIM: false, // unfortunately looks like no FIM support on groq
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -579,12 +648,13 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: null, // not specified?
|
||||
cost: { input: 0.29, output: 0.39 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] }, // we're using reasoning_format:parsed so really don't need to know openSourceThinkTags
|
||||
},
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
const groqSettings: ProviderSettings = {
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
const groqSettings: VoidStaticProviderInfo = {
|
||||
providerReasoningIOSettings: {
|
||||
input: {
|
||||
includeInPayload: (reasoningInfo) => {
|
||||
|
|
@ -600,23 +670,67 @@ const groqSettings: ProviderSettings = {
|
|||
modelOptionsFallback: (modelName) => { return null }
|
||||
}
|
||||
|
||||
const ollamaModelOptions = {
|
||||
'qwen2.5-coder:3b': {
|
||||
contextWindow: 32_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: { sizeGb: 1.9 },
|
||||
supportsFIM: true,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'qwen2.5-coder': {
|
||||
contextWindow: 128_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: { sizeGb: 4.7 },
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
},
|
||||
'qwq': {
|
||||
contextWindow: 128_000,
|
||||
maxOutputTokens: 32_000,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: { sizeGb: 20 },
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] },
|
||||
},
|
||||
'deepseek-r1': {
|
||||
contextWindow: 128_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: { sizeGb: 4.7 },
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] },
|
||||
},
|
||||
|
||||
} as const satisfies Record<string, VoidStaticModelInfo>
|
||||
|
||||
export const ollamaRecommendedModels = ['qwen2.5-coder:3b', 'qwq', 'deepseek-r1'] as const satisfies (keyof typeof ollamaModelOptions)[]
|
||||
|
||||
|
||||
|
||||
// ---------------- VLLM, OLLAMA, OPENAICOMPAT (self-hosted / local) ----------------
|
||||
const vLLMSettings: ProviderSettings = {
|
||||
|
||||
const vLLMSettings: VoidStaticProviderInfo = {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://docs.vllm.ai/en/stable/features/reasoning_outputs.html#streaming-chat-completions
|
||||
providerReasoningIOSettings: { output: { nameOfFieldInDelta: 'reasoning_content' }, },
|
||||
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName),
|
||||
modelOptions: {},
|
||||
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
|
||||
modelOptions: {}, // TODO
|
||||
}
|
||||
|
||||
const ollamaSettings: ProviderSettings = {
|
||||
const ollamaSettings: VoidStaticProviderInfo = {
|
||||
// reasoning: we need to filter out reasoning <think> tags manually
|
||||
providerReasoningIOSettings: { output: { needsManualParse: true }, },
|
||||
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName),
|
||||
modelOptions: {},
|
||||
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName, { downloadable: { sizeGb: 'not-known' } }),
|
||||
modelOptions: ollamaModelOptions,
|
||||
}
|
||||
|
||||
const openaiCompatible: ProviderSettings = {
|
||||
const openaiCompatible: VoidStaticProviderInfo = {
|
||||
// reasoning: we have no idea what endpoint they used, so we can't consistently parse out reasoning
|
||||
modelOptionsFallback: (modelName) => extensiveModelFallback(modelName),
|
||||
modelOptions: {},
|
||||
|
|
@ -629,6 +743,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -637,6 +752,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -645,6 +761,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -653,6 +770,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0, output: 0 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -662,11 +780,13 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 128_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0.8, output: 2.4 },
|
||||
downloadable: false,
|
||||
},
|
||||
'anthropic/claude-3.7-sonnet:thinking': {
|
||||
contextWindow: 200_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 3.00, output: 15.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: { // same as anthropic, see above
|
||||
|
|
@ -681,6 +801,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 3.00, output: 15.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false, // stupidly, openrouter separates thinking from non-thinking
|
||||
|
|
@ -689,6 +810,7 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
contextWindow: 200_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 3.00, output: 15.00 },
|
||||
downloadable: false,
|
||||
supportsFIM: false,
|
||||
supportsSystemMessage: 'system-role',
|
||||
reasoningCapabilities: false,
|
||||
|
|
@ -699,22 +821,25 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
|||
maxOutputTokens: null,
|
||||
cost: { input: 0.3, output: 0.9 },
|
||||
reasoningCapabilities: false,
|
||||
downloadable: false,
|
||||
},
|
||||
'qwen/qwen-2.5-coder-32b-instruct': {
|
||||
...openSourceModelOptions_assumingOAICompat['qwen2.5coder'],
|
||||
contextWindow: 33_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0.07, output: 0.16 },
|
||||
downloadable: false,
|
||||
},
|
||||
'qwen/qwq-32b': {
|
||||
...openSourceModelOptions_assumingOAICompat['qwq'],
|
||||
contextWindow: 33_000,
|
||||
maxOutputTokens: null,
|
||||
cost: { input: 0.07, output: 0.16 },
|
||||
downloadable: false,
|
||||
}
|
||||
} as const satisfies { [s: string]: ModelOptions }
|
||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||
|
||||
const openRouterSettings: ProviderSettings = {
|
||||
const openRouterSettings: VoidStaticProviderInfo = {
|
||||
// reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models
|
||||
providerReasoningIOSettings: {
|
||||
input: {
|
||||
|
|
@ -741,7 +866,7 @@ const openRouterSettings: ProviderSettings = {
|
|||
|
||||
// ---------------- model settings of everything above ----------------
|
||||
|
||||
const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSettings } = {
|
||||
const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProviderInfo } = {
|
||||
openAI: openAISettings,
|
||||
anthropic: anthropicSettings,
|
||||
xAI: xAISettings,
|
||||
|
|
@ -767,8 +892,10 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSetting
|
|||
// ---------------- exports ----------------
|
||||
|
||||
// returns the capabilities and the adjusted modelName if it was a fallback
|
||||
export const getModelCapabilities = (providerName: ProviderName, modelName: string): ModelOptions & { modelName: string; isUnrecognizedModel: boolean } => {
|
||||
export const getModelCapabilities = (providerName: ProviderName, modelName: string): VoidStaticModelInfo & { modelName: string; isUnrecognizedModel: boolean } => {
|
||||
|
||||
const lowercaseModelName = modelName.toLowerCase()
|
||||
|
||||
const { modelOptions, modelOptionsFallback } = modelSettingsOfProvider[providerName]
|
||||
|
||||
// search model options object directly first
|
||||
|
|
|
|||
|
|
@ -92,6 +92,10 @@ export const voidTools = {
|
|||
}
|
||||
},
|
||||
|
||||
// pathname_search: {
|
||||
// name: 'pathname_search',
|
||||
// description: `Returns all pathnames that match a given \`find\`-style query over the entire workspace. ONLY searches file names. ONLY searches the current workspace. You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`,
|
||||
|
||||
search_pathnames_only: {
|
||||
name: 'search_pathnames_only',
|
||||
description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path.`,
|
||||
|
|
@ -102,6 +106,8 @@ export const voidTools = {
|
|||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
search_files: {
|
||||
name: 'search_files',
|
||||
description: `Returns all pathnames that match a given query (searches ONLY file contents). The query can be any substring or glob. You can follow this with read_file to view result contents.`,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta
|
|||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { IMetricsService } from './metricsService.js';
|
||||
import { getModelCapabilities } from './modelCapabilities.js';
|
||||
import { defaultProviderSettings, getModelCapabilities } from './modelCapabilities.js';
|
||||
import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, defaultProviderSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode } from './voidSettingsTypes.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
// name is the name in the dropdown
|
||||
|
|
@ -71,10 +71,10 @@ export interface IVoidSettingsService {
|
|||
|
||||
|
||||
|
||||
const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], options: { existingModels: VoidModelInfo[] }) => {
|
||||
const _updatedModelsAfterDefaultModelsChange = (defaultModelNames: string[], options: { existingModels: VoidStatefulModelInfo[] }) => {
|
||||
const { existingModels } = options
|
||||
|
||||
const existingModelsMap: Record<string, VoidModelInfo> = {}
|
||||
const existingModelsMap: Record<string, VoidStatefulModelInfo> = {}
|
||||
for (const existingModel of existingModels) {
|
||||
existingModelsMap[existingModel.modelName] = existingModel
|
||||
}
|
||||
|
|
@ -363,7 +363,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
|||
const modelIdx = models.findIndex(m => m.modelName === modelName)
|
||||
if (modelIdx === -1) return
|
||||
const newIsHidden = !models[modelIdx].isHidden
|
||||
const newModels: VoidModelInfo[] = [
|
||||
const newModels: VoidStatefulModelInfo[] = [
|
||||
...models.slice(0, modelIdx),
|
||||
{ ...models[modelIdx], isHidden: newIsHidden },
|
||||
...models.slice(modelIdx + 1, Infinity)
|
||||
|
|
|
|||
|
|
@ -4,49 +4,13 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { defaultModelsOfProvider } from './modelCapabilities.js';
|
||||
import { defaultModelsOfProvider, defaultProviderSettings } from './modelCapabilities.js';
|
||||
import { VoidSettingsState } from './voidSettingsService.js'
|
||||
|
||||
|
||||
type UnionOfKeys<T> = T extends T ? keyof T : never;
|
||||
|
||||
|
||||
export const defaultProviderSettings = {
|
||||
anthropic: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAI: {
|
||||
apiKey: '',
|
||||
},
|
||||
deepseek: {
|
||||
apiKey: '',
|
||||
},
|
||||
ollama: {
|
||||
endpoint: 'http://127.0.0.1:11434',
|
||||
},
|
||||
vLLM: {
|
||||
endpoint: 'http://localhost:8000',
|
||||
},
|
||||
openRouter: {
|
||||
apiKey: '',
|
||||
},
|
||||
openAICompatible: {
|
||||
endpoint: '',
|
||||
apiKey: '',
|
||||
},
|
||||
gemini: {
|
||||
apiKey: '',
|
||||
},
|
||||
groq: {
|
||||
apiKey: '',
|
||||
},
|
||||
xAI: {
|
||||
apiKey: ''
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
|
||||
|
||||
export type ProviderName = keyof typeof defaultProviderSettings
|
||||
export const providerNames = Object.keys(defaultProviderSettings) as ProviderName[]
|
||||
|
|
@ -64,7 +28,7 @@ export const customSettingNamesOfProvider = (providerName: ProviderName) => {
|
|||
|
||||
|
||||
|
||||
export type VoidModelInfo = { // <-- STATEFUL
|
||||
export type VoidStatefulModelInfo = { // <-- STATEFUL
|
||||
modelName: string,
|
||||
isDefault: boolean, // whether or not it's a default for its provider
|
||||
isHidden: boolean, // whether or not the user is hiding it (switched off)
|
||||
|
|
@ -75,7 +39,7 @@ export type VoidModelInfo = { // <-- STATEFUL
|
|||
|
||||
type CommonProviderSettings = {
|
||||
_didFillInProviderSettings: boolean | undefined, // undefined initially, computed when user types in all fields
|
||||
models: VoidModelInfo[],
|
||||
models: VoidStatefulModelInfo[],
|
||||
}
|
||||
|
||||
export type SettingsAtProvider<providerName extends ProviderName> = CustomProviderSettings<providerName> & CommonProviderSettings
|
||||
|
|
@ -227,7 +191,7 @@ const defaultCustomSettings: Record<CustomSettingName, undefined> = {
|
|||
}
|
||||
|
||||
|
||||
const modelInfoOfDefaultModelNames = (defaultModelNames: string[]): { models: VoidModelInfo[] } => {
|
||||
const modelInfoOfDefaultModelNames = (defaultModelNames: string[]): { models: VoidStatefulModelInfo[] } => {
|
||||
return {
|
||||
models: defaultModelNames.map((modelName, i) => ({
|
||||
modelName,
|
||||
|
|
@ -334,6 +298,8 @@ export const displayInfoOfFeatureName = (featureName: FeatureName) => {
|
|||
export const refreshableProviderNames = localProviderNames
|
||||
export type RefreshableProviderName = typeof refreshableProviderNames[number]
|
||||
|
||||
// models that come with download buttons
|
||||
export const hasDownloadButtonsOnModelsProviderNames = ['ollama'] as const satisfies ProviderName[]
|
||||
|
||||
|
||||
|
||||
|
|
@ -389,6 +355,7 @@ export type GlobalSettings = {
|
|||
enableFastApply: boolean;
|
||||
chatMode: ChatMode;
|
||||
autoApprove: boolean;
|
||||
showInlineSuggestions: boolean;
|
||||
}
|
||||
|
||||
export const defaultGlobalSettings: GlobalSettings = {
|
||||
|
|
@ -399,6 +366,7 @@ export const defaultGlobalSettings: GlobalSettings = {
|
|||
enableFastApply: true,
|
||||
chatMode: 'agent',
|
||||
autoApprove: false,
|
||||
showInlineSuggestions: true,
|
||||
}
|
||||
|
||||
export type GlobalSettingName = keyof GlobalSettings
|
||||
|
|
|
|||
|
|
@ -195,6 +195,192 @@ const prepareMessages_addSystemInstructions = ({
|
|||
return { messages: newMessages, separateSystemMessageStr }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// // convert messages as if about to send to openai
|
||||
// /*
|
||||
// reference - https://platform.openai.com/docs/guides/function-calling#function-calling-steps
|
||||
// openai MESSAGE (role=assistant):
|
||||
// "tool_calls":[{
|
||||
// "type": "function",
|
||||
// "id": "call_12345xyz",
|
||||
// "function": {
|
||||
// "name": "get_weather",
|
||||
// "arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
|
||||
// }]
|
||||
|
||||
// openai RESPONSE (role=user):
|
||||
// { "role": "tool",
|
||||
// "tool_call_id": tool_call.id,
|
||||
// "content": str(result) }
|
||||
|
||||
// also see
|
||||
// openai on prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting
|
||||
// openai on developer system message - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command
|
||||
// */
|
||||
|
||||
// type PrepareMessagesToolsOpenAI = (
|
||||
// Exclude<InternalLLMChatMessage, { role: 'assistant' | 'tool' }> | {
|
||||
// role: 'assistant',
|
||||
// content: string | (AnthropicReasoning | { type: 'text'; text: string })[];
|
||||
// tool_calls?: {
|
||||
// type: 'function';
|
||||
// id: string;
|
||||
// function: {
|
||||
// name: string;
|
||||
// arguments: string;
|
||||
// }
|
||||
// }[]
|
||||
// } | {
|
||||
// role: 'tool',
|
||||
// tool_call_id: string;
|
||||
// content: string;
|
||||
// }
|
||||
// )[]
|
||||
// const prepareMessages_tools_openai = ({ messages }: { messages: InternalLLMChatMessage[], }) => {
|
||||
|
||||
// const newMessages: PrepareMessagesToolsOpenAI = [];
|
||||
|
||||
// for (let i = 0; i < messages.length; i += 1) {
|
||||
// const currMsg = messages[i]
|
||||
|
||||
// if (currMsg.role !== 'tool') {
|
||||
// newMessages.push(currMsg)
|
||||
// continue
|
||||
// }
|
||||
|
||||
// // edit previous assistant message to have called the tool
|
||||
// const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined
|
||||
// if (prevMsg?.role === 'assistant') {
|
||||
// prevMsg.tool_calls = [{
|
||||
// type: 'function',
|
||||
// id: currMsg.id,
|
||||
// function: {
|
||||
// name: currMsg.name,
|
||||
// arguments: JSON.stringify(currMsg.params)
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
|
||||
// // add the tool
|
||||
// newMessages.push({
|
||||
// role: 'tool',
|
||||
// tool_call_id: currMsg.id,
|
||||
// content: currMsg.content || EMPTY_TOOL_CONTENT,
|
||||
// })
|
||||
// }
|
||||
// return { messages: newMessages }
|
||||
|
||||
// }
|
||||
|
||||
|
||||
// // convert messages as if about to send to anthropic
|
||||
// /*
|
||||
// https://docs.anthropic.com/en/docs/build-with-claude/tool-use#tool-use-examples
|
||||
// anthropic MESSAGE (role=assistant):
|
||||
// "content": [{
|
||||
// "type": "text",
|
||||
// "text": "<thinking>I need to call the get_weather function, and the user wants SF, which is likely San Francisco, CA.</thinking>"
|
||||
// }, {
|
||||
// "type": "tool_use",
|
||||
// "id": "toolu_01A09q90qw90lq917835lq9",
|
||||
// "name": "get_weather",
|
||||
// "input": { "location": "San Francisco, CA", "unit": "celsius" }
|
||||
// }]
|
||||
// anthropic RESPONSE (role=user):
|
||||
// "content": [{
|
||||
// "type": "tool_result",
|
||||
// "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
|
||||
// "content": "15 degrees"
|
||||
// }]
|
||||
// */
|
||||
|
||||
// type PrepareMessagesToolsAnthropic = (
|
||||
// Exclude<InternalLLMChatMessage, { role: 'assistant' | 'user' }> | {
|
||||
// role: 'assistant',
|
||||
// content: string | (
|
||||
// | AnthropicReasoning
|
||||
// | {
|
||||
// type: 'text';
|
||||
// text: string;
|
||||
// }
|
||||
// | {
|
||||
// type: 'tool_use';
|
||||
// name: string;
|
||||
// input: Record<string, any>;
|
||||
// id: string;
|
||||
// })[]
|
||||
// } | {
|
||||
// role: 'user',
|
||||
// content: string | ({
|
||||
// type: 'text';
|
||||
// text: string;
|
||||
// } | {
|
||||
// type: 'tool_result';
|
||||
// tool_use_id: string;
|
||||
// content: string;
|
||||
// })[]
|
||||
// }
|
||||
// )[]
|
||||
// /*
|
||||
// Converts:
|
||||
|
||||
// assistant: ...content
|
||||
// tool: (id, name, params)
|
||||
// ->
|
||||
// assistant: ...content, call(name, id, params)
|
||||
// user: ...content, result(id, content)
|
||||
// */
|
||||
// const prepareMessages_tools_anthropic = ({ messages }: { messages: InternalLLMChatMessage[], }) => {
|
||||
// const newMessages: PrepareMessagesToolsAnthropic = messages;
|
||||
|
||||
|
||||
// for (let i = 0; i < newMessages.length; i += 1) {
|
||||
// const currMsg = newMessages[i]
|
||||
|
||||
// if (currMsg.role !== 'tool') continue
|
||||
|
||||
// const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined
|
||||
|
||||
// if (prevMsg?.role === 'assistant') {
|
||||
// if (typeof prevMsg.content === 'string') prevMsg.content = [{ type: 'text', text: prevMsg.content }]
|
||||
// prevMsg.content.push({ type: 'tool_use', id: currMsg.id, name: currMsg.name, input: parseObject(currMsg.params) })
|
||||
// }
|
||||
|
||||
// // turn each tool into a user message with tool results at the end
|
||||
// newMessages[i] = {
|
||||
// role: 'user',
|
||||
// content: [
|
||||
// ...[{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content || EMPTY_TOOL_CONTENT }] as const,
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// return { messages: newMessages }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// type PrepareMessagesTools = PrepareMessagesToolsAnthropic | PrepareMessagesToolsOpenAI
|
||||
|
||||
// const prepareMessages_tools = ({ messages, supportsTools }: { messages: InternalLLMChatMessage[], supportsTools: false | 'TODO-yes-but-we-handle-it-manually' | 'anthropic-style' | 'openai-style' }): { messages: PrepareMessagesTools } => {
|
||||
// if (!supportsTools) {
|
||||
// return { messages: messages }
|
||||
// }
|
||||
// else if (supportsTools === 'anthropic-style') {
|
||||
// return prepareMessages_tools_anthropic({ messages })
|
||||
// }
|
||||
// else if (supportsTools === 'openai-style') {
|
||||
// return prepareMessages_tools_openai({ messages })
|
||||
// }
|
||||
// else {
|
||||
// throw new Error(`supportsTools type not recognized`)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// remove rawAnthropicAssistantContent, and make content equal to it if supportsAnthropicContent
|
||||
const prepareMessages_anthropicReasoning = ({ messages, supportsAnthropicReasoningSignature }: { messages: LLMChatMessage[], supportsAnthropicReasoningSignature: boolean }) => {
|
||||
const newMessages: InternalLLMChatMessage[] = []
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import { Ollama } from 'ollama';
|
|||
import OpenAI, { ClientOptions } from 'openai';
|
||||
|
||||
import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js';
|
||||
import { ChatMode, defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js';
|
||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js';
|
||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings } from '../../common/modelCapabilities.js';
|
||||
import { extractReasoningWrapper, extractToolsWrapper } from './extractGrammar.js';
|
||||
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ const newOpenAICompatibleSDK = ({ settingsOfProvider, providerName, includeInPay
|
|||
}
|
||||
else if (providerName === 'gemini') {
|
||||
const thisConfig = settingsOfProvider[providerName]
|
||||
return new OpenAI({ baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
|
||||
return new OpenAI({ baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/', apiKey: thisConfig.apiKey, ...commonPayloadOpts })
|
||||
}
|
||||
else if (providerName === 'deepseek') {
|
||||
const thisConfig = settingsOfProvider[providerName]
|
||||
|
|
|
|||
Loading…
Reference in a new issue