mirror of
https://github.com/voideditor/void
synced 2026-05-23 09:28: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];
|
||||
}
|
||||
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) {
|
||||
if (interrupted) {
|
||||
// the tool result is added when we stop running
|
||||
return { interrupted: true }
|
||||
}
|
||||
if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here
|
||||
|
||||
|
||||
const errorMessage = getErrorMessage(error)
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, })
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -678,7 +678,8 @@ const ToolHeaderWrapper = ({
|
|||
onClick,
|
||||
isOpen,
|
||||
isRejected,
|
||||
}: ToolHeaderParams) => {
|
||||
className, // applies to the main content
|
||||
}: ToolHeaderParams & { className?: string }) => {
|
||||
|
||||
const [isOpen_, setIsOpen] = useState(false);
|
||||
const isExpanded = isOpen !== undefined ? isOpen : isOpen_
|
||||
|
|
@ -687,7 +688,7 @@ const ToolHeaderWrapper = ({
|
|||
const isClickable = !!(isDropdown || onClick)
|
||||
|
||||
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 */}
|
||||
<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' : ''}`}>
|
||||
|
|
@ -1346,17 +1347,18 @@ const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) =>
|
|||
|
||||
if (lintErrors.length === 0) return null;
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<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">
|
||||
{lintErrors.map((error, i) => (
|
||||
<div key={i}>Lines {error.startLineNumber}-{error.endLineNumber}: {error.message}</div>
|
||||
))}
|
||||
</div>
|
||||
</ToolHeaderWrapper>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -418,6 +418,7 @@ export const VoidSwitch = ({
|
|||
onChange,
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
...props
|
||||
}: {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
|
|
@ -425,7 +426,7 @@ export const VoidSwitch = ({
|
|||
size?: 'xxs' | 'xs' | 'sm' | 'sm+' | 'md';
|
||||
}) => {
|
||||
return (
|
||||
<label className="inline-flex items-center">
|
||||
<label className="inline-flex items-center" {...props}>
|
||||
<div
|
||||
onClick={() => !disabled && onChange(!value)}
|
||||
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.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.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 React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames } from '../../../../common/voidSettingsTypes.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 { X, RefreshCw, Loader2, Check, MoveRight, PlusCircle, MinusCircle, Download, Trash, StopCircle, Square, ExternalLink } from 'lucide-react'
|
||||
import { isWindows, isLinux, isMacintosh } from '../../../../../../../base/common/platform.js'
|
||||
import { X, RefreshCw, Loader2, Check, } from 'lucide-react'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { env } from '../../../../../../../base/common/process.js'
|
||||
import { ModelDropdown } from './ModelDropdown.js'
|
||||
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
||||
import { WarningBox } from './WarningBox.js'
|
||||
import { os } from '../../../../common/helpers/systemInfo.js'
|
||||
import { IconLoading, IconX } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { getModelCapabilities, getProviderCapabilities, ollamaRecommendedModels, VoidStaticModelInfo } from '../../../../common/modelCapabilities.js'
|
||||
import { IconLoading } from '../sidebar-tsx/SidebarChat.js'
|
||||
|
||||
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -157,7 +154,7 @@ const AddButton = ({ disabled, text = 'Add', ...props }: { disabled?: boolean, t
|
|||
|
||||
|
||||
// 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 settingsStateService = accessor.get('IVoidSettingsService')
|
||||
|
|
@ -165,6 +162,7 @@ const AddModelInputBox = ({ providerName: permanentProviderName, className, comp
|
|||
const settingsState = useSettingsState()
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [showCheckmark, setShowCheckmark] = useState(false)
|
||||
|
||||
// const providerNameRef = useRef<ProviderName | null>(null)
|
||||
const [userChosenProviderName, setUserChosenProviderName] = useState<ProviderName>('anthropic')
|
||||
|
|
@ -176,6 +174,10 @@ const AddModelInputBox = ({ providerName: permanentProviderName, className, comp
|
|||
|
||||
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) {
|
||||
return <div
|
||||
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)
|
||||
setIsOpen(false)
|
||||
setShowCheckmark(true)
|
||||
setTimeout(() => {
|
||||
setShowCheckmark(false)
|
||||
setIsOpen(false)
|
||||
}, 1500)
|
||||
setErrorString('')
|
||||
setModelName('')
|
||||
}}
|
||||
|
|
@ -284,7 +290,16 @@ export const ModelDump = () => {
|
|||
|
||||
const isNewProviderName = (i > 0 ? modelDump[i - 1] : undefined)?.providerName !== providerName
|
||||
|
||||
const providerTitle = displayInfoOfProviderName(providerName).title
|
||||
|
||||
const disabled = !providerEnabled
|
||||
const value = disabled ? false : !isHidden
|
||||
|
||||
const tooltipName = (
|
||||
disabled ? `Add ${providerTitle} to enable`
|
||||
: value === true ? 'Enabled'
|
||||
: 'Disabled'
|
||||
)
|
||||
|
||||
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
|
||||
|
|
@ -292,7 +307,7 @@ export const ModelDump = () => {
|
|||
>
|
||||
{/* left part is width:full */}
|
||||
<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>
|
||||
</div>
|
||||
{/* 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>
|
||||
|
||||
<VoidSwitch
|
||||
value={disabled ? false : !isHidden}
|
||||
value={value}
|
||||
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
|
||||
disabled={disabled}
|
||||
size='sm'
|
||||
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-place='right'
|
||||
data-tooltip-content={tooltipName}
|
||||
/>
|
||||
|
||||
<div className={`w-5 flex items-center justify-center`}>
|
||||
|
|
@ -405,7 +424,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
// </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 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 = () => {
|
||||
const voidSettingsState = useSettingsState()
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -537,7 +579,8 @@ export const FeaturesTab = () => {
|
|||
<h2 className={`text-3xl mb-2`}>Models</h2>
|
||||
<ErrorBoundary>
|
||||
<ModelDump />
|
||||
<AddModelInputBox className='my-4' compact />
|
||||
<AddModelInputBox className='mt-4' compact />
|
||||
<RedoOnboardingButton className='mt-2 mb-4' />
|
||||
<AutoDetectLocalModelsToggle />
|
||||
<RefreshableModels />
|
||||
</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 fileService = accessor.get('IFileService')
|
||||
|
||||
|
|
@ -959,14 +1002,6 @@ export const Settings = () => {
|
|||
|
||||
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%' }}>
|
||||
<div className='overflow-y-auto w-full h-full px-10 py-10 select-none'>
|
||||
|
||||
|
|
@ -1013,669 +1048,3 @@ export const Settings = () => {
|
|||
</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;
|
||||
padding: 0px 8px;
|
||||
border-radius: 6px;
|
||||
z-index: 999;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
#void-tooltip {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default defineConfig({
|
|||
'./src2/sidebar-tsx/index.tsx',
|
||||
'./src2/void-settings-tsx/index.tsx',
|
||||
'./src2/void-tooltip/index.tsx',
|
||||
'./src2/void-onboarding/index.tsx',
|
||||
'./src2/quick-edit-tsx/index.tsx',
|
||||
'./src2/diff/index.tsx',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -211,6 +211,14 @@ export class ToolsService implements IToolsService {
|
|||
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) => {
|
||||
|
|
@ -322,6 +330,12 @@ export class ToolsService implements IToolsService {
|
|||
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 }) => {
|
||||
|
|
@ -359,20 +373,11 @@ export class ToolsService implements IToolsService {
|
|||
editCodeService.interruptURIStreaming({ uri: diffZoneURI })
|
||||
}
|
||||
|
||||
// at end, get lint errors
|
||||
const lintErrorsPromise = applyDonePromise.then(async () => {
|
||||
await timeout(500)
|
||||
|
||||
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, }
|
||||
await timeout(2000)
|
||||
const { lintErrors } = this._getLintErrors(uri)
|
||||
return { lintErrors }
|
||||
})
|
||||
|
||||
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 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
|
||||
this.stringOfResult = {
|
||||
|
|
@ -406,6 +415,11 @@ export class ToolsService implements IToolsService {
|
|||
search_files: (params, result) => {
|
||||
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) => {
|
||||
return `URI ${params.uri.fsPath} successfully created.`
|
||||
|
|
@ -416,7 +430,7 @@ export class ToolsService implements IToolsService {
|
|||
edit_file: (params, result) => {
|
||||
const lintErrsString = (
|
||||
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.`)
|
||||
: '')
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ import './voidSelectionHelperWidget.js'
|
|||
// register tooltip service
|
||||
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) ----------
|
||||
|
||||
// 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
|
||||
],
|
||||
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.5-sonnet',
|
||||
'deepseek/deepseek-r1',
|
||||
'deepseek/deepseek-r1-zero:free',
|
||||
'mistralai/codestral-2501',
|
||||
'qwen/qwen-2.5-coder-32b-instruct',
|
||||
'openrouter/quasar-alpha',
|
||||
'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',
|
||||
'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-flash-exp:free',
|
||||
],
|
||||
|
|
@ -285,6 +287,12 @@ const openSourceModelOptions_assumingOAICompat = {
|
|||
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> }
|
||||
|
||||
|
||||
|
|
@ -305,9 +313,6 @@ const extensiveModelFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (
|
|||
...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('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('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')) return toFallback(openAIModelOptions['gpt-4o'])
|
||||
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')) 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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,16 +11,17 @@ import { toolNamesThatRequireApproval } from '../toolsServiceTypes.js';
|
|||
import { IVoidModelService } from '../voidModelService.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 = ['```', '```']
|
||||
|
||||
// Maximum limits for directory structure information
|
||||
export const MAX_DIRSTR_CHARS_TOTAL_BEGINNING = 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_TOOL = 100
|
||||
|
||||
|
||||
|
||||
// Maximum character limits for prefix and suffix context
|
||||
export const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
||||
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ export const voidTools = {
|
|||
|
||||
read_file: {
|
||||
name: 'read_file',
|
||||
description: `Returns file contents of a given URI.`,
|
||||
description: `Returns full contents of a given file.`,
|
||||
params: {
|
||||
...uriParam('file'),
|
||||
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) ---
|
||||
|
||||
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: {
|
||||
...uriParam('file or folder'),
|
||||
},
|
||||
|
|
@ -581,10 +590,16 @@ Instructions:
|
|||
`
|
||||
}
|
||||
|
||||
export const ctrlKStream_userMessage = ({ selection, prefix, suffix, instructions, fimTags, isOllamaFIM, language }: {
|
||||
selection: string, prefix: string, suffix: string, instructions: string, fimTags: QuickEditFimTagsType, language: string,
|
||||
isOllamaFIM: false, // we require this be false for clarity
|
||||
}) => {
|
||||
export const ctrlKStream_userMessage = ({
|
||||
selection,
|
||||
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
|
||||
|
||||
// prompt the model artifically on how to do FIM
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export type ToolCallParams = {
|
|||
'get_dir_structure': { rootURI: URI },
|
||||
'search_pathnames_only': { queryStr: string, searchInFolder: string | 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 },
|
||||
'create_file_or_folder': { uri: URI, isFolder: boolean },
|
||||
|
|
@ -43,6 +44,7 @@ export type ToolResultType = {
|
|||
'get_dir_structure': { str: string, },
|
||||
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
||||
'search_files': { uris: URI[], hasNextPage: boolean },
|
||||
'read_lint_errors': { lintErrors: LintErrorItem[] | null },
|
||||
// ---
|
||||
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
|
||||
'create_file_or_folder': {},
|
||||
|
|
|
|||
|
|
@ -357,6 +357,7 @@ export type GlobalSettings = {
|
|||
autoApprove: boolean;
|
||||
showInlineSuggestions: boolean;
|
||||
includeToolLintErrors: boolean;
|
||||
isOnboardingComplete: boolean;
|
||||
}
|
||||
|
||||
export const defaultGlobalSettings: GlobalSettings = {
|
||||
|
|
@ -369,6 +370,7 @@ export const defaultGlobalSettings: GlobalSettings = {
|
|||
autoApprove: false,
|
||||
showInlineSuggestions: true,
|
||||
includeToolLintErrors: true,
|
||||
isOnboardingComplete: true,
|
||||
}
|
||||
|
||||
export type GlobalSettingName = keyof GlobalSettings
|
||||
|
|
|
|||
Loading…
Reference in a new issue