mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Merge pull request #393 from voideditor/model-selection
Quasar + misc fixes before 1.0.3
This commit is contained in:
commit
5d7f8fc91f
15 changed files with 975 additions and 731 deletions
|
|
@ -616,12 +616,13 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
delete this._currentlyRunningToolInterruptor[threadId];
|
delete this._currentlyRunningToolInterruptor[threadId];
|
||||||
}
|
}
|
||||||
toolResult = await result // ts is bad... await is needed
|
toolResult = await result // ts is bad... await is needed
|
||||||
|
|
||||||
|
if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (interrupted) {
|
if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here
|
||||||
// the tool result is added when we stop running
|
|
||||||
return { interrupted: true }
|
|
||||||
}
|
|
||||||
const errorMessage = getErrorMessage(error)
|
const errorMessage = getErrorMessage(error)
|
||||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, })
|
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, })
|
||||||
return {}
|
return {}
|
||||||
|
|
|
||||||
|
|
@ -678,7 +678,8 @@ const ToolHeaderWrapper = ({
|
||||||
onClick,
|
onClick,
|
||||||
isOpen,
|
isOpen,
|
||||||
isRejected,
|
isRejected,
|
||||||
}: ToolHeaderParams) => {
|
className, // applies to the main content
|
||||||
|
}: ToolHeaderParams & { className?: string }) => {
|
||||||
|
|
||||||
const [isOpen_, setIsOpen] = useState(false);
|
const [isOpen_, setIsOpen] = useState(false);
|
||||||
const isExpanded = isOpen !== undefined ? isOpen : isOpen_
|
const isExpanded = isOpen !== undefined ? isOpen : isOpen_
|
||||||
|
|
@ -687,7 +688,7 @@ const ToolHeaderWrapper = ({
|
||||||
const isClickable = !!(isDropdown || onClick)
|
const isClickable = !!(isDropdown || onClick)
|
||||||
|
|
||||||
return (<div className=''>
|
return (<div className=''>
|
||||||
<div className="w-full border border-void-border-3 rounded px-2 py-1 bg-void-bg-3 overflow-hidden ">
|
<div className={`w-full border border-void-border-3 rounded px-2 py-1 bg-void-bg-3 overflow-hidden ${className}`}>
|
||||||
{/* header */}
|
{/* header */}
|
||||||
<div className={`select-none flex items-center min-h-[24px] ${!isDropdown ? 'mx-1' : ''}`}>
|
<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' : ''}`}>
|
<div className={`flex items-center w-full gap-x-2 overflow-hidden justify-between ${isRejected ? 'line-through' : ''}`}>
|
||||||
|
|
@ -1346,17 +1347,18 @@ const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) =>
|
||||||
|
|
||||||
if (lintErrors.length === 0) return null;
|
if (lintErrors.length === 0) return null;
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-2">
|
<div className="w-full px-2">
|
||||||
<div className="w-full border-l border-r border-b border-void-border-2 rounded bg-void-bg-3 overflow-hidden">
|
<ToolHeaderWrapper className='!border-t-0' title={'Lint errors'} desc1={''} isOpen={isOpen} onClick={() => { setIsOpen(o => !o) }} >
|
||||||
|
|
||||||
<div className="text-xs text-void-fg-4 opacity-80 border-l-2 border-void-warning px-2 py-0.5 flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap">
|
<div className="text-xs text-void-fg-4 opacity-80 border-l-2 border-void-warning px-2 py-0.5 flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap">
|
||||||
{lintErrors.map((error, i) => (
|
{lintErrors.map((error, i) => (
|
||||||
<div key={i}>Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
|
<div key={i}>Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</ToolHeaderWrapper>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,7 @@ export const VoidSwitch = ({
|
||||||
onChange,
|
onChange,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
value: boolean;
|
value: boolean;
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
|
|
@ -425,7 +426,7 @@ export const VoidSwitch = ({
|
||||||
size?: 'xxs' | 'xs' | 'sm' | 'sm+' | 'md';
|
size?: 'xxs' | 'xs' | 'sm' | 'sm+' | 'md';
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<label className="inline-flex items-center">
|
<label className="inline-flex items-center" {...props}>
|
||||||
<div
|
<div
|
||||||
onClick={() => !disabled && onChange(!value)}
|
onClick={() => !disabled && onChange(!value)}
|
||||||
className={`
|
className={`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,745 @@
|
||||||
|
/*--------------------------------------------------------------------------------------
|
||||||
|
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||||
|
*--------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useAccessor, useIsDark, useSettingsState } from '../util/services.js';
|
||||||
|
import { Check, ExternalLink, X } from 'lucide-react';
|
||||||
|
import { displayInfoOfProviderName, ProviderName, providerNames, refreshableProviderNames } from '../../../../common/voidSettingsTypes.js';
|
||||||
|
import { getModelCapabilities, ollamaRecommendedModels } from '../../../../common/modelCapabilities.js';
|
||||||
|
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
|
||||||
|
import { AddModelInputBox, AnimatedCheckmarkButton, ollamaSetupInstructions, OneClickSwitchButton, SettingsForProvider } from '../void-settings-tsx/Settings.js';
|
||||||
|
|
||||||
|
|
||||||
|
export const VoidOnboarding = () => {
|
||||||
|
|
||||||
|
const voidSettingsState = useSettingsState()
|
||||||
|
const isOnboardingComplete = voidSettingsState.globalSettings.isOnboardingComplete
|
||||||
|
|
||||||
|
const isDark = useIsDark()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
bg-void-bg-3 fixed top-0 right-0 bottom-0 left-0 width-full h-full z-[99999]
|
||||||
|
transition-all duration-1000 ${isOnboardingComplete ? 'opacity-0 pointer-events-none' : 'opacity-100 pointer-events-auto'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<VoidOnboardingContent />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const FADE_DURATION_MS = 2000
|
||||||
|
|
||||||
|
|
||||||
|
const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: React.ReactNode, delayMs?: number, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||||
|
|
||||||
|
const [opacity, setOpacity] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setOpacity(1)
|
||||||
|
}, delayMs)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [setOpacity, delayMs])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} style={{ opacity, transition: `opacity ${FADE_DURATION_MS}ms ease-in-out` }} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Onboarding
|
||||||
|
// OnboardingPage
|
||||||
|
// title:
|
||||||
|
// div
|
||||||
|
// "Welcome to Void"
|
||||||
|
// image
|
||||||
|
// content:<></>
|
||||||
|
// title
|
||||||
|
// content
|
||||||
|
// prev/next
|
||||||
|
|
||||||
|
// OnboardingPage
|
||||||
|
// title:
|
||||||
|
// div
|
||||||
|
// "How would you like to use Void?"
|
||||||
|
// content:
|
||||||
|
// ModelQuestionContent
|
||||||
|
// |
|
||||||
|
// div
|
||||||
|
// "I want to:"
|
||||||
|
// div
|
||||||
|
// "Use the smartest models"
|
||||||
|
// "Keep my data fully private"
|
||||||
|
// "Save money"
|
||||||
|
// "I don't know"
|
||||||
|
// | div
|
||||||
|
// | div
|
||||||
|
// "We recommend using "
|
||||||
|
// "Set API"
|
||||||
|
// | div
|
||||||
|
// ""
|
||||||
|
// | div
|
||||||
|
//
|
||||||
|
// title
|
||||||
|
// content
|
||||||
|
// prev/next
|
||||||
|
//
|
||||||
|
// OnboardingPage
|
||||||
|
// title
|
||||||
|
// content
|
||||||
|
// prev/next
|
||||||
|
|
||||||
|
|
||||||
|
const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className="px-6 py-2 bg-[#0e70c0] enabled:hover:bg-[#1177cb] disabled:opacity-50 disabled:cursor-not-allowed rounded text-white duration-300 transition-all"
|
||||||
|
{...props.disabled && {
|
||||||
|
'data-tooltip-id': 'void-tooltip',
|
||||||
|
'data-tooltip-content': 'Disabled (Please enter all required fields or choose another Provider)',
|
||||||
|
'data-tooltip-place': 'top',
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SkipButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className="px-6 py-2 rounded bg-void-bg-2 hover:bg-void-bg-3 text-void-fg-2 duration-300 transition-all"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
Skip
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PreviousButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className="px-6 py-2 rounded text-void-fg-3 opacity-80 hover:brightness-110 duration-300 transition-all"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const OnboardingPageShell = ({ top, bottom, content, hasMaxWidth = true, className = '', }: {
|
||||||
|
top?: React.ReactNode,
|
||||||
|
bottom?: React.ReactNode,
|
||||||
|
content?: React.ReactNode,
|
||||||
|
hasMaxWidth?: boolean,
|
||||||
|
className?: string,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`min-h-full flex flex-col gap-4 w-full mx-auto ${hasMaxWidth ? 'max-w-[600px]' : ''} ${className}`}>
|
||||||
|
<FadeIn className='w-full pt-16'>{top}</FadeIn>
|
||||||
|
<FadeIn className='w-full my-auto'>{content}</FadeIn>
|
||||||
|
<div className='w-full pb-8'>{bottom}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const OllamaDownloadOrRemoveModelButton = ({ modelName, isModelInstalled, sizeGb }: { modelName: string, isModelInstalled: boolean, sizeGb: number | false | 'not-known' }) => {
|
||||||
|
|
||||||
|
|
||||||
|
// for now just link to the ollama download page
|
||||||
|
return <a
|
||||||
|
href={`https://ollama.com/library/${modelName}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center text-void-fg-2 hover:text-void-fg-1"
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-3.5 h-3.5" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
// if (isModelInstalled) {
|
||||||
|
// return <div className="flex items-center">
|
||||||
|
|
||||||
|
// <span className="flex items-center">Uninstall</span>
|
||||||
|
|
||||||
|
// <IconShell1
|
||||||
|
// className="ml-1"
|
||||||
|
// Icon={Trash}
|
||||||
|
// onClick={() => {
|
||||||
|
|
||||||
|
// setIsModelInstalling(false);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// else if (isModelInstalling) {
|
||||||
|
// return <div className="flex items-center">
|
||||||
|
|
||||||
|
// <span className="flex items-center">{`Download? ${typeof sizeGb === 'number' ? `(${sizeGb} Gb)` : ''}`}</span>
|
||||||
|
|
||||||
|
// <IconShell1
|
||||||
|
// className="ml-1"
|
||||||
|
// Icon={Square}
|
||||||
|
// onClick={() => {
|
||||||
|
// // abort()
|
||||||
|
|
||||||
|
// // TODO!!!!!!!!!!! don't do this
|
||||||
|
// setIsModelInstalling(false);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// else if (!isModelInstalled) {
|
||||||
|
|
||||||
|
// return <div className="flex items-center">
|
||||||
|
|
||||||
|
// <span className="flex items-center">Download ({sizeGb} Gb)</span>
|
||||||
|
|
||||||
|
// <IconShell1
|
||||||
|
// className="ml-1"
|
||||||
|
// Icon={Download}
|
||||||
|
// onClick={() => {
|
||||||
|
// // this is a check for whether the model was installed:
|
||||||
|
|
||||||
|
// if (isModelInstalling) return
|
||||||
|
|
||||||
|
|
||||||
|
// // TODO!!!!!! don't do this
|
||||||
|
|
||||||
|
|
||||||
|
// // install(modelname), callback = setIsModelInstalling(false);
|
||||||
|
|
||||||
|
// setIsModelInstalling(true);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return <></>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const YesNoText = ({ val }: { val: boolean | null }) => {
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className={
|
||||||
|
val === true ? "text text-green-500"
|
||||||
|
: val === false ? 'text-red-500'
|
||||||
|
: "text text-yellow-500"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
val === true ? "Yes"
|
||||||
|
: val === false ? 'No'
|
||||||
|
: "Yes*"
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const abbreviateNumber = (num: number): string => {
|
||||||
|
if (num >= 1000000) {
|
||||||
|
// For millions
|
||||||
|
return Math.floor(num / 1000000) + 'M';
|
||||||
|
} else if (num >= 1000) {
|
||||||
|
// For thousands
|
||||||
|
return Math.floor(num / 1000) + 'K';
|
||||||
|
} else {
|
||||||
|
// For numbers less than 1000
|
||||||
|
return num.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
||||||
|
|
||||||
|
const accessor = useAccessor()
|
||||||
|
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||||
|
const voidSettingsState = useSettingsState()
|
||||||
|
const isDetectableLocally = (refreshableProviderNames as ProviderName[]).includes(providerName)
|
||||||
|
// const providerCapabilities = getProviderCapabilities(providerName)
|
||||||
|
|
||||||
|
|
||||||
|
// info used to show the table
|
||||||
|
const infoOfModelName: Record<string, { showAsDefault: boolean, isDownloaded: boolean }> = {}
|
||||||
|
|
||||||
|
voidSettingsState.settingsOfProvider[providerName].models.forEach(m => {
|
||||||
|
infoOfModelName[m.modelName] = {
|
||||||
|
showAsDefault: m.isDefault,
|
||||||
|
isDownloaded: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// special case columns for ollama; show recommended models as default
|
||||||
|
if (providerName === 'ollama') {
|
||||||
|
for (const modelName of ollamaRecommendedModels) {
|
||||||
|
if (modelName in infoOfModelName) continue
|
||||||
|
infoOfModelName[modelName] = {
|
||||||
|
...infoOfModelName[modelName],
|
||||||
|
showAsDefault: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <table className="table-fixed border-collapse mb-6 bg-void-bg-2 text-sm mx-auto select-text">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-void-border-1 text-nowrap text-ellipsis">
|
||||||
|
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[200px]">Models Offered</th>
|
||||||
|
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Cost/M</th>
|
||||||
|
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Context</th>
|
||||||
|
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Chat</th>
|
||||||
|
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Agent</th>
|
||||||
|
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Autotab</th>
|
||||||
|
{/* <th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Reasoning</th> */}
|
||||||
|
{isDetectableLocally && <th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Detected</th>}
|
||||||
|
{providerName === 'ollama' && <th className="text-left py-2 px-3 font-normal text-void-fg-3">Download</th>}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.keys(infoOfModelName).map(modelName => {
|
||||||
|
const { showAsDefault, isDownloaded } = infoOfModelName[modelName]
|
||||||
|
|
||||||
|
|
||||||
|
const capabilities = getModelCapabilities(providerName, modelName)
|
||||||
|
const {
|
||||||
|
downloadable,
|
||||||
|
cost,
|
||||||
|
supportsFIM,
|
||||||
|
reasoningCapabilities,
|
||||||
|
contextWindow,
|
||||||
|
|
||||||
|
isUnrecognizedModel,
|
||||||
|
maxOutputTokens,
|
||||||
|
supportsSystemMessage,
|
||||||
|
} = capabilities
|
||||||
|
|
||||||
|
// TODO update this when tools work
|
||||||
|
const supportsTools = !!!((capabilities as unknown as any).supportsTools)
|
||||||
|
|
||||||
|
const removeModelButton = <button
|
||||||
|
className="absolute -left-1 top-1/2 transform -translate-y-1/2 -translate-x-full text-void-fg-3 hover:text-void-fg-1 text-xs"
|
||||||
|
onClick={() => voidSettingsService.deleteModel(providerName, modelName)}
|
||||||
|
>
|
||||||
|
<X className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={modelName} className="border-b border-void-border-1 hover:bg-void-bg-3/50">
|
||||||
|
<td className="py-2 px-3 relative">
|
||||||
|
{!showAsDefault && removeModelButton}
|
||||||
|
{modelName}
|
||||||
|
</td>
|
||||||
|
<td className="py-2 px-3">${cost.output ?? ''}</td>
|
||||||
|
<td className="py-2 px-3">{contextWindow ? abbreviateNumber(contextWindow) : ''}</td>
|
||||||
|
<td className="py-2 px-3"><YesNoText val={true} /></td>
|
||||||
|
<td className="py-2 px-3"><YesNoText val={!!supportsTools} /></td>
|
||||||
|
<td className="py-2 px-3"><YesNoText val={!!supportsFIM} /></td>
|
||||||
|
{/* <td className="py-2 px-3"><YesNoText val={!!reasoningCapabilities} /></td> */}
|
||||||
|
{isDetectableLocally && <td className="py-2 px-3">{!!isDownloaded ? <Check className="w-4 h-4" /> : <></>}</td>}
|
||||||
|
{providerName === 'ollama' && <th className="py-2 px-3">
|
||||||
|
<OllamaDownloadOrRemoveModelButton modelName={modelName} isModelInstalled={infoOfModelName[modelName].isDownloaded} sizeGb={downloadable && downloadable.sizeGb} />
|
||||||
|
</th>}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<tr className="hover:bg-void-bg-3/50">
|
||||||
|
<td className="py-2 px-3 text-void-accent">
|
||||||
|
<AddModelInputBox
|
||||||
|
key={providerName}
|
||||||
|
providerName={providerName}
|
||||||
|
compact={true} />
|
||||||
|
</td>
|
||||||
|
<td colSpan={4}></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type WantToUseOption = 'smart' | 'private' | 'cheap' | 'all'
|
||||||
|
|
||||||
|
const VoidOnboardingContent = () => {
|
||||||
|
|
||||||
|
|
||||||
|
const accessor = useAccessor()
|
||||||
|
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||||
|
|
||||||
|
const voidSettingsState = useSettingsState()
|
||||||
|
|
||||||
|
const [pageIndex, setPageIndex] = useState(0)
|
||||||
|
|
||||||
|
|
||||||
|
// page 1 state
|
||||||
|
const [wantToUseOption, setWantToUseOption] = useState<WantToUseOption>('smart')
|
||||||
|
|
||||||
|
// page 2 state
|
||||||
|
const [selectedProviderName, setSelectedProviderName] = useState<ProviderName | null>(null)
|
||||||
|
|
||||||
|
const providerNamesOfWantToUseOption: { [wantToUseOption in WantToUseOption]: ProviderName[] } = {
|
||||||
|
smart: ['anthropic', 'openAI', 'gemini', 'openRouter'],
|
||||||
|
private: ['ollama', 'vLLM', 'openAICompatible'],
|
||||||
|
cheap: ['gemini', 'deepseek', 'openRouter', 'ollama', 'vLLM'],
|
||||||
|
all: providerNames,
|
||||||
|
// TODO allow user to redo onboarding
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const didFillInProviderSettings = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName]._didFillInProviderSettings
|
||||||
|
const isApiKeyLongEnoughIfApiKeyExists = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].apiKey ? voidSettingsState.settingsOfProvider[selectedProviderName].apiKey.length > 15 : true
|
||||||
|
const isAtLeastOneModel = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].models.length >= 1
|
||||||
|
|
||||||
|
const didFillInSelectedProviderSettings = !!(didFillInProviderSettings && isApiKeyLongEnoughIfApiKeyExists && isAtLeastOneModel)
|
||||||
|
|
||||||
|
const prevAndNextButtons = <div className="max-w-[600px] w-full mx-auto flex flex-col items-end">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<PreviousButton
|
||||||
|
onClick={() => { setPageIndex(pageIndex - 1) }}
|
||||||
|
/>
|
||||||
|
<NextButton
|
||||||
|
onClick={() => { setPageIndex(pageIndex + 1) }}
|
||||||
|
disabled={pageIndex === 2 && !didFillInSelectedProviderSettings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
// cannot be md
|
||||||
|
const basicDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
|
||||||
|
smart: "Models with the best performance on benchmarks.",
|
||||||
|
private: "Fully private and hosted on your computer/network.",
|
||||||
|
cheap: "Free and low-cost options.",
|
||||||
|
all: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be md
|
||||||
|
const detailedDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
|
||||||
|
smart: "Most intelligent and best for agent mode.",
|
||||||
|
private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor.com) for help setting up at your company.",
|
||||||
|
cheap: "Great deals like Gemini 2.5 Pro or self-host a model with Ollama or vLLM for free.",
|
||||||
|
all: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the selected provider name appropriately
|
||||||
|
useEffect(() => {
|
||||||
|
if (wantToUseOption && providerNamesOfWantToUseOption[wantToUseOption].length > 0) {
|
||||||
|
setSelectedProviderName(providerNamesOfWantToUseOption[wantToUseOption][0]);
|
||||||
|
} else {
|
||||||
|
setSelectedProviderName(null);
|
||||||
|
}
|
||||||
|
}, [wantToUseOption]);
|
||||||
|
|
||||||
|
// set wantToUseOption to smart when page changes
|
||||||
|
useEffect(() => {
|
||||||
|
setWantToUseOption(wantToUseOption);
|
||||||
|
}, [pageIndex]);
|
||||||
|
|
||||||
|
|
||||||
|
// reset the page to page 0 if the user redos onboarding
|
||||||
|
useEffect(() => {
|
||||||
|
if (!voidSettingsState.globalSettings.isOnboardingComplete) {
|
||||||
|
setPageIndex(0)
|
||||||
|
}
|
||||||
|
}, [setPageIndex, voidSettingsState.globalSettings.isOnboardingComplete])
|
||||||
|
|
||||||
|
|
||||||
|
// TODO add a description next to the skip button saying (you can always restart the onboarding in Settings)
|
||||||
|
const contentOfIdx: { [pageIndex: number]: React.ReactNode } = {
|
||||||
|
// 0: <OnboardingPageShell
|
||||||
|
// top={
|
||||||
|
// <div className='bg-green-600 h-6 w-32' />
|
||||||
|
// }
|
||||||
|
// content={
|
||||||
|
// <div className='bg-red-600 h-[10000px] w-32' />
|
||||||
|
// }
|
||||||
|
// bottom={
|
||||||
|
// <div className='bg-blue-600 h-6 w-32' />
|
||||||
|
// }
|
||||||
|
// />,
|
||||||
|
0: <OnboardingPageShell
|
||||||
|
top={
|
||||||
|
<div className="text-5xl font-light text-center">Welcome to Void</div>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<FadeIn
|
||||||
|
delayMs={500}
|
||||||
|
className="text-center"
|
||||||
|
onClick={() => { setPageIndex(pageIndex + 1) }}
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</FadeIn>
|
||||||
|
}
|
||||||
|
bottom={
|
||||||
|
''
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
1: <OnboardingPageShell
|
||||||
|
hasMaxWidth={false}
|
||||||
|
top={
|
||||||
|
<FadeIn className='flex flex-col items-center'>
|
||||||
|
<div className="text-5xl font-light text-center">AI Preferences</div>
|
||||||
|
|
||||||
|
<div className="mt-[10%] text-base text-void-fg-2 mb-8 text-center">What are you looking for in an AI model?</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center w-full md:flex-nowrap md:max-w-[80%] max-w-[90%] gap-4">
|
||||||
|
<div
|
||||||
|
onClick={() => { setWantToUseOption('smart'); setPageIndex(pageIndex + 1); }}
|
||||||
|
className="w-full max-w-[250px] h-full relative p-6 aspect-[8/7] border border-void-border-1 rounded-md group flex flex-col items-center justify-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
|
||||||
|
<span className="text-5xl mb-4 relative z-10">🧠</span>
|
||||||
|
<h3 className="text-xl font-medium mb-3 relative z-10">Intelligence</h3>
|
||||||
|
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['smart']}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={() => { setWantToUseOption('private'); setPageIndex(pageIndex + 1); }}
|
||||||
|
className="w-full max-w-[250px] h-full relative p-6 aspect-[8/7] border border-void-border-1 rounded-md group flex flex-col items-center justify-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
|
||||||
|
<span className="text-5xl mb-4 relative z-10">🔒</span>
|
||||||
|
<h3 className="text-xl font-medium mb-3 relative z-10">Privacy</h3>
|
||||||
|
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['private']}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={() => { setWantToUseOption('cheap'); setPageIndex(pageIndex + 1); }}
|
||||||
|
className="w-full max-w-[250px] h-full relative p-6 aspect-[8/7] border border-void-border-1 rounded-md group flex flex-col items-center justify-center cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
|
||||||
|
<span className="text-5xl mb-4 relative z-10">💵</span>
|
||||||
|
<h3 className="text-xl font-medium mb-3 relative z-10">Affordability</h3>
|
||||||
|
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['cheap']}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</FadeIn>
|
||||||
|
}
|
||||||
|
content={<></>}
|
||||||
|
/>,
|
||||||
|
2: <OnboardingPageShell
|
||||||
|
top={
|
||||||
|
<div className='flex flex-col items-center'>
|
||||||
|
{/* Title */}
|
||||||
|
<div className="text-5xl font-light text-center">Choose a Provider</div>
|
||||||
|
|
||||||
|
{/* Preference Selector */}
|
||||||
|
<div className="mt-6 mb-6 mx-auto flex items-center overflow-hidden bg-zinc-700/5 dark:bg-zinc-300/5 rounded-md">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setWantToUseOption('smart');
|
||||||
|
}}
|
||||||
|
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
||||||
|
${wantToUseOption === 'smart'
|
||||||
|
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
||||||
|
: 'text-void-fg-3 hover:text-void-fg-2'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Intelligent
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setWantToUseOption('private');
|
||||||
|
}}
|
||||||
|
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
||||||
|
${wantToUseOption === 'private'
|
||||||
|
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
||||||
|
: 'text-void-fg-3 hover:text-void-fg-2'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Private
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setWantToUseOption('cheap');
|
||||||
|
}}
|
||||||
|
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
||||||
|
${wantToUseOption === 'cheap'
|
||||||
|
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
||||||
|
: 'text-void-fg-3 hover:text-void-fg-2'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Low-Cost
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setWantToUseOption('all')
|
||||||
|
}}
|
||||||
|
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
||||||
|
${wantToUseOption === 'all'
|
||||||
|
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
||||||
|
: 'text-void-fg-3 hover:text-void-fg-2'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Provider Buttons */}
|
||||||
|
<div
|
||||||
|
key={wantToUseOption}
|
||||||
|
className="mb-2 flex flex-wrap items-center w-full"
|
||||||
|
>
|
||||||
|
|
||||||
|
{(wantToUseOption === 'all' ? providerNames : providerNamesOfWantToUseOption[wantToUseOption]).map((providerName) => {
|
||||||
|
const isSelected = selectedProviderName === providerName
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={providerName}
|
||||||
|
onClick={() => setSelectedProviderName(providerName)}
|
||||||
|
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-colors duration-150 border
|
||||||
|
${isSelected ? 'bg-[#0e70c0] text-white shadow-sm border-[#0e70c0]/80' : 'bg-[#0e70c0]/10 text-void-fg-3 hover:bg-[#0e70c0]/30 border-[#0e70c0]/20'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{displayInfoOfProviderName(providerName).title}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="text-left self-start text-sm text-void-fg-3 px-2 py-1">
|
||||||
|
<ChatMarkdownRender string={detailedDescOfWantToUseOption[wantToUseOption]} chatMessageLocation={undefined} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* ModelsTable and ProviderFields */}
|
||||||
|
{selectedProviderName && <div className='mt-4'>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Models Table */}
|
||||||
|
<TableOfModelsForProvider providerName={selectedProviderName} />
|
||||||
|
|
||||||
|
|
||||||
|
{/* Add provider section - simplified styling */}
|
||||||
|
<div className='mb-5 mt-8'>
|
||||||
|
<div className=''>
|
||||||
|
Add {displayInfoOfProviderName(selectedProviderName).title}
|
||||||
|
|
||||||
|
|
||||||
|
{selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedProviderName &&
|
||||||
|
<SettingsForProvider providerName={selectedProviderName} showProviderTitle={false} showProviderSuggestions={false} />
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* Button and status indicators */}
|
||||||
|
{!didFillInProviderSettings ? <p className="text-xs text-void-fg-3 mt-2">Please fill in all fields to continue</p>
|
||||||
|
: !isAtLeastOneModel ? <p className="text-xs text-void-fg-3 mt-2">Please add a model to continue</p>
|
||||||
|
: !isApiKeyLongEnoughIfApiKeyExists ? <p className="text-xs text-void-fg-3 mt-2">Please enter a valid API key</p>
|
||||||
|
: <div className="mt-2"><AnimatedCheckmarkButton text='Added' /></div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom={
|
||||||
|
prevAndNextButtons
|
||||||
|
}
|
||||||
|
|
||||||
|
/>,
|
||||||
|
|
||||||
|
// 2.5: <div className="max-w-[600px] w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
||||||
|
// <FadeIn>
|
||||||
|
// <div className="text-5xl font-light mb-6 mt-12 text-center">Autocomplete</div>
|
||||||
|
|
||||||
|
// <div className="text-center flex flex-col gap-4 w-full max-w-md mx-auto">
|
||||||
|
// <h4 className="text-void-fg-3 mb-2">Void offers free autocomplete with locally hosted models</h4>
|
||||||
|
// <h4 className="text-void-fg-3 mb-2">[have buttons for Ollama install Qwen2.5coder3b and memory requirements] </h4>
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
// </FadeIn>
|
||||||
|
|
||||||
|
// {prevAndNextButtons}
|
||||||
|
// </div>,
|
||||||
|
3: <OnboardingPageShell
|
||||||
|
top={
|
||||||
|
<div>
|
||||||
|
<div className="text-5xl font-light text-center">Settings and Themes</div>
|
||||||
|
|
||||||
|
<div className="mt-8 text-center flex flex-col items-center gap-4 w-full max-w-md mx-auto">
|
||||||
|
<h4 className="text-void-fg-3 mb-4">Transfer your settings from an existing editor?</h4>
|
||||||
|
<OneClickSwitchButton className='w-full px-4 py-2' fromEditor="VS Code" />
|
||||||
|
<OneClickSwitchButton className='w-full px-4 py-2' fromEditor="Cursor" />
|
||||||
|
<OneClickSwitchButton className='w-full px-4 py-2' fromEditor="Windsurf" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
bottom={prevAndNextButtons}
|
||||||
|
/>,
|
||||||
|
4: <OnboardingPageShell
|
||||||
|
top={
|
||||||
|
<div className="text-5xl font-light text-center">Jump in</div>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className="text-center"
|
||||||
|
onClick={() => {
|
||||||
|
// TODO make a fadeout effect
|
||||||
|
voidSettingsService.setGlobalSetting('isOnboardingComplete', true)
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
Enter the Void
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
bottom={
|
||||||
|
<PreviousButton
|
||||||
|
onClick={() => { setPageIndex(pageIndex - 1) }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <div key={pageIndex} className="w-full h-full text-left mx-auto overflow-y-auto flex flex-col items-center justify-around">
|
||||||
|
{contentOfIdx[pageIndex]}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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 { VoidOnboarding } from './VoidOnboarding.js'
|
||||||
|
|
||||||
|
export const mountVoidOnboarding = mountFnGenerator(VoidOnboarding)
|
||||||
|
|
@ -3,22 +3,19 @@
|
||||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||||
*--------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
|
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames } from '../../../../common/voidSettingsTypes.js'
|
||||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames } from '../../../../common/voidSettingsTypes.js'
|
|
||||||
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
|
||||||
import { VoidButtonBgDarken, VoidCheckBox, VoidCustomDropdownBox, VoidInputBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js'
|
import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js'
|
||||||
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
|
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
|
||||||
import { X, RefreshCw, Loader2, Check, MoveRight, PlusCircle, MinusCircle, Download, Trash, StopCircle, Square, ExternalLink } from 'lucide-react'
|
import { X, RefreshCw, Loader2, Check, } from 'lucide-react'
|
||||||
import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/common/platform.js'
|
|
||||||
import { URI } from '../../../../../../../base/common/uri.js'
|
import { URI } from '../../../../../../../base/common/uri.js'
|
||||||
import { env } from '../../../../../../../base/common/process.js'
|
import { env } from '../../../../../../../base/common/process.js'
|
||||||
import { ModelDropdown } from './ModelDropdown.js'
|
import { ModelDropdown } from './ModelDropdown.js'
|
||||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
||||||
import { WarningBox } from './WarningBox.js'
|
import { WarningBox } from './WarningBox.js'
|
||||||
import { os } from '../../../../common/helpers/systemInfo.js'
|
import { os } from '../../../../common/helpers/systemInfo.js'
|
||||||
import { IconLoading, IconX } from '../sidebar-tsx/SidebarChat.js'
|
import { IconLoading } from '../sidebar-tsx/SidebarChat.js'
|
||||||
import { getModelCapabilities, getProviderCapabilities, ollamaRecommendedModels, VoidStaticModelInfo } from '../../../../common/modelCapabilities.js'
|
|
||||||
|
|
||||||
|
|
||||||
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
||||||
|
|
@ -99,7 +96,7 @@ const RefreshableModels = () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const AnimatedCheckmarkButton = ({ text, className }: { text?: string, className?: string }) => {
|
export const AnimatedCheckmarkButton = ({ text, className }: { text?: string, className?: string }) => {
|
||||||
const [dashOffset, setDashOffset] = useState(40);
|
const [dashOffset, setDashOffset] = useState(40);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -157,7 +154,7 @@ const AddButton = ({ disabled, text = 'Add', ...props }: { disabled?: boolean, t
|
||||||
|
|
||||||
|
|
||||||
// shows a providerName dropdown if no `providerName` is given
|
// shows a providerName dropdown if no `providerName` is given
|
||||||
const AddModelInputBox = ({ providerName: permanentProviderName, className, compact }: { providerName?: ProviderName, className?: string, compact?: boolean }) => {
|
export const AddModelInputBox = ({ providerName: permanentProviderName, className, compact }: { providerName?: ProviderName, className?: string, compact?: boolean }) => {
|
||||||
|
|
||||||
const accessor = useAccessor()
|
const accessor = useAccessor()
|
||||||
const settingsStateService = accessor.get('IVoidSettingsService')
|
const settingsStateService = accessor.get('IVoidSettingsService')
|
||||||
|
|
@ -165,6 +162,7 @@ const AddModelInputBox = ({ providerName: permanentProviderName, className, comp
|
||||||
const settingsState = useSettingsState()
|
const settingsState = useSettingsState()
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [showCheckmark, setShowCheckmark] = useState(false)
|
||||||
|
|
||||||
// const providerNameRef = useRef<ProviderName | null>(null)
|
// const providerNameRef = useRef<ProviderName | null>(null)
|
||||||
const [userChosenProviderName, setUserChosenProviderName] = useState<ProviderName>('anthropic')
|
const [userChosenProviderName, setUserChosenProviderName] = useState<ProviderName>('anthropic')
|
||||||
|
|
@ -176,6 +174,10 @@ const AddModelInputBox = ({ providerName: permanentProviderName, className, comp
|
||||||
|
|
||||||
const numModels = settingsState.settingsOfProvider[providerName].models.length
|
const numModels = settingsState.settingsOfProvider[providerName].models.length
|
||||||
|
|
||||||
|
if (showCheckmark) {
|
||||||
|
return <AnimatedCheckmarkButton text='Added' className={`bg-[#0e70c0] text-white px-3 py-1 rounded-sm ${className}`} />
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return <div
|
return <div
|
||||||
className={`text-void-fg-4 flex flex-nowrap text-nowrap items-center hover:brightness-110 cursor-pointer ${className}`}
|
className={`text-void-fg-4 flex flex-nowrap text-nowrap items-center hover:brightness-110 cursor-pointer ${className}`}
|
||||||
|
|
@ -240,7 +242,11 @@ const AddModelInputBox = ({ providerName: permanentProviderName, className, comp
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsStateService.addModel(providerName, modelName)
|
settingsStateService.addModel(providerName, modelName)
|
||||||
setIsOpen(false)
|
setShowCheckmark(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowCheckmark(false)
|
||||||
|
setIsOpen(false)
|
||||||
|
}, 1500)
|
||||||
setErrorString('')
|
setErrorString('')
|
||||||
setModelName('')
|
setModelName('')
|
||||||
}}
|
}}
|
||||||
|
|
@ -284,7 +290,16 @@ export const ModelDump = () => {
|
||||||
|
|
||||||
const isNewProviderName = (i > 0 ? modelDump[i - 1] : undefined)?.providerName !== providerName
|
const isNewProviderName = (i > 0 ? modelDump[i - 1] : undefined)?.providerName !== providerName
|
||||||
|
|
||||||
|
const providerTitle = displayInfoOfProviderName(providerName).title
|
||||||
|
|
||||||
const disabled = !providerEnabled
|
const disabled = !providerEnabled
|
||||||
|
const value = disabled ? false : !isHidden
|
||||||
|
|
||||||
|
const tooltipName = (
|
||||||
|
disabled ? `Add ${providerTitle} to enable`
|
||||||
|
: value === true ? 'Enabled'
|
||||||
|
: 'Disabled'
|
||||||
|
)
|
||||||
|
|
||||||
return <div key={`${modelName}${providerName}`}
|
return <div key={`${modelName}${providerName}`}
|
||||||
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate
|
className={`flex items-center justify-between gap-4 hover:bg-black/10 dark:hover:bg-gray-300/10 py-1 px-3 rounded-sm overflow-hidden cursor-default truncate
|
||||||
|
|
@ -292,7 +307,7 @@ export const ModelDump = () => {
|
||||||
>
|
>
|
||||||
{/* left part is width:full */}
|
{/* left part is width:full */}
|
||||||
<div className={`flex-grow flex items-center gap-4`}>
|
<div className={`flex-grow flex items-center gap-4`}>
|
||||||
<span className='w-full max-w-32'>{isNewProviderName ? displayInfoOfProviderName(providerName).title : ''}</span>
|
<span className='w-full max-w-32'>{isNewProviderName ? providerTitle : ''}</span>
|
||||||
<span className='w-fit truncate'>{modelName}</span>
|
<span className='w-fit truncate'>{modelName}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* right part is anything that fits */}
|
{/* right part is anything that fits */}
|
||||||
|
|
@ -307,10 +322,14 @@ export const ModelDump = () => {
|
||||||
<span className='opacity-50 truncate'>{isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'}</span>
|
<span className='opacity-50 truncate'>{isAutodetected ? '(detected locally)' : isDefault ? '' : '(custom model)'}</span>
|
||||||
|
|
||||||
<VoidSwitch
|
<VoidSwitch
|
||||||
value={disabled ? false : !isHidden}
|
value={value}
|
||||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
size='sm'
|
size='sm'
|
||||||
|
|
||||||
|
data-tooltip-id='void-tooltip'
|
||||||
|
data-tooltip-place='right'
|
||||||
|
data-tooltip-content={tooltipName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={`w-5 flex items-center justify-center`}>
|
<div className={`w-5 flex items-center justify-center`}>
|
||||||
|
|
@ -405,7 +424,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
||||||
// </div >
|
// </div >
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const SettingsForProvider = ({ providerName, showProviderTitle, showProviderSuggestions }: { providerName: ProviderName, showProviderTitle: boolean, showProviderSuggestions: boolean }) => {
|
export const SettingsForProvider = ({ providerName, showProviderTitle, showProviderSuggestions }: { providerName: ProviderName, showProviderTitle: boolean, showProviderSuggestions: boolean }) => {
|
||||||
const voidSettingsState = useSettingsState()
|
const voidSettingsState = useSettingsState()
|
||||||
|
|
||||||
const needsModel = isProviderNameDisabled(providerName, voidSettingsState) === 'addModel'
|
const needsModel = isProviderNameDisabled(providerName, voidSettingsState) === 'addModel'
|
||||||
|
|
@ -527,6 +546,29 @@ const FastApplyMethodDropdown = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const ollamaSetupInstructions = <div className='prose-p:my-0 prose-ol:list-decimal prose-p:py-0 prose-ol:my-0 prose-ol:py-0 prose-span:my-0 prose-span:py-0 text-void-fg-3 text-sm list-decimal select-text'>
|
||||||
|
<div className=''><ChatMarkdownRender string={`Ollama Setup Instructions`} chatMessageLocation={undefined} /></div>
|
||||||
|
<div className=' pl-6'><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} chatMessageLocation={undefined} /></div>
|
||||||
|
<div className=' pl-6'><ChatMarkdownRender string={`2. Open your terminal.`} chatMessageLocation={undefined} /></div>
|
||||||
|
<div className=' pl-6'><ChatMarkdownRender string={`3. Run \`ollama pull your_model\` to install a model.`} chatMessageLocation={undefined} /></div>
|
||||||
|
<div className=' pl-6'><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} chatMessageLocation={undefined} /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
const RedoOnboardingButton = ({ className }: { className?: string }) => {
|
||||||
|
const accessor = useAccessor()
|
||||||
|
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||||
|
return <div
|
||||||
|
className={`text-void-fg-4 flex flex-nowrap text-nowrap items-center hover:brightness-110 cursor-pointer ${className}`}
|
||||||
|
onClick={() => { voidSettingsService.setGlobalSetting('isOnboardingComplete', false) }}
|
||||||
|
>
|
||||||
|
See onboarding screen?
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const FeaturesTab = () => {
|
export const FeaturesTab = () => {
|
||||||
const voidSettingsState = useSettingsState()
|
const voidSettingsState = useSettingsState()
|
||||||
const accessor = useAccessor()
|
const accessor = useAccessor()
|
||||||
|
|
@ -537,7 +579,8 @@ export const FeaturesTab = () => {
|
||||||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ModelDump />
|
<ModelDump />
|
||||||
<AddModelInputBox className='my-4' compact />
|
<AddModelInputBox className='mt-4' compact />
|
||||||
|
<RedoOnboardingButton className='mt-2 mb-4' />
|
||||||
<AutoDetectLocalModelsToggle />
|
<AutoDetectLocalModelsToggle />
|
||||||
<RefreshableModels />
|
<RefreshableModels />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
@ -822,7 +865,7 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }: { fromEditor?: TransferEditorType, className?: string }) => {
|
export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }: { fromEditor?: TransferEditorType, className?: string }) => {
|
||||||
const accessor = useAccessor()
|
const accessor = useAccessor()
|
||||||
const fileService = accessor.get('IFileService')
|
const fileService = accessor.get('IFileService')
|
||||||
|
|
||||||
|
|
@ -959,14 +1002,6 @@ export const Settings = () => {
|
||||||
|
|
||||||
const [tab, setTab] = useState<TabName>('models')
|
const [tab, setTab] = useState<TabName>('models')
|
||||||
|
|
||||||
|
|
||||||
const deleteme = true
|
|
||||||
if (deleteme) {
|
|
||||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ width: '100%', height: '100%' }}>
|
|
||||||
<VoidOnboarding />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ height: '100%', width: '100%' }}>
|
return <div className={`@@void-scope ${isDark ? 'dark' : ''}`} style={{ height: '100%', width: '100%' }}>
|
||||||
<div className='overflow-y-auto w-full h-full px-10 py-10 select-none'>
|
<div className='overflow-y-auto w-full h-full px-10 py-10 select-none'>
|
||||||
|
|
||||||
|
|
@ -1013,669 +1048,3 @@ export const Settings = () => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const FADE_DURATION_MS = 2000
|
|
||||||
|
|
||||||
|
|
||||||
const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: React.ReactNode, delayMs?: number, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
|
|
||||||
const [opacity, setOpacity] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
setOpacity(1)
|
|
||||||
}, delayMs)
|
|
||||||
|
|
||||||
return () => clearTimeout(timeout)
|
|
||||||
}, [setOpacity, delayMs])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className} style={{ opacity, transition: `opacity ${FADE_DURATION_MS}ms ease-in-out` }} {...props}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Onboarding
|
|
||||||
// OnboardingPage
|
|
||||||
// title:
|
|
||||||
// div
|
|
||||||
// "Welcome to Void"
|
|
||||||
// image
|
|
||||||
// content:<></>
|
|
||||||
// title
|
|
||||||
// content
|
|
||||||
// prev/next
|
|
||||||
|
|
||||||
// OnboardingPage
|
|
||||||
// title:
|
|
||||||
// div
|
|
||||||
// "How would you like to use Void?"
|
|
||||||
// content:
|
|
||||||
// ModelQuestionContent
|
|
||||||
// |
|
|
||||||
// div
|
|
||||||
// "I want to:"
|
|
||||||
// div
|
|
||||||
// "Use the smartest models"
|
|
||||||
// "Keep my data fully private"
|
|
||||||
// "Save money"
|
|
||||||
// "I don't know"
|
|
||||||
// | div
|
|
||||||
// | div
|
|
||||||
// "We recommend using "
|
|
||||||
// "Set API"
|
|
||||||
// | div
|
|
||||||
// ""
|
|
||||||
// | div
|
|
||||||
//
|
|
||||||
// title
|
|
||||||
// content
|
|
||||||
// prev/next
|
|
||||||
//
|
|
||||||
// OnboardingPage
|
|
||||||
// title
|
|
||||||
// content
|
|
||||||
// prev/next
|
|
||||||
|
|
||||||
|
|
||||||
const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={onClick}
|
|
||||||
className="px-6 py-2 rounded bg-void-accent hover:bg-void-accent/90 text-white"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const SkipButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={onClick}
|
|
||||||
className="px-6 py-2 rounded bg-void-bg-2 hover:bg-void-bg-3 text-void-fg-2"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
Skip
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreviousButton = ({ onClick, ...props }: { onClick: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={onClick}
|
|
||||||
className="px-6 py-2 rounded bg-void-bg-2 hover:bg-void-bg-3"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const ollamaSetupInstructions = <div className='prose-p:my-0 prose-ol:list-decimal prose-p:py-0 prose-ol:my-0 prose-ol:py-0 prose-span:my-0 prose-span:py-0 text-void-fg-3 text-sm list-decimal select-text'>
|
|
||||||
<div className=''><ChatMarkdownRender string={`Ollama Setup Instructions`} chatMessageLocation={undefined} /></div>
|
|
||||||
<div className=' pl-6'><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} chatMessageLocation={undefined} /></div>
|
|
||||||
<div className=' pl-6'><ChatMarkdownRender string={`2. Open your terminal.`} chatMessageLocation={undefined} /></div>
|
|
||||||
<div className=' pl-6'><ChatMarkdownRender string={`3. Run \`ollama pull your_model\` to install a model.`} chatMessageLocation={undefined} /></div>
|
|
||||||
<div className=' pl-6'><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} chatMessageLocation={undefined} /></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
const OllamaDownloadOrRemoveModelButton = ({ modelName, isModelInstalled, sizeGb }: { modelName: string, isModelInstalled: boolean, sizeGb: number | false | 'not-known' }) => {
|
|
||||||
|
|
||||||
|
|
||||||
// for now just link to the ollama download page
|
|
||||||
return <a
|
|
||||||
href={`https://ollama.com/library/${modelName}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center text-void-fg-2 hover:text-void-fg-1"
|
|
||||||
>
|
|
||||||
<ExternalLink className="w-3.5 h-3.5" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
// if (isModelInstalled) {
|
|
||||||
// return <div className="flex items-center">
|
|
||||||
|
|
||||||
// <span className="flex items-center">Uninstall</span>
|
|
||||||
|
|
||||||
// <IconShell1
|
|
||||||
// className="ml-1"
|
|
||||||
// Icon={Trash}
|
|
||||||
// onClick={() => {
|
|
||||||
|
|
||||||
// setIsModelInstalling(false);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// </div>
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// else if (isModelInstalling) {
|
|
||||||
// return <div className="flex items-center">
|
|
||||||
|
|
||||||
// <span className="flex items-center">{`Download? ${typeof sizeGb === 'number' ? `(${sizeGb} Gb)` : ''}`}</span>
|
|
||||||
|
|
||||||
// <IconShell1
|
|
||||||
// className="ml-1"
|
|
||||||
// Icon={Square}
|
|
||||||
// onClick={() => {
|
|
||||||
// // abort()
|
|
||||||
|
|
||||||
// // TODO!!!!!!!!!!! don't do this
|
|
||||||
// setIsModelInstalling(false);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// </div>
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// else if (!isModelInstalled) {
|
|
||||||
|
|
||||||
// return <div className="flex items-center">
|
|
||||||
|
|
||||||
// <span className="flex items-center">Download ({sizeGb} Gb)</span>
|
|
||||||
|
|
||||||
// <IconShell1
|
|
||||||
// className="ml-1"
|
|
||||||
// Icon={Download}
|
|
||||||
// onClick={() => {
|
|
||||||
// // this is a check for whether the model was installed:
|
|
||||||
|
|
||||||
// if (isModelInstalling) return
|
|
||||||
|
|
||||||
|
|
||||||
// // TODO!!!!!! don't do this
|
|
||||||
|
|
||||||
|
|
||||||
// // install(modelname), callback = setIsModelInstalling(false);
|
|
||||||
|
|
||||||
// setIsModelInstalling(true);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return <></>
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const YesNoText = ({ val }: { val: boolean | null }) => {
|
|
||||||
|
|
||||||
return <div
|
|
||||||
className={
|
|
||||||
val === true ? "text text-green-500"
|
|
||||||
: val === false ? 'text-red-500'
|
|
||||||
: "text text-yellow-500"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
val === true ? "Yes"
|
|
||||||
: val === false ? 'No'
|
|
||||||
: "Yes*"
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const abbreviateNumber = (num: number): string => {
|
|
||||||
if (num >= 1000000) {
|
|
||||||
// For millions
|
|
||||||
return Math.floor(num / 1000000) + 'M';
|
|
||||||
} else if (num >= 1000) {
|
|
||||||
// For thousands
|
|
||||||
return Math.floor(num / 1000) + 'K';
|
|
||||||
} else {
|
|
||||||
// For numbers less than 1000
|
|
||||||
return num.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName }) => {
|
|
||||||
|
|
||||||
const accessor = useAccessor()
|
|
||||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
|
||||||
const voidSettingsState = useSettingsState()
|
|
||||||
const isDetectableLocally = (refreshableProviderNames as ProviderName[]).includes(providerName)
|
|
||||||
// const providerCapabilities = getProviderCapabilities(providerName)
|
|
||||||
|
|
||||||
|
|
||||||
// info used to show the table
|
|
||||||
const infoOfModelName: Record<string, { showAsDefault: boolean, isDownloaded: boolean }> = {}
|
|
||||||
|
|
||||||
voidSettingsState.settingsOfProvider[providerName].models.forEach(m => {
|
|
||||||
infoOfModelName[m.modelName] = {
|
|
||||||
showAsDefault: m.isDefault,
|
|
||||||
isDownloaded: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// special case columns for ollama; show recommended models as default
|
|
||||||
if (providerName === 'ollama') {
|
|
||||||
for (const modelName of ollamaRecommendedModels) {
|
|
||||||
if (modelName in infoOfModelName) continue
|
|
||||||
infoOfModelName[modelName] = {
|
|
||||||
...infoOfModelName[modelName],
|
|
||||||
showAsDefault: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <table className="table-fixed border-collapse mb-6 bg-void-bg-2 text-sm mx-auto select-text">
|
|
||||||
<thead>
|
|
||||||
<tr className="border-b border-void-border-1 text-nowrap text-ellipsis">
|
|
||||||
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[200px]">Models Offered</th>
|
|
||||||
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Cost/M</th>
|
|
||||||
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Context</th>
|
|
||||||
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Chat</th>
|
|
||||||
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Agent</th>
|
|
||||||
<th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Autotab</th>
|
|
||||||
{/* <th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Reasoning</th> */}
|
|
||||||
{isDetectableLocally && <th className="text-left py-2 px-3 font-normal text-void-fg-3 min-w-[10%]">Detected</th>}
|
|
||||||
{providerName === 'ollama' && <th className="text-left py-2 px-3 font-normal text-void-fg-3">Download</th>}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{Object.keys(infoOfModelName).map(modelName => {
|
|
||||||
const { showAsDefault, isDownloaded } = infoOfModelName[modelName]
|
|
||||||
|
|
||||||
const {
|
|
||||||
downloadable,
|
|
||||||
cost,
|
|
||||||
supportsTools,
|
|
||||||
supportsFIM,
|
|
||||||
reasoningCapabilities,
|
|
||||||
contextWindow,
|
|
||||||
|
|
||||||
isUnrecognizedModel,
|
|
||||||
maxOutputTokens,
|
|
||||||
supportsSystemMessage,
|
|
||||||
} = getModelCapabilities(providerName, modelName)
|
|
||||||
|
|
||||||
|
|
||||||
const removeModelButton = <button
|
|
||||||
className="absolute -left-1 top-1/2 transform -translate-y-1/2 -translate-x-full text-void-fg-3 hover:text-void-fg-1 text-xs"
|
|
||||||
onClick={() => voidSettingsService.deleteModel(providerName, modelName)}
|
|
||||||
>
|
|
||||||
<X className="w-3.5 h-3.5" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={modelName} className="border-b border-void-border-1 hover:bg-void-bg-3/50">
|
|
||||||
<td className="py-2 px-3 relative">
|
|
||||||
{!showAsDefault && removeModelButton}
|
|
||||||
{modelName}
|
|
||||||
</td>
|
|
||||||
<td className="py-2 px-3">${cost.output ?? ''}</td>
|
|
||||||
<td className="py-2 px-3">{contextWindow ? abbreviateNumber(contextWindow) : ''}</td>
|
|
||||||
<td className="py-2 px-3"><YesNoText val={true} /></td>
|
|
||||||
<td className="py-2 px-3"><YesNoText val={!!supportsTools || null} /></td>
|
|
||||||
<td className="py-2 px-3"><YesNoText val={!!supportsFIM} /></td>
|
|
||||||
{/* <td className="py-2 px-3"><YesNoText val={!!reasoningCapabilities} /></td> */}
|
|
||||||
{isDetectableLocally && <td className="py-2 px-3">{!!isDownloaded ? <Check className="w-4 h-4" /> : <></>}</td>}
|
|
||||||
{providerName === 'ollama' && <th className="py-2 px-3">
|
|
||||||
<OllamaDownloadOrRemoveModelButton modelName={modelName} isModelInstalled={infoOfModelName[modelName].isDownloaded} sizeGb={downloadable && downloadable.sizeGb} />
|
|
||||||
</th>}
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
<tr className="hover:bg-void-bg-3/50">
|
|
||||||
<td className="py-2 px-3 text-void-accent">
|
|
||||||
<AddModelInputBox
|
|
||||||
key={providerName}
|
|
||||||
providerName={providerName}
|
|
||||||
compact={true} />
|
|
||||||
</td>
|
|
||||||
<td colSpan={4}></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type WantToUseOption = 'smart' | 'private' | 'cheap' | 'all'
|
|
||||||
|
|
||||||
const VoidOnboarding = () => {
|
|
||||||
|
|
||||||
const accessor = useAccessor()
|
|
||||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
|
||||||
|
|
||||||
const voidSettingsState = useSettingsState()
|
|
||||||
const isOnboardingComplete = false // voidSettingsService._isOnboardingComplete
|
|
||||||
|
|
||||||
if (isOnboardingComplete) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const [pageIndex, setPageIndex] = useState(0)
|
|
||||||
|
|
||||||
|
|
||||||
const skipButton = <SkipButton onClick={() => { setPageIndex(pageIndex + 1) }} />
|
|
||||||
|
|
||||||
|
|
||||||
// page 1 state
|
|
||||||
const [wantToUseOption, setWantToUseOption] = useState<WantToUseOption>('smart')
|
|
||||||
|
|
||||||
// page 2 state
|
|
||||||
const [selectedProviderName, setSelectedProviderName] = useState<ProviderName | null>(null)
|
|
||||||
|
|
||||||
const providerNamesOfWantToUseOption: { [wantToUseOption in WantToUseOption]: ProviderName[] } = {
|
|
||||||
smart: ['anthropic', 'openAI', 'gemini', 'openRouter'],
|
|
||||||
private: ['ollama', 'vLLM', 'openAICompatible'],
|
|
||||||
cheap: ['gemini', 'deepseek', 'openRouter', 'ollama', 'vLLM'],
|
|
||||||
all: providerNames,
|
|
||||||
// TODO allow user to redo onboarding
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const didFillInProviderSettings = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName]._didFillInProviderSettings
|
|
||||||
const isApiKeyLongEnoughIfApiKeyExists = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].apiKey ? voidSettingsState.settingsOfProvider[selectedProviderName].apiKey.length > 15 : true
|
|
||||||
const isAtLeastOneModel = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].models.length >= 1
|
|
||||||
|
|
||||||
const didFillInSelectedProviderSettings = !!(didFillInProviderSettings && isApiKeyLongEnoughIfApiKeyExists && isAtLeastOneModel)
|
|
||||||
|
|
||||||
const prevAndNextButtons = <div className="self-end flex items-center gap-1 pb-8">
|
|
||||||
<PreviousButton
|
|
||||||
onClick={() => { setPageIndex(pageIndex - 1) }}
|
|
||||||
/>
|
|
||||||
<NextButton
|
|
||||||
onClick={() => { setPageIndex(pageIndex + 1) }}
|
|
||||||
disabled={pageIndex === 2 && !didFillInSelectedProviderSettings}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
// cannot be md
|
|
||||||
const basicDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
|
|
||||||
smart: "Models with the best performance on benchmarks.",
|
|
||||||
private: "Fully private and hosted on your computer/network.",
|
|
||||||
cheap: "Free and affordable options.",
|
|
||||||
all: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// can be md
|
|
||||||
const detailedDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = {
|
|
||||||
smart: "Most intelligent and best for agent mode.",
|
|
||||||
private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor.com) for help setting up at your company.",
|
|
||||||
cheap: "Great deals like Gemini 2.5 Pro or self-host a model for free.",
|
|
||||||
all: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the selected provider name appropriately
|
|
||||||
useEffect(() => {
|
|
||||||
if (wantToUseOption && providerNamesOfWantToUseOption[wantToUseOption].length > 0) {
|
|
||||||
setSelectedProviderName(providerNamesOfWantToUseOption[wantToUseOption][0]);
|
|
||||||
} else {
|
|
||||||
setSelectedProviderName(null);
|
|
||||||
}
|
|
||||||
}, [wantToUseOption]);
|
|
||||||
|
|
||||||
// set wantToUseOption to smart when page changes
|
|
||||||
useEffect(() => {
|
|
||||||
setWantToUseOption(wantToUseOption);
|
|
||||||
}, [pageIndex]);
|
|
||||||
|
|
||||||
|
|
||||||
// TODO add a description next to the skip button saying (you can always restart the onboarding in Settings)
|
|
||||||
const contentOfIdx: { [pageIndex: number]: React.ReactNode } = {
|
|
||||||
0: <div className="max-w-[600px] w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
|
||||||
<FadeIn >
|
|
||||||
<div className="text-5xl font-light mb-6 mt-12 text-center">Welcome to Void</div>
|
|
||||||
|
|
||||||
|
|
||||||
{/* <div className="w-8 h-8 mb-2">
|
|
||||||
<VoidImage className='h-full w-full' />
|
|
||||||
</div> */}
|
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
<FadeIn delayMs={1000} className="text-center pb-8" onClick={() => { setPageIndex(pageIndex + 1) }}>
|
|
||||||
Get Started
|
|
||||||
</FadeIn>
|
|
||||||
</div>,
|
|
||||||
1: <div className="max-w-full w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
|
||||||
|
|
||||||
<FadeIn>
|
|
||||||
|
|
||||||
<div className="text-5xl font-light mb-6 mt-12 text-center">AI Preferences</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col items-center w-full mx-auto">
|
|
||||||
|
|
||||||
<div className="text-base text-void-fg-2 mb-8 text-center">What are you looking for in an AI model?</div>
|
|
||||||
|
|
||||||
<div className="flex md:flex-nowrap gap-4 w-full md:max-w-[80%] max-w-[90%]">
|
|
||||||
<div
|
|
||||||
onClick={() => { setWantToUseOption('smart'); setPageIndex(pageIndex + 1); }}
|
|
||||||
className="flex flex-col items-center w-full justify-center p-6 rounded-md cursor-pointer md:aspect-[8/7] border-void-border-1 border relative overflow-hidden group"
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
|
|
||||||
<span className="text-5xl mb-4 relative z-10">🧠</span>
|
|
||||||
<h3 className="text-xl font-medium mb-3 relative z-10">Intelligence</h3>
|
|
||||||
<p className="text-center text-root text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['smart']}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
onClick={() => { setWantToUseOption('private'); setPageIndex(pageIndex + 1); }}
|
|
||||||
className="flex flex-col items-center w-full justify-center p-6 rounded-md cursor-pointer md:aspect-[8/7] border-void-border-1 border relative overflow-hidden group"
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
|
|
||||||
<span className="text-5xl mb-4 relative z-10">🔒</span>
|
|
||||||
<h3 className="text-xl font-medium mb-3 relative z-10">Privacy</h3>
|
|
||||||
<p className="text-center text-sm text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['private']}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
onClick={() => { setWantToUseOption('cheap'); setPageIndex(pageIndex + 1); }}
|
|
||||||
className="flex flex-col items-center w-full justify-center p-6 rounded-md cursor-pointer md:aspect-[8/7] border-void-border-1 border relative overflow-hidden group"
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/15 via-[#0e70c0]/5 to-transparent dark:from-[#0e70c0]/20 dark:via-[#0e70c0]/10 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-100"></div>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0e70c0]/25 via-[#0e70c0]/10 to-[#0e70c0]/5 dark:from-[#0e70c0]/30 dark:via-[#0e70c0]/15 dark:to-[#0e70c0]/5 transition-opacity duration-300 ease-in-out opacity-0 group-hover:opacity-100"></div>
|
|
||||||
<span className="text-5xl mb-4 relative z-10">💵</span>
|
|
||||||
<h3 className="text-xl font-medium mb-3 relative z-10">Low-Cost</h3>
|
|
||||||
<p className="text-center text-sm text-void-fg-2 relative z-10">{basicDescOfWantToUseOption['cheap']}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
<div className="max-w-[600px] w-full flex flex-col items-center justify-between">
|
|
||||||
{prevAndNextButtons}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>,
|
|
||||||
2: <div className="max-w-[600px] w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
|
||||||
<FadeIn className="flex flex-col gap-2 w-full">
|
|
||||||
|
|
||||||
<div className="text-5xl font-light mb-6 mt-12 text-center">Choose a Provider</div>
|
|
||||||
|
|
||||||
<div className="mx-auto flex items-center overflow-hidden bg-zinc-700/5 dark:bg-zinc-300/5 rounded-md">
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setWantToUseOption('smart');
|
|
||||||
}}
|
|
||||||
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
|
||||||
${wantToUseOption === 'smart'
|
|
||||||
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
|
||||||
: 'text-void-fg-3 hover:text-void-fg-2'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
Intelligent
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setWantToUseOption('private');
|
|
||||||
}}
|
|
||||||
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
|
||||||
${wantToUseOption === 'private'
|
|
||||||
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
|
||||||
: 'text-void-fg-3 hover:text-void-fg-2'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
Private
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setWantToUseOption('cheap');
|
|
||||||
}}
|
|
||||||
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
|
||||||
${wantToUseOption === 'cheap'
|
|
||||||
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
|
||||||
: 'text-void-fg-3 hover:text-void-fg-2'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
Low-Cost
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setWantToUseOption('all')
|
|
||||||
}}
|
|
||||||
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors
|
|
||||||
${wantToUseOption === 'all'
|
|
||||||
? 'bg-zinc-700/10 dark:bg-zinc-300/10 text-white font-medium'
|
|
||||||
: 'text-void-fg-3 hover:text-void-fg-2'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Provider Buttons */}
|
|
||||||
<div
|
|
||||||
key={wantToUseOption}
|
|
||||||
className="flex flex-wrap items-center mt-4 min-h-[37px] w-full"
|
|
||||||
>
|
|
||||||
|
|
||||||
{(wantToUseOption === 'all' ? providerNames : providerNamesOfWantToUseOption[wantToUseOption]).map((providerName) => {
|
|
||||||
const isSelected = selectedProviderName === providerName
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={providerName}
|
|
||||||
onClick={() => setSelectedProviderName(providerName)}
|
|
||||||
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-colors duration-150 border
|
|
||||||
${isSelected ? 'bg-[#0e70c0] text-white shadow-sm border-[#0e70c0]/80' : 'bg-[#0e70c0]/10 text-void-fg-3 hover:bg-[#0e70c0]/30 border-[#0e70c0]/20'}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{displayInfoOfProviderName(providerName).title}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
<div className="text-left text-sm text-void-fg-3 px-2 py-1">
|
|
||||||
|
|
||||||
<div className='pl-4 select-text'>
|
|
||||||
<ChatMarkdownRender string={detailedDescOfWantToUseOption[wantToUseOption]} chatMessageLocation={undefined} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{/* ModelsTable and ProviderFields */}
|
|
||||||
{selectedProviderName && <div className='mt-4'>
|
|
||||||
|
|
||||||
|
|
||||||
{/* Models Table */}
|
|
||||||
<TableOfModelsForProvider providerName={selectedProviderName} />
|
|
||||||
|
|
||||||
|
|
||||||
{/* Add provider section - simplified styling */}
|
|
||||||
<div className='mb-5 mt-8'>
|
|
||||||
<div className='text-base font-semibold'>
|
|
||||||
Add {displayInfoOfProviderName(selectedProviderName).title}
|
|
||||||
|
|
||||||
|
|
||||||
{selectedProviderName === 'ollama' ? ollamaSetupInstructions : ''}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedProviderName &&
|
|
||||||
<SettingsForProvider providerName={selectedProviderName} showProviderTitle={false} showProviderSuggestions={false} />
|
|
||||||
}
|
|
||||||
|
|
||||||
{/* Button and status indicators */}
|
|
||||||
{!didFillInProviderSettings ? <p className="text-xs text-void-fg-3 mt-2">Please fill in all fields to continue</p>
|
|
||||||
: !isAtLeastOneModel ? <p className="text-xs text-void-fg-3 mt-2">Please add a model to continue</p>
|
|
||||||
: !isApiKeyLongEnoughIfApiKeyExists ? <p className="text-xs text-void-fg-3 mt-2">Please enter a valid API key</p>
|
|
||||||
: <div className="mt-2"><AnimatedCheckmarkButton text='Added' /></div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
{prevAndNextButtons}
|
|
||||||
</div>,
|
|
||||||
// 2.5: <div className="max-w-[600px] w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
|
||||||
// <FadeIn>
|
|
||||||
// <div className="text-5xl font-light mb-6 mt-12 text-center">Autocomplete</div>
|
|
||||||
|
|
||||||
// <div className="text-center flex flex-col gap-4 w-full max-w-md mx-auto">
|
|
||||||
// <h4 className="text-void-fg-3 mb-2">Void offers free autocomplete with locally hosted models</h4>
|
|
||||||
// <h4 className="text-void-fg-3 mb-2">[have buttons for Ollama install Qwen2.5coder3b and memory requirements] </h4>
|
|
||||||
|
|
||||||
// </div>
|
|
||||||
// </FadeIn>
|
|
||||||
|
|
||||||
// {prevAndNextButtons}
|
|
||||||
// </div>,
|
|
||||||
3: <div className="max-w-[600px] w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
|
||||||
<FadeIn>
|
|
||||||
<div className="text-5xl font-light mb-6 mt-12">Settings and Themes</div>
|
|
||||||
|
|
||||||
<div className="text-center flex flex-col items-center gap-4 w-full max-w-md mx-auto">
|
|
||||||
<h4 className="text-void-fg-3 mb-4">Transfer your settings from an existing editor?</h4>
|
|
||||||
<OneClickSwitchButton className='w-full' fromEditor="VS Code" />
|
|
||||||
<OneClickSwitchButton className='w-full' fromEditor="Cursor" />
|
|
||||||
<OneClickSwitchButton className='w-full' fromEditor="Windsurf" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
{prevAndNextButtons}
|
|
||||||
</div>,
|
|
||||||
4: <div className="max-w-[600px] w-full h-full text-left mx-auto flex flex-col items-center justify-between">
|
|
||||||
<FadeIn className="text-5xl font-light mb-6 mt-12">
|
|
||||||
Jump in
|
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
<FadeIn className="text-center">
|
|
||||||
Enter the Void
|
|
||||||
</FadeIn>
|
|
||||||
|
|
||||||
{prevAndNextButtons}
|
|
||||||
</div>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return <div key={pageIndex} className="w-full h-full text-left mx-auto overflow-y-auto flex flex-col items-center justify-around">
|
|
||||||
{contentOfIdx[pageIndex]}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const VoidTooltip = () => {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 0px 8px;
|
padding: 0px 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
z-index: 999;
|
z-index: 999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
#void-tooltip {
|
#void-tooltip {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export default defineConfig({
|
||||||
'./src2/sidebar-tsx/index.tsx',
|
'./src2/sidebar-tsx/index.tsx',
|
||||||
'./src2/void-settings-tsx/index.tsx',
|
'./src2/void-settings-tsx/index.tsx',
|
||||||
'./src2/void-tooltip/index.tsx',
|
'./src2/void-tooltip/index.tsx',
|
||||||
|
'./src2/void-onboarding/index.tsx',
|
||||||
'./src2/quick-edit-tsx/index.tsx',
|
'./src2/quick-edit-tsx/index.tsx',
|
||||||
'./src2/diff/index.tsx',
|
'./src2/diff/index.tsx',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,14 @@ export class ToolsService implements IToolsService {
|
||||||
return { queryStr, searchInFolder, isRegex, pageNumber }
|
return { queryStr, searchInFolder, isRegex, pageNumber }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
read_lint_errors: (params: RawToolParamsObj) => {
|
||||||
|
const {
|
||||||
|
uri: uriUnknown,
|
||||||
|
} = params
|
||||||
|
const uri = validateURI(uriUnknown)
|
||||||
|
return { uri }
|
||||||
|
},
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
create_file_or_folder: (params: RawToolParamsObj) => {
|
create_file_or_folder: (params: RawToolParamsObj) => {
|
||||||
|
|
@ -322,6 +330,12 @@ export class ToolsService implements IToolsService {
|
||||||
return { result: { queryStr, uris, hasNextPage } }
|
return { result: { queryStr, uris, hasNextPage } }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
read_lint_errors: async ({ uri }) => {
|
||||||
|
await timeout(1000)
|
||||||
|
const { lintErrors } = this._getLintErrors(uri)
|
||||||
|
return { result: { lintErrors } }
|
||||||
|
},
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
create_file_or_folder: async ({ uri, isFolder }) => {
|
create_file_or_folder: async ({ uri, isFolder }) => {
|
||||||
|
|
@ -359,20 +373,11 @@ export class ToolsService implements IToolsService {
|
||||||
editCodeService.interruptURIStreaming({ uri: diffZoneURI })
|
editCodeService.interruptURIStreaming({ uri: diffZoneURI })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// at end, get lint errors
|
||||||
const lintErrorsPromise = applyDonePromise.then(async () => {
|
const lintErrorsPromise = applyDonePromise.then(async () => {
|
||||||
await timeout(500)
|
await timeout(2000)
|
||||||
|
const { lintErrors } = this._getLintErrors(uri)
|
||||||
const lintErrors = this.markerService
|
return { lintErrors }
|
||||||
.read({ resource: uri })
|
|
||||||
.map(l => ({
|
|
||||||
code: typeof l.code === 'string' ? l.code : l.code?.value || '',
|
|
||||||
message: l.message,
|
|
||||||
startLineNumber: l.startLineNumber,
|
|
||||||
endLineNumber: l.endLineNumber,
|
|
||||||
} satisfies LintErrorItem))
|
|
||||||
|
|
||||||
if (!lintErrors.length) return { lintErrors: null }
|
|
||||||
return { lintErrors, }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { result: lintErrorsPromise, interruptTool }
|
return { result: lintErrorsPromise, interruptTool }
|
||||||
|
|
@ -386,7 +391,11 @@ export class ToolsService implements IToolsService {
|
||||||
|
|
||||||
const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : ''
|
const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : ''
|
||||||
|
|
||||||
const lintErrorsStr = (lintErrors: LintErrorItem[]) => lintErrors.map((e, i) => `Error ${i + 1}:\nLines Affected: ${e.startLineNumber}-${e.endLineNumber}\nError message:${e.message}`).join('\n\n')
|
const stringifyLintErrors = (lintErrors: LintErrorItem[]) => {
|
||||||
|
return lintErrors
|
||||||
|
.map((e, i) => `Error ${i + 1}:\nLines Affected: ${e.startLineNumber}-${e.endLineNumber}\nError message:${e.message}`)
|
||||||
|
.join('\n\n')
|
||||||
|
}
|
||||||
|
|
||||||
// given to the LLM after the call
|
// given to the LLM after the call
|
||||||
this.stringOfResult = {
|
this.stringOfResult = {
|
||||||
|
|
@ -406,6 +415,11 @@ export class ToolsService implements IToolsService {
|
||||||
search_files: (params, result) => {
|
search_files: (params, result) => {
|
||||||
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)
|
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)
|
||||||
},
|
},
|
||||||
|
read_lint_errors: (params, result) => {
|
||||||
|
return result.lintErrors ?
|
||||||
|
stringifyLintErrors(result.lintErrors)
|
||||||
|
: 'No lint errors found.'
|
||||||
|
},
|
||||||
// ---
|
// ---
|
||||||
create_file_or_folder: (params, result) => {
|
create_file_or_folder: (params, result) => {
|
||||||
return `URI ${params.uri.fsPath} successfully created.`
|
return `URI ${params.uri.fsPath} successfully created.`
|
||||||
|
|
@ -416,7 +430,7 @@ export class ToolsService implements IToolsService {
|
||||||
edit_file: (params, result) => {
|
edit_file: (params, result) => {
|
||||||
const lintErrsString = (
|
const lintErrsString = (
|
||||||
this.voidSettingsService.state.globalSettings.includeToolLintErrors ?
|
this.voidSettingsService.state.globalSettings.includeToolLintErrors ?
|
||||||
(result.lintErrors ? ` Lint errors found after change:\n${lintErrorsStr(result.lintErrors)}.\nIf this is related to a change made while calling this tool, you might want to fix the error.`
|
(result.lintErrors ? ` Lint errors found after change:\n${stringifyLintErrors(result.lintErrors)}.\nIf this is related to a change made while calling this tool, you might want to fix the error.`
|
||||||
: ` No lint errors found.`)
|
: ` No lint errors found.`)
|
||||||
: '')
|
: '')
|
||||||
|
|
||||||
|
|
@ -454,6 +468,21 @@ export class ToolsService implements IToolsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private _getLintErrors(uri: URI): { lintErrors: LintErrorItem[] | null } {
|
||||||
|
const lintErrors = this.markerService
|
||||||
|
.read({ resource: uri })
|
||||||
|
.map(l => ({
|
||||||
|
code: typeof l.code === 'string' ? l.code : l.code?.value || '',
|
||||||
|
message: l.message,
|
||||||
|
startLineNumber: l.startLineNumber,
|
||||||
|
endLineNumber: l.endLineNumber,
|
||||||
|
} satisfies LintErrorItem))
|
||||||
|
|
||||||
|
if (!lintErrors.length) return { lintErrors: null }
|
||||||
|
return { lintErrors, }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSingleton(IToolsService, ToolsService, InstantiationType.Eager);
|
registerSingleton(IToolsService, ToolsService, InstantiationType.Eager);
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ import './voidSelectionHelperWidget.js'
|
||||||
// register tooltip service
|
// register tooltip service
|
||||||
import './tooltipService.js'
|
import './tooltipService.js'
|
||||||
|
|
||||||
|
// register onboarding service
|
||||||
|
import './voidOnboardingService.js'
|
||||||
|
|
||||||
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
|
// ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ----------
|
||||||
|
|
||||||
// llmMessage
|
// llmMessage
|
||||||
|
|
|
||||||
|
|
@ -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 { mountVoidOnboarding } from './react/out/void-onboarding/index.js'
|
||||||
|
import { h, getActiveWindow } from '../../../../base/browser/dom.js';
|
||||||
|
|
||||||
|
// Onboarding contribution that mounts the component at startup
|
||||||
|
export class OnboardingContribution extends Disposable implements IWorkbenchContribution {
|
||||||
|
static readonly ID = 'workbench.contrib.voidOnboarding';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize(): 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) {
|
||||||
|
|
||||||
|
const onboardingContainer = h('div.void-onboarding-container').root;
|
||||||
|
workbench.appendChild(onboardingContainer);
|
||||||
|
|
||||||
|
// Mount the React component
|
||||||
|
this.instantiationService.invokeFunction((accessor: ServicesAccessor) => {
|
||||||
|
const result = mountVoidOnboarding(onboardingContainer, accessor);
|
||||||
|
if (result && typeof result.dispose === 'function') {
|
||||||
|
this._register(toDisposable(result.dispose));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register cleanup for the DOM element
|
||||||
|
this._register(toDisposable(() => {
|
||||||
|
if (onboardingContainer.parentElement) {
|
||||||
|
onboardingContainer.parentElement.removeChild(onboardingContainer);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the contribution to be initialized during the AfterRestored phase
|
||||||
|
registerWorkbenchContribution2(OnboardingContribution.ID, OnboardingContribution, WorkbenchPhase.AfterRestored);
|
||||||
|
|
@ -78,15 +78,17 @@ export const defaultModelsOfProvider = {
|
||||||
vLLM: [ // autodetected
|
vLLM: [ // autodetected
|
||||||
],
|
],
|
||||||
openRouter: [ // https://openrouter.ai/models
|
openRouter: [ // https://openrouter.ai/models
|
||||||
'anthropic/claude-3.7-sonnet:thinking',
|
// 'anthropic/claude-3.7-sonnet:thinking',
|
||||||
'anthropic/claude-3.7-sonnet',
|
'anthropic/claude-3.7-sonnet',
|
||||||
'anthropic/claude-3.5-sonnet',
|
'anthropic/claude-3.5-sonnet',
|
||||||
'deepseek/deepseek-r1',
|
'deepseek/deepseek-r1',
|
||||||
'deepseek/deepseek-r1-zero:free',
|
'deepseek/deepseek-r1-zero:free',
|
||||||
'mistralai/codestral-2501',
|
'openrouter/quasar-alpha',
|
||||||
'qwen/qwen-2.5-coder-32b-instruct',
|
'google/gemini-2.5-pro-preview-03-25',
|
||||||
|
// 'mistralai/codestral-2501',
|
||||||
|
// 'qwen/qwen-2.5-coder-32b-instruct',
|
||||||
// 'mistralai/mistral-small-3.1-24b-instruct:free',
|
// 'mistralai/mistral-small-3.1-24b-instruct:free',
|
||||||
'google/gemini-2.0-flash-lite-preview-02-05:free',
|
// 'google/gemini-2.0-flash-lite-preview-02-05:free',
|
||||||
// 'google/gemini-2.0-pro-exp-02-05:free',
|
// 'google/gemini-2.0-pro-exp-02-05:free',
|
||||||
// 'google/gemini-2.0-flash-exp:free',
|
// 'google/gemini-2.0-flash-exp:free',
|
||||||
],
|
],
|
||||||
|
|
@ -285,6 +287,12 @@ const openSourceModelOptions_assumingOAICompat = {
|
||||||
contextWindow: 128_000, maxOutputTokens: 8_192,
|
contextWindow: 128_000, maxOutputTokens: 8_192,
|
||||||
|
|
||||||
},
|
},
|
||||||
|
'quasar': { // openrouter/quasar-alpha
|
||||||
|
supportsFIM: false,
|
||||||
|
supportsSystemMessage: 'system-role',
|
||||||
|
reasoningCapabilities: false,
|
||||||
|
contextWindow: 1_000_000, maxOutputTokens: 32_000,
|
||||||
|
}
|
||||||
} as const satisfies { [s: string]: Partial<VoidStaticModelInfo> }
|
} as const satisfies { [s: string]: Partial<VoidStaticModelInfo> }
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -305,9 +313,6 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (
|
||||||
...fallbackKnownValues
|
...fallbackKnownValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower))
|
|
||||||
return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat])
|
|
||||||
|
|
||||||
if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) return toFallback(geminiModelOptions['gemini-2.5-pro-exp-03-25'])
|
if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) return toFallback(geminiModelOptions['gemini-2.5-pro-exp-03-25'])
|
||||||
|
|
||||||
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions['claude-3-5-sonnet-20241022'])
|
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions['claude-3-5-sonnet-20241022'])
|
||||||
|
|
@ -338,6 +343,8 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (
|
||||||
|
|
||||||
if (lower.includes('openhands')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['openhands-lm-32b'], }) // max output unclear
|
if (lower.includes('openhands')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['openhands-lm-32b'], }) // max output unclear
|
||||||
|
|
||||||
|
if (lower.includes('quasar') || lower.includes('quaser')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['quasar'] })
|
||||||
|
|
||||||
if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions['gpt-4o-mini'])
|
if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions['gpt-4o-mini'])
|
||||||
if (lower.includes('4o')) return toFallback(openAIModelOptions['gpt-4o'])
|
if (lower.includes('4o')) return toFallback(openAIModelOptions['gpt-4o'])
|
||||||
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions['o1-mini'])
|
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions['o1-mini'])
|
||||||
|
|
@ -345,6 +352,9 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (
|
||||||
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions['o3-mini'])
|
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions['o3-mini'])
|
||||||
// if (lower.includes('o3')) return toFallback(openAIModelOptions['o3'])
|
// if (lower.includes('o3')) return toFallback(openAIModelOptions['o3'])
|
||||||
|
|
||||||
|
if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower))
|
||||||
|
return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat])
|
||||||
|
|
||||||
return toFallback(modelOptionsDefaults)
|
return toFallback(modelOptionsDefaults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,17 @@ import { toolNamesThatRequireApproval } from '../toolsServiceTypes.js';
|
||||||
import { IVoidModelService } from '../voidModelService.js';
|
import { IVoidModelService } from '../voidModelService.js';
|
||||||
import { ChatMode } from '../voidSettingsTypes.js';
|
import { ChatMode } from '../voidSettingsTypes.js';
|
||||||
|
|
||||||
// this is just for ease of readability
|
// Triple backtick wrapper used throughout the prompts for code blocks
|
||||||
export const tripleTick = ['```', '```']
|
export const tripleTick = ['```', '```']
|
||||||
|
|
||||||
|
// Maximum limits for directory structure information
|
||||||
export const MAX_DIRSTR_CHARS_TOTAL_BEGINNING = 20_000
|
export const MAX_DIRSTR_CHARS_TOTAL_BEGINNING = 20_000
|
||||||
export const MAX_DIRSTR_CHARS_TOTAL_TOOL = 20_000
|
export const MAX_DIRSTR_CHARS_TOTAL_TOOL = 20_000
|
||||||
export const MAX_DIRSTR_RESULTS_TOTAL_BEGINNING = 100
|
export const MAX_DIRSTR_RESULTS_TOTAL_BEGINNING = 100
|
||||||
export const MAX_DIRSTR_RESULTS_TOTAL_TOOL = 100
|
export const MAX_DIRSTR_RESULTS_TOTAL_TOOL = 100
|
||||||
|
|
||||||
|
|
||||||
|
// Maximum character limits for prefix and suffix context
|
||||||
export const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
export const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ export const voidTools = {
|
||||||
|
|
||||||
read_file: {
|
read_file: {
|
||||||
name: 'read_file',
|
name: 'read_file',
|
||||||
description: `Returns file contents of a given URI.`,
|
description: `Returns full contents of a given file.`,
|
||||||
params: {
|
params: {
|
||||||
...uriParam('file'),
|
...uriParam('file'),
|
||||||
start_line: { description: 'Optional. Default is 1. Start reading on this line.' },
|
start_line: { description: 'Optional. Default is 1. Start reading on this line.' },
|
||||||
|
|
@ -123,11 +124,19 @@ export const voidTools = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
read_lint_errors: {
|
||||||
|
name: 'read_lint_errors',
|
||||||
|
description: `Returns all lint errors on a given file.`,
|
||||||
|
params: {
|
||||||
|
...uriParam('file'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// --- editing (create/delete) ---
|
// --- editing (create/delete) ---
|
||||||
|
|
||||||
create_file_or_folder: {
|
create_file_or_folder: {
|
||||||
name: 'create_file_or_folder',
|
name: 'create_file_or_folder',
|
||||||
description: `Create a file or folder at the given path. To create a folder, ensure the path ends with a trailing slash.`,
|
description: `Create a file or folder at the given path. To create a folder, the path MUST end with a trailing slash.`,
|
||||||
params: {
|
params: {
|
||||||
...uriParam('file or folder'),
|
...uriParam('file or folder'),
|
||||||
},
|
},
|
||||||
|
|
@ -581,10 +590,16 @@ Instructions:
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instructions, fimTags, isOllamaFIM, language }: {
|
export const ctrlKStream_userMessage = ({
|
||||||
selection: string, prefix: string, suffix: string, instructions: string, fimTags: QuickEditFimTagsType, language: string,
|
selection,
|
||||||
isOllamaFIM: false, // we require this be false for clarity
|
prefix,
|
||||||
}) => {
|
suffix,
|
||||||
|
instructions,
|
||||||
|
// isOllamaFIM: false, // Remove unused variable
|
||||||
|
fimTags,
|
||||||
|
language }: {
|
||||||
|
selection: string, prefix: string, suffix: string, instructions: string, fimTags: QuickEditFimTagsType, language: string,
|
||||||
|
}) => {
|
||||||
const { preTag, sufTag, midTag } = fimTags
|
const { preTag, sufTag, midTag } = fimTags
|
||||||
|
|
||||||
// prompt the model artifically on how to do FIM
|
// prompt the model artifically on how to do FIM
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ export type ToolCallParams = {
|
||||||
'get_dir_structure': { rootURI: URI },
|
'get_dir_structure': { rootURI: URI },
|
||||||
'search_pathnames_only': { queryStr: string, searchInFolder: string | null, pageNumber: number },
|
'search_pathnames_only': { queryStr: string, searchInFolder: string | null, pageNumber: number },
|
||||||
'search_files': { queryStr: string, isRegex: boolean, searchInFolder: URI | null, pageNumber: number },
|
'search_files': { queryStr: string, isRegex: boolean, searchInFolder: URI | null, pageNumber: number },
|
||||||
|
'read_lint_errors': { uri: URI },
|
||||||
// ---
|
// ---
|
||||||
'edit_file': { uri: URI, changeDescription: string },
|
'edit_file': { uri: URI, changeDescription: string },
|
||||||
'create_file_or_folder': { uri: URI, isFolder: boolean },
|
'create_file_or_folder': { uri: URI, isFolder: boolean },
|
||||||
|
|
@ -43,6 +44,7 @@ export type ToolResultType = {
|
||||||
'get_dir_structure': { str: string, },
|
'get_dir_structure': { str: string, },
|
||||||
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
||||||
'search_files': { uris: URI[], hasNextPage: boolean },
|
'search_files': { uris: URI[], hasNextPage: boolean },
|
||||||
|
'read_lint_errors': { lintErrors: LintErrorItem[] | null },
|
||||||
// ---
|
// ---
|
||||||
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
|
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
|
||||||
'create_file_or_folder': {},
|
'create_file_or_folder': {},
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,7 @@ export type GlobalSettings = {
|
||||||
autoApprove: boolean;
|
autoApprove: boolean;
|
||||||
showInlineSuggestions: boolean;
|
showInlineSuggestions: boolean;
|
||||||
includeToolLintErrors: boolean;
|
includeToolLintErrors: boolean;
|
||||||
|
isOnboardingComplete: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultGlobalSettings: GlobalSettings = {
|
export const defaultGlobalSettings: GlobalSettings = {
|
||||||
|
|
@ -369,6 +370,7 @@ export const defaultGlobalSettings: GlobalSettings = {
|
||||||
autoApprove: false,
|
autoApprove: false,
|
||||||
showInlineSuggestions: true,
|
showInlineSuggestions: true,
|
||||||
includeToolLintErrors: true,
|
includeToolLintErrors: true,
|
||||||
|
isOnboardingComplete: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GlobalSettingName = keyof GlobalSettings
|
export type GlobalSettingName = keyof GlobalSettings
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue