mirror of
https://github.com/voideditor/void
synced 2026-05-23 09:28:23 +00:00
commit
152cfd93aa
19 changed files with 552 additions and 562 deletions
|
|
@ -1,112 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e # Exit on error
|
||||
set -x # Print commands as they are executed
|
||||
|
||||
# Configuration
|
||||
APP_NAME="void"
|
||||
APP_VERSION="1.0.0"
|
||||
ARCH="x86_64"
|
||||
|
||||
export ARCH
|
||||
|
||||
# Check if void binary exists in current directory
|
||||
if [ ! -f "./void" ]; then
|
||||
echo "Error: void binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if icon exists
|
||||
if [ ! -f "./void.png" ]; then
|
||||
echo "Error: void.png icon not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temporary directory
|
||||
TEMP_DIR="$(mktemp -d)"
|
||||
echo "Created temporary directory: $TEMP_DIR"
|
||||
APP_DIR="$TEMP_DIR/$APP_NAME.AppDir"
|
||||
|
||||
# Create basic AppDir structure
|
||||
mkdir -pv "$APP_DIR/usr/bin"
|
||||
mkdir -pv "$APP_DIR/usr/lib"
|
||||
mkdir -pv "$APP_DIR/usr/share/applications"
|
||||
mkdir -pv "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
|
||||
|
||||
# Exclude create-appimage.sh and appimagetool-x86_64.AppImage from being copied
|
||||
echo "Copying files excluding create-appimage.sh and appimagetool-x86_64.AppImage..."
|
||||
for file in ./*; do
|
||||
if [[ "$file" != "./create-appimage.sh" && "$file" != "./appimagetool-x86_64.AppImage" ]]; then
|
||||
cp -rv "$file" "$APP_DIR/usr/bin/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy the icon to required locations
|
||||
cp -v ./void.png "$APP_DIR/void.png"
|
||||
cp -v ./void.png "$APP_DIR/usr/share/icons/hicolor/256x256/apps/void.png"
|
||||
|
||||
# Copy dependencies with error checking
|
||||
echo "Copying dependencies..."
|
||||
for lib in $(ldd ./void | grep "=> /" | awk '{print $3}'); do
|
||||
if [ -f "$lib" ]; then
|
||||
cp -v "$lib" "$APP_DIR/usr/lib/" || echo "Failed to copy $lib"
|
||||
else
|
||||
echo "Warning: Library $lib not found"
|
||||
fi
|
||||
done
|
||||
|
||||
# Create desktop file with error checking
|
||||
echo "Creating desktop file..."
|
||||
if ! cat > "$APP_DIR/$APP_NAME.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Name=$APP_NAME
|
||||
Exec=void
|
||||
Icon=void
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
Comment=Void Linux Application
|
||||
EOF
|
||||
then
|
||||
echo "Error creating desktop file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make desktop file executable
|
||||
chmod +x "$APP_DIR/$APP_NAME.desktop"
|
||||
|
||||
# Copy the desktop file to the applications directory
|
||||
cp -v "$APP_DIR/$APP_NAME.desktop" "$APP_DIR/usr/share/applications/"
|
||||
|
||||
# Create AppRun with error checking
|
||||
echo "Creating AppRun..."
|
||||
if ! cat > "$APP_DIR/AppRun" <<EOF
|
||||
#!/bin/bash
|
||||
cd "\$(dirname "\$0")/usr/bin"
|
||||
export LD_LIBRARY_PATH="\$APPDIR/usr/lib:\$LD_LIBRARY_PATH"
|
||||
exec ./void "\$@"
|
||||
EOF
|
||||
then
|
||||
echo "Error creating AppRun"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make AppRun executable
|
||||
chmod +x "$APP_DIR/AppRun"
|
||||
|
||||
# Download appimagetool if not present in the current directory
|
||||
if [ ! -f "./appimagetool-x86_64.AppImage" ]; then
|
||||
echo "Downloading appimagetool-x86_64.AppImage..."
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
else
|
||||
echo "appimagetool-x86_64.AppImage is already present."
|
||||
fi
|
||||
|
||||
# Create the AppImage
|
||||
echo "Creating AppImage..."
|
||||
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APP_DIR" "${APP_NAME}-${APP_VERSION}-${ARCH}.AppImage"
|
||||
|
||||
# Cleanup
|
||||
echo "Cleaning up..."
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "AppImage creation complete!"
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"nameShort": "Void",
|
||||
"nameLong": "Void",
|
||||
"voidVersion": "1.2.1",
|
||||
"voidVersion": "1.2.3",
|
||||
"applicationName": "void",
|
||||
"dataFolderName": ".void-editor",
|
||||
"win32MutexName": "voideditor",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
|
||||
|
||||
# README
|
||||
|
||||
This is a community-made AppImage creation script.
|
||||
|
||||
There are some reported bugs with it.
|
||||
|
||||
To generate an AppImage yourself, feel free to look at
|
||||
stable-linux.yml in the separate `void-builder/` repo,
|
||||
which runs a GitHub Action that builds the AppImage you see on our website.
|
||||
|
||||
|
||||
# Void AppImage Creation Script
|
||||
|
||||
This script automates the process of creating an AppImage for the Void Editor using Docker. It works on macOS and Linux platforms.
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ import { Position } from '../../../../editor/common/core/position.js';
|
|||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { shorten } from '../../../../base/common/labels.js';
|
||||
import { IVoidModelService } from '../common/voidModelService.js';
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { findLast, findLastIdx } from '../../../../base/common/arraysFind.js';
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js';
|
||||
import { VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
|
||||
|
|
@ -246,8 +244,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@IMetricsService private readonly _metricsService: IMetricsService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IEditCodeService private readonly _editCodeService: IEditCodeService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IConvertToLLMMessageService private readonly _convertToLLMMessagesService: IConvertToLLMMessageService,
|
||||
|
|
@ -266,9 +262,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// always be in a thread
|
||||
this.openNewThread()
|
||||
|
||||
// when the user changes files, automatically add the new file as a stagingSelection
|
||||
this._register(this._editorService.onDidActiveEditorChange(() => this._addCurrentFileAsStagingSelectionDuringFileChange()));
|
||||
|
||||
|
||||
// keep track of user-modified files
|
||||
// const disposablesOfModelId: { [modelId: string]: IDisposable[] } = {}
|
||||
|
|
@ -288,40 +281,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
}
|
||||
|
||||
|
||||
// add the current file to the thread being edited
|
||||
private _addCurrentFileAsStagingSelectionDuringFileChange() {
|
||||
const newModel = this._codeEditorService.getActiveCodeEditor()?.getModel() ?? null
|
||||
if (!newModel) { return }
|
||||
|
||||
const isCurrentlyFocusing = this.isCurrentlyFocusingMessage()
|
||||
if (isCurrentlyFocusing) return
|
||||
|
||||
// only add if the user hasn't sent a message yet
|
||||
if (this.getCurrentThread().messages.length !== 0) return
|
||||
|
||||
const newStagingSelection: StagingSelectionItem = {
|
||||
type: 'File',
|
||||
uri: newModel.uri,
|
||||
language: newModel.getLanguageId(),
|
||||
state: { wasAddedAsCurrentFile: true }
|
||||
}
|
||||
|
||||
const oldStagingSelections = this.getCurrentThreadState().stagingSelections || [];
|
||||
|
||||
// remove all old selectons that are marked as `wasAddedAsCurrentFile`
|
||||
const newStagingSelections: StagingSelectionItem[] = oldStagingSelections.filter(s => s.state && !s.state.wasAddedAsCurrentFile)
|
||||
|
||||
const fileIsAlreadyHere = oldStagingSelections.some(s => s.type === 'File' && s.uri.fsPath === newStagingSelection.uri.fsPath)
|
||||
|
||||
if (!fileIsAlreadyHere) {
|
||||
newStagingSelections.push(newStagingSelection)
|
||||
}
|
||||
|
||||
this.setCurrentThreadState({ stagingSelections: newStagingSelections });
|
||||
|
||||
}
|
||||
|
||||
|
||||
// !!! this is important for properly restoring URIs from storage
|
||||
// should probably re-use code from void/src/vs/base/common/marshalling.ts instead. but this is simple enough
|
||||
private _convertThreadDataFromStorage(threadsStr: string): ChatThreads {
|
||||
|
|
@ -390,36 +349,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
|
||||
|
||||
editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => {
|
||||
|
||||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return // should never happen
|
||||
|
||||
if (thread.messages?.[messageIdx]?.role !== 'user') {
|
||||
throw new Error(`Error: editing a message with role !=='user'`)
|
||||
}
|
||||
|
||||
// get prev and curr selections before clearing the message
|
||||
const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message
|
||||
|
||||
// clear messages up to the index
|
||||
const slicedMessages = thread.messages.slice(0, messageIdx)
|
||||
this._setState({
|
||||
allThreads: {
|
||||
...this.state.allThreads,
|
||||
[thread.id]: {
|
||||
...thread,
|
||||
messages: slicedMessages
|
||||
}
|
||||
}
|
||||
}, true)
|
||||
|
||||
// re-add the message and stream it
|
||||
this.addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId })
|
||||
|
||||
}
|
||||
|
||||
|
||||
private _currentModelSelectionProps = () => {
|
||||
// these settings should not change throughout the loop (eg anthropic breaks if you change its thinking mode and it's using tools)
|
||||
const featureName: FeatureName = 'Chat'
|
||||
|
|
@ -924,7 +853,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
const [_, toIdx] = c
|
||||
if (toIdx === fromIdx) return
|
||||
|
||||
// console.log(`going from ${fromIdx} to ${toIdx}`)
|
||||
console.log(`going from ${fromIdx} to ${toIdx}`)
|
||||
|
||||
// update the user's checkpoint
|
||||
this._addUserModificationsToCurrCheckpoint({ threadId })
|
||||
|
|
@ -936,15 +865,14 @@ A,B,C are all files.
|
|||
x means a checkpoint where the file changed.
|
||||
|
||||
A B C D E F G H I
|
||||
x x x x x x x x x
|
||||
| | | | | | | | |
|
||||
x | | | | | | | x
|
||||
---x-|-|-|-x-|-x-|----- <-- to
|
||||
x | | | | | x
|
||||
| | x x |
|
||||
| | | |
|
||||
-------x-|---x-x------- <-- from
|
||||
x
|
||||
x x x x x x <-- you can't always go up to find the "before" version; sometimes you need to go down
|
||||
| | | | | | x
|
||||
--x-|-|-|-x---x-|----- <-- to
|
||||
| | | | x x
|
||||
| | x x |
|
||||
| | | |
|
||||
----x-|---x-x------- <-- from
|
||||
x
|
||||
|
||||
We need to revert anything that happened between to+1 and from.
|
||||
**We do this by finding the last x from 0...`to` for each file and applying those contents.**
|
||||
|
|
@ -952,9 +880,19 @@ We only need to do it for files that were edited since `to`, ie files between to
|
|||
*/
|
||||
if (toIdx < fromIdx) {
|
||||
const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: toIdx + 1, hiIdx: fromIdx })
|
||||
|
||||
const idxes = function* () {
|
||||
for (let k = toIdx; k >= 0; k -= 1) { // first go up
|
||||
yield k
|
||||
}
|
||||
for (let k = toIdx + 1; k < thread.messages.length; k += 1) { // then go down
|
||||
yield k
|
||||
}
|
||||
}
|
||||
|
||||
for (const fsPath in lastIdxOfURI) {
|
||||
// apply lowest down content for each uri (or original if not found)
|
||||
for (let k = toIdx; k >= 0; k -= 1) {
|
||||
// find the first instance of this file starting at toIdx (go up to latest file; if there is none, go down)
|
||||
for (const k of idxes()) {
|
||||
const message = thread.messages[k]
|
||||
if (message.role !== 'checkpoint') continue
|
||||
const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified })
|
||||
|
|
@ -970,16 +908,16 @@ We only need to do it for files that were edited since `to`, ie files between to
|
|||
/*
|
||||
if redoing
|
||||
|
||||
A B C D E F G H I
|
||||
x x x x x x x x x
|
||||
| | | | | | | | |
|
||||
x | | | | | | | x
|
||||
---x-|-|-|-x-|-x-|----- <-- from
|
||||
x | | | | | x
|
||||
| | x x |
|
||||
| | | |
|
||||
-------x-|---x-x------- <-- to
|
||||
x
|
||||
A B C D E F G H I J
|
||||
x x x x x x x
|
||||
| | | | | | x x x
|
||||
--x-|-|-|-x---x-|-|--- <-- from
|
||||
| | | | x x
|
||||
| | x x |
|
||||
| | | |
|
||||
----x-|---x-x-----|--- <-- to
|
||||
x x
|
||||
|
||||
|
||||
We need to apply latest change for anything that happened between from+1 and to.
|
||||
We only need to do it for files that were edited since `from`, ie files between from+1...to.
|
||||
|
|
@ -995,7 +933,6 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
if (!res) continue
|
||||
const { voidFileSnapshot } = res
|
||||
if (!voidFileSnapshot) continue
|
||||
|
||||
this._editCodeService.restoreVoidFileSnapshot(URI.file(fsPath), voidFileSnapshot)
|
||||
break
|
||||
}
|
||||
|
|
@ -1044,11 +981,15 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
})
|
||||
}
|
||||
|
||||
async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) {
|
||||
dismissStreamError(threadId: string): void {
|
||||
this._setStreamState(threadId, { error: undefined }, 'merge')
|
||||
}
|
||||
|
||||
|
||||
private async _addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) {
|
||||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return // should never happen
|
||||
|
||||
|
||||
const llmCancelToken = this.streamState[threadId]?.streamingToken // currently streaming LLM on this thread
|
||||
if (llmCancelToken === undefined && this.streamState[threadId]?.isRunning === 'LLM') {
|
||||
// if about to call the other LLM, just wait for it by stopping right now
|
||||
|
|
@ -1057,14 +998,11 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
// stop it (this simply resolves the promise to free up space)
|
||||
if (llmCancelToken !== undefined) this._llmMessageService.abort(llmCancelToken)
|
||||
|
||||
|
||||
|
||||
// add dummy before this message to keep checkpoint before user message idea consistent
|
||||
if (thread.messages.length === 0) {
|
||||
this._addUserCheckpoint({ threadId })
|
||||
}
|
||||
|
||||
|
||||
const { chatMode } = this._settingsService.state.globalSettings
|
||||
|
||||
// add user's message to chat history
|
||||
|
|
@ -1084,11 +1022,61 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
)
|
||||
}
|
||||
|
||||
dismissStreamError(threadId: string): void {
|
||||
this._setStreamState(threadId, { error: undefined }, 'merge')
|
||||
|
||||
async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string }) {
|
||||
const thread = this.state.allThreads[threadId];
|
||||
if (!thread) return
|
||||
|
||||
// if there's a current checkpoint, delete all messages after it
|
||||
if (thread.state.currCheckpointIdx !== null) {
|
||||
const checkpointIdx = thread.state.currCheckpointIdx;
|
||||
const newMessages = thread.messages.slice(0, checkpointIdx + 1);
|
||||
|
||||
// Update the thread with truncated messages
|
||||
const newThreads = {
|
||||
...this.state.allThreads,
|
||||
[threadId]: {
|
||||
...thread,
|
||||
lastModified: new Date().toISOString(),
|
||||
messages: newMessages,
|
||||
}
|
||||
};
|
||||
this._storeAllThreads(newThreads);
|
||||
this._setState({ allThreads: newThreads }, true);
|
||||
}
|
||||
|
||||
// Now call the original method to add the user message and stream the response
|
||||
await this._addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId });
|
||||
|
||||
}
|
||||
|
||||
editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => {
|
||||
|
||||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return // should never happen
|
||||
|
||||
if (thread.messages?.[messageIdx]?.role !== 'user') {
|
||||
throw new Error(`Error: editing a message with role !=='user'`)
|
||||
}
|
||||
|
||||
// get prev and curr selections before clearing the message
|
||||
const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message
|
||||
|
||||
// clear messages up to the index
|
||||
const slicedMessages = thread.messages.slice(0, messageIdx)
|
||||
this._setState({
|
||||
allThreads: {
|
||||
...this.state.allThreads,
|
||||
[thread.id]: {
|
||||
...thread,
|
||||
messages: slicedMessages
|
||||
}
|
||||
}
|
||||
}, true)
|
||||
|
||||
// re-add the message and stream it
|
||||
this._addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId })
|
||||
}
|
||||
|
||||
// ---------- the rest ----------
|
||||
|
||||
|
|
@ -1386,21 +1374,6 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
// switch to the thread
|
||||
this.switchToThread(threadId)
|
||||
|
||||
// add the current file as a staging selection
|
||||
const model = this._codeEditorService.getActiveCodeEditor()?.getModel()
|
||||
if (model) {
|
||||
this._setThreadState(this.state.currentThreadId, {
|
||||
stagingSelections: [{
|
||||
type: 'File',
|
||||
uri: model.uri,
|
||||
language: model.getLanguageId(),
|
||||
state: {
|
||||
wasAddedAsCurrentFile: true
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// otherwise, start a new thread
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/
|
|||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { ChatMessage } from '../common/chatThreadServiceTypes.js';
|
||||
import { getIsReasoningEnabledState, getMaxOutputTokens, getModelCapabilities } from '../common/modelCapabilities.js';
|
||||
import { toolCallXMLStr, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
|
||||
import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
|
||||
import { AnthropicLLMChatMessage, AnthropicReasoning, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { ChatMode, FeatureName, ModelSelection } from '../common/voidSettingsTypes.js';
|
||||
|
|
@ -143,13 +143,19 @@ const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsA
|
|||
// add anthropic reasoning
|
||||
if (currMsg.role === 'assistant') {
|
||||
if (currMsg.anthropicReasoning && supportsAnthropicReasoning) {
|
||||
|
||||
const content = currMsg.content
|
||||
newMessages[i] = {
|
||||
role: 'assistant',
|
||||
content: content ? [...currMsg.anthropicReasoning, { type: 'text' as const, text: content }] : currMsg.anthropicReasoning
|
||||
}
|
||||
}
|
||||
else {
|
||||
newMessages[i] = {
|
||||
role: 'assistant',
|
||||
content: currMsg.content,
|
||||
// strip away anthropicReasoning
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +205,7 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop
|
|||
// alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere
|
||||
let content: LLMChatMessage['content'] = c.content
|
||||
if (next?.role === 'tool') {
|
||||
content = `${content}\n\n${toolCallXMLStr(next.name, next.rawParams)}`
|
||||
content = `${content}\n\n${reParsedToolXMLString(next.name, next.rawParams)}`
|
||||
}
|
||||
|
||||
// anthropic reasoning
|
||||
|
|
@ -442,7 +448,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
`...Directories string cut off, use tools to read more...`
|
||||
: `...Directories string cut off, ask user for more if necessary...`
|
||||
})
|
||||
const includeXMLToolDefinitions = specialToolFormat === undefined
|
||||
const includeXMLToolDefinitions = !specialToolFormat
|
||||
|
||||
const runningTerminalIds = this.terminalToolService.listTerminalIds()
|
||||
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode, includeXMLToolDefinitions })
|
||||
|
|
|
|||
|
|
@ -258,6 +258,22 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
this._realignAllDiffAreasLines(uri, change.text, change.range)
|
||||
}
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
|
||||
// if diffarea has no diffs after a user edit, delete it
|
||||
const diffAreasToDelete: DiffZone[] = []
|
||||
for (const diffareaid of this.diffAreasOfURI[uri.fsPath] ?? []) {
|
||||
const diffArea = this.diffAreaOfId[diffareaid] ?? null
|
||||
const shouldDelete = diffArea?.type === 'DiffZone' && Object.keys(diffArea._diffOfId).length === 0
|
||||
if (shouldDelete) {
|
||||
diffAreasToDelete.push(diffArea)
|
||||
}
|
||||
}
|
||||
if (diffAreasToDelete.length !== 0) {
|
||||
const { onFinishEdit } = this._addToHistory(uri)
|
||||
diffAreasToDelete.forEach(da => this._deleteDiffZone(da))
|
||||
onFinishEdit()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1562,20 +1578,22 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
const errContentOfInvalidStr = (str: string & ReturnType<typeof findTextInCode>, blockOrig: string, blockNum: number, blocks: ExtractedSearchReplaceBlock[]) => {
|
||||
const errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => {
|
||||
|
||||
const descStr = str === `Not found` ?
|
||||
`The most recent ORIGINAL code could not be found in the file, so you were interrupted. The text in ORIGINAL must EXACTLY match lines of code. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: str === `Not unique` ?
|
||||
`The most recent ORIGINAL code shows up multiple times in the file, so you were interrupted. You might want to expand the ORIGINAL excerpt so it's unique. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: ``
|
||||
: str === 'Has overlap' ?
|
||||
`The most recent ORIGINAL code has overlap with another ORIGINAL code block that you outputted. Do NOT output any overlapping edits. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: ``
|
||||
|
||||
// string of <<<<< ORIGINAL >>>>> REPLACE blocks so far so LLM can understand what it currently has
|
||||
// const blocksSoFarStr = blocks.slice(0, blockNum).map(block => `${ORIGINAL}\n${block.orig}\n${DIVIDER}\n${block.final}\n${FINAL}`).join('\n')
|
||||
// const soFarStr = blocksSoFarStr ? `These are the Search/Replace blocks that have been applied so far:${tripleTick[0]}\n${blocksSoFarStr}\n${tripleTick[1]}` : ''
|
||||
// const continueMsg = soFarStr ? `${soFarStr}Please continue outputting SEARCH/REPLACE blocks starting where this leaves off.` : ''
|
||||
// const errMsg = `${descStr}${continueMsg ? `\n${continueMsg}` : ''}`
|
||||
const soFarStr = 'All of your previous outputs have been ignored. Please re-output ALL SEARCH/REPLACE blocks starting from the first one, and avoid the error.'
|
||||
const soFarStr = 'All of your previous outputs have been ignored. Please re-output ALL SEARCH/REPLACE blocks starting from the first one, and avoid the error this time.'
|
||||
const errMsg = `${descStr}\n${soFarStr}`
|
||||
return errMsg
|
||||
|
||||
|
|
@ -1610,7 +1628,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
const addedTrackingZoneOfBlockNum: TrackingZone<SearchReplaceDiffAreaMetadata>[] = []
|
||||
diffZone._streamState.line = 1
|
||||
|
||||
const N_RETRIES = 2
|
||||
const N_RETRIES = 4
|
||||
|
||||
// allowed to throw errors - this is called inside a promise that handles everything
|
||||
const runSearchReplace = async () => {
|
||||
|
|
@ -1684,17 +1702,25 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
// if this is the first time we're seeing this block, add it as a diffarea so we can start streaming in it
|
||||
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
|
||||
|
||||
|
||||
const originalBounds = findTextInCode(block.orig, originalFileCode, true)
|
||||
// if error
|
||||
if (typeof originalBounds === 'string') {
|
||||
// Check for overlap with existing modified ranges
|
||||
const hasOverlap = addedTrackingZoneOfBlockNum.some(trackingZone => {
|
||||
const [existingStart, existingEnd] = trackingZone.metadata.originalBounds;
|
||||
const hasNoOverlap = endLine < existingStart || startLine > existingEnd
|
||||
return !hasNoOverlap
|
||||
});
|
||||
|
||||
if (typeof originalBounds === 'string' || hasOverlap) {
|
||||
const errorMessage = typeof originalBounds === 'string' ? originalBounds : 'Has overlap' as const
|
||||
|
||||
console.log('--------------Error finding text in code:')
|
||||
console.log('originalFileCode', { originalFileCode })
|
||||
console.log('fullText', { fullText })
|
||||
console.log('error:', originalBounds)
|
||||
console.log('error:', errorMessage)
|
||||
console.log('block.orig:', block.orig)
|
||||
console.log('---------')
|
||||
const content = errContentOfInvalidStr(originalBounds, block.orig, blockNum, blocks)
|
||||
const content = errContentOfInvalidStr(errorMessage, block.orig)
|
||||
messages.push(
|
||||
{ role: 'assistant', content: fullText }, // latest output
|
||||
{ role: 'user', content: content } // user explanation of what's wrong
|
||||
|
|
|
|||
|
|
@ -1364,12 +1364,12 @@ const EditToolLintErrors = ({ lintErrors }: { lintErrors: LintErrorItem[] }) =>
|
|||
style={{ background: 'none' }}
|
||||
>
|
||||
<ChevronRight
|
||||
className={`mr-1 h-4 w-4 flex-shrink-0 transition-transform duration-100 text-void-fg-4 group-hover:text-void-fg-3 ${isOpen ? 'rotate-90' : ''}`}
|
||||
className={`mr-1 h-3 w-3 flex-shrink-0 transition-transform duration-100 text-void-fg-4 group-hover:text-void-fg-3 ${isOpen ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
<span className="font-medium text-void-fg-4 group-hover:text-void-fg-3 text-xs">Lint errors</span>
|
||||
</div>
|
||||
<div
|
||||
className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'opacity-100 py-1' : 'max-h-0 opacity-0'} text-xs pl-5`}
|
||||
className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'opacity-100' : 'max-h-0 opacity-0'} text-xs pl-4`}
|
||||
>
|
||||
<div className="flex flex-col gap-0.5 overflow-x-auto whitespace-nowrap text-void-fg-4 opacity-90 border-l-2 border-void-warning px-2 py-0.5">
|
||||
{lintErrors.map((error, i) => (
|
||||
|
|
@ -1949,10 +1949,14 @@ const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIs
|
|||
style={{ position: 'relative', display: 'inline-block' }} // allow absolute icon
|
||||
onClick={() => {
|
||||
if (threadIsRunning) return
|
||||
chatThreadService.jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified: true })
|
||||
chatThreadService.jumpToCheckpointBeforeMessageIdx({
|
||||
threadId,
|
||||
messageIdx,
|
||||
jumpToUserModified: messageIdx === (chatThreadService.state.allThreads[threadId]?.messages.length ?? 0) - 1
|
||||
})
|
||||
}}
|
||||
>
|
||||
Checkpoint
|
||||
Checkpoint
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
--void-bg-1: var(--vscode-input-background);
|
||||
--void-bg-1-alt: var(--vscode-badge-background);
|
||||
--void-bg-2: var(--vscode-sideBar-background);
|
||||
--void-bg-2-alt: color-mix(in srgb, var(--vscode-sideBar-background) 30%, var(--vscode-editor-background) 70%);
|
||||
--void-bg-2-alt: color-mix(in srgb, var(--vscode-editor-background) 30%, var(--vscode-sideBar-background) 70%);
|
||||
--void-bg-3: var(--vscode-editor-background);
|
||||
|
||||
--void-fg-0: color-mix(in srgb, var(--vscode-tab-activeForeground) 90%, black 10%);
|
||||
|
|
|
|||
|
|
@ -664,57 +664,60 @@ export const VoidCustomDropdownBox = <T extends NonNullable<any>>({
|
|||
{isOpen && (
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
className="z-10 bg-void-bg-1 border-void-border-1 border overflow-hidden rounded shadow-lg"
|
||||
className="z-10 bg-void-bg-1 border-void-border-3 border rounded shadow-lg"
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? 0,
|
||||
left: x ?? 0,
|
||||
width: matchInputWidth
|
||||
width: (matchInputWidth
|
||||
? (refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0)
|
||||
: Math.max(
|
||||
(refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0),
|
||||
(measureRef.current instanceof HTMLElement ? measureRef.current.offsetWidth : 0)
|
||||
),
|
||||
))
|
||||
}}
|
||||
>
|
||||
{options.map((option) => {
|
||||
const thisOptionIsSelected = getOptionsEqual(option, selectedOption);
|
||||
const optionName = getOptionDropdownName(option);
|
||||
const optionDetail = getOptionDropdownDetail?.(option) || '';
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
><div className='overflow-auto max-h-80'>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={optionName}
|
||||
className={`flex items-center px-2 py-1 cursor-pointer whitespace-nowrap
|
||||
{options.map((option) => {
|
||||
const thisOptionIsSelected = getOptionsEqual(option, selectedOption);
|
||||
const optionName = getOptionDropdownName(option);
|
||||
const optionDetail = getOptionDropdownDetail?.(option) || '';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={optionName}
|
||||
className={`flex items-center px-2 py-1 pr-4 cursor-pointer whitespace-nowrap
|
||||
transition-all duration-100
|
||||
bg-void-bg-1
|
||||
${thisOptionIsSelected ? 'bg-void-bg-2' : 'hover:bg-void-bg-2'}
|
||||
${thisOptionIsSelected ? 'bg-void-bg-2' : 'bg-void-bg-2-alt hover:bg-void-bg-2'}
|
||||
`}
|
||||
onClick={() => {
|
||||
onChangeOption(option);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="w-4 flex justify-center flex-shrink-0">
|
||||
{thisOptionIsSelected && (
|
||||
<svg className="size-3" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M10 3L4.5 8.5L2 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
onClick={() => {
|
||||
onChangeOption(option);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="w-4 flex justify-center flex-shrink-0">
|
||||
{thisOptionIsSelected && (
|
||||
<svg className="size-3" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M10 3L4.5 8.5L2 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<span className="flex justify-between w-full">
|
||||
<span>{optionName}</span>
|
||||
<span className='text-void-fg-4 opacity-60'>{optionDetail}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span className="flex justify-between w-full">
|
||||
<span>{optionName}</span>
|
||||
<span className='text-void-fg-4 opacity-60'>{optionDetail}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => {
|
|||
const { model } = await voidModelService.getModelSafe(nextURI)
|
||||
if (model) {
|
||||
// switch to the URI
|
||||
editorService.openCodeEditor({ resource: nextURI, options: { revealIfVisible: true } }, editor)
|
||||
editorService.openCodeEditor({ resource: model.uri, options: { revealIfVisible: true } }, editor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAccessor, useIsDark, useSettingsState } from '../util/services.js';
|
||||
import { Check, ExternalLink, X } from 'lucide-react';
|
||||
import { Brain, Check, DollarSign, ExternalLink, Lock, 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';
|
||||
|
||||
const OVERRIDE_VALUE = true
|
||||
const OVERRIDE_VALUE = false
|
||||
|
||||
export const VoidOnboarding = () => {
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ export const VoidOnboarding = () => {
|
|||
<div className={`@@void-scope ${isDark ? 'dark' : ''}`}>
|
||||
<div
|
||||
className={`
|
||||
hidden
|
||||
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'}
|
||||
`}
|
||||
|
|
@ -38,11 +37,12 @@ export const VoidOnboarding = () => {
|
|||
|
||||
const FADE_DURATION_MS = 2000
|
||||
|
||||
|
||||
const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: React.ReactNode, delayMs?: number, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||
const FadeIn = ({ children, className, delayMs = 0, durationMs, ...props }: { children: React.ReactNode, delayMs?: number, durationMs?: number, className?: string } & React.HTMLAttributes<HTMLDivElement>) => {
|
||||
|
||||
const [opacity, setOpacity] = useState(0)
|
||||
|
||||
const effectiveDurationMs = durationMs ?? FADE_DURATION_MS
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
|
|
@ -54,7 +54,7 @@ const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: Reac
|
|||
|
||||
|
||||
return (
|
||||
<div className={className} style={{ opacity, transition: `opacity ${FADE_DURATION_MS}ms ease-in-out` }} {...props}>
|
||||
<div className={className} style={{ opacity, transition: `opacity ${effectiveDurationMs}ms ease-in-out` }} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -102,15 +102,14 @@ const FadeIn = ({ children, className, delayMs = 0, ...props }: { children: Reac
|
|||
// 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"
|
||||
className="px-6 py-2 bg-zinc-100 enabled:hover:bg-zinc-100 disabled:bg-zinc-100/40 disabled:cursor-not-allowed rounded text-black duration-600 transition-all"
|
||||
{...props.disabled && {
|
||||
'data-tooltip-id': 'void-tooltip',
|
||||
'data-tooltip-content': 'Disabled (Please enter all required fields or choose another Provider)',
|
||||
'data-tooltip-content': 'Disabled (Please enter all required fields or choose another provider)',
|
||||
'data-tooltip-place': 'top',
|
||||
}}
|
||||
{...props}
|
||||
|
|
@ -120,23 +119,11 @@ const NextButton = ({ onClick, ...props }: { onClick: () => void } & React.Butto
|
|||
)
|
||||
}
|
||||
|
||||
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"
|
||||
className="px-6 py-2 rounded text-void-fg-3 opacity-80 hover:brightness-115 duration-600 transition-all"
|
||||
{...props}
|
||||
>
|
||||
Back
|
||||
|
|
@ -154,10 +141,10 @@ const OnboardingPageShell = ({ top, bottom, content, hasMaxWidth = true, classNa
|
|||
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 className={`min-h-full text-lg flex flex-col gap-4 w-full mx-auto ${hasMaxWidth ? 'max-w-[600px]' : ''} ${className}`}>
|
||||
{top && <FadeIn className='w-full mb-auto pt-16'>{top}</FadeIn>}
|
||||
{content && <FadeIn className='w-full my-auto'>{content}</FadeIn>}
|
||||
{bottom && <div className='w-full mt-auto pb-8'>{bottom}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -252,9 +239,9 @@ 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 ? "text text-emerald-500"
|
||||
: val === false ? 'text-rose-600'
|
||||
: "text text-amber-300"
|
||||
}
|
||||
>
|
||||
{
|
||||
|
|
@ -344,7 +331,6 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
|
|||
} = 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"
|
||||
|
|
@ -364,7 +350,7 @@ const TableOfModelsForProvider = ({ providerName }: { providerName: ProviderName
|
|||
<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={!!true} /></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>}
|
||||
|
|
@ -406,18 +392,42 @@ const VoidOnboardingContent = () => {
|
|||
// page 1 state
|
||||
const [wantToUseOption, setWantToUseOption] = useState<WantToUseOption>('smart')
|
||||
|
||||
// page 2 state
|
||||
const [selectedProviderName, setSelectedProviderName] = useState<ProviderName | null>(null)
|
||||
// Replace the single selectedProviderName with four separate states
|
||||
// page 2 state - each tab gets its own state
|
||||
const [selectedIntelligentProvider, setSelectedIntelligentProvider] = useState<ProviderName>('anthropic');
|
||||
const [selectedPrivateProvider, setSelectedPrivateProvider] = useState<ProviderName>('ollama');
|
||||
const [selectedAffordableProvider, setSelectedAffordableProvider] = useState<ProviderName>('gemini');
|
||||
const [selectedAllProvider, setSelectedAllProvider] = useState<ProviderName>('anthropic');
|
||||
|
||||
// Helper function to get the current selected provider based on active tab
|
||||
const getSelectedProvider = (): ProviderName => {
|
||||
switch (wantToUseOption) {
|
||||
case 'smart': return selectedIntelligentProvider;
|
||||
case 'private': return selectedPrivateProvider;
|
||||
case 'cheap': return selectedAffordableProvider;
|
||||
case 'all': return selectedAllProvider;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to set the selected provider for the current tab
|
||||
const setSelectedProvider = (provider: ProviderName) => {
|
||||
switch (wantToUseOption) {
|
||||
case 'smart': setSelectedIntelligentProvider(provider); break;
|
||||
case 'private': setSelectedPrivateProvider(provider); break;
|
||||
case 'cheap': setSelectedAffordableProvider(provider); break;
|
||||
case 'all': setSelectedAllProvider(provider); break;
|
||||
}
|
||||
}
|
||||
|
||||
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 selectedProviderName = getSelectedProvider();
|
||||
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
|
||||
|
|
@ -425,7 +435,7 @@ const VoidOnboardingContent = () => {
|
|||
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">
|
||||
<div className="flex items-center gap-2">
|
||||
<PreviousButton
|
||||
onClick={() => { setPageIndex(pageIndex - 1) }}
|
||||
/>
|
||||
|
|
@ -440,8 +450,8 @@ const VoidOnboardingContent = () => {
|
|||
// 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.",
|
||||
private: "Host on your computer or local network for full data privacy.",
|
||||
cheap: "Free and affordable options.",
|
||||
all: "",
|
||||
}
|
||||
|
||||
|
|
@ -453,20 +463,21 @@ const VoidOnboardingContent = () => {
|
|||
all: "",
|
||||
}
|
||||
|
||||
// set the selected provider name appropriately
|
||||
// Modified: initialize separate provider states on initial render instead of watching wantToUseOption changes
|
||||
useEffect(() => {
|
||||
if (wantToUseOption && providerNamesOfWantToUseOption[wantToUseOption].length > 0) {
|
||||
setSelectedProviderName(providerNamesOfWantToUseOption[wantToUseOption][0]);
|
||||
} else {
|
||||
setSelectedProviderName(null);
|
||||
if (selectedIntelligentProvider === undefined) {
|
||||
setSelectedIntelligentProvider(providerNamesOfWantToUseOption['smart'][0]);
|
||||
}
|
||||
}, [wantToUseOption]);
|
||||
|
||||
// set wantToUseOption to smart when page changes
|
||||
useEffect(() => {
|
||||
setWantToUseOption(wantToUseOption);
|
||||
}, [pageIndex]);
|
||||
|
||||
if (selectedPrivateProvider === undefined) {
|
||||
setSelectedPrivateProvider(providerNamesOfWantToUseOption['private'][0]);
|
||||
}
|
||||
if (selectedAffordableProvider === undefined) {
|
||||
setSelectedAffordableProvider(providerNamesOfWantToUseOption['cheap'][0]);
|
||||
}
|
||||
if (selectedAllProvider === undefined) {
|
||||
setSelectedAllProvider(providerNamesOfWantToUseOption['all'][0]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// reset the page to page 0 if the user redos onboarding
|
||||
useEffect(() => {
|
||||
|
|
@ -476,168 +487,175 @@ const VoidOnboardingContent = () => {
|
|||
}, [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={
|
||||
''
|
||||
<>
|
||||
<div className="text-5xl font-light text-center">Welcome to Void</div>
|
||||
<FadeIn
|
||||
delayMs={1500}
|
||||
className="text-center"
|
||||
onClick={() => { setPageIndex(pageIndex + 1) }}
|
||||
>
|
||||
Get Started
|
||||
</FadeIn>
|
||||
|
||||
</>
|
||||
}
|
||||
/>,
|
||||
1: <OnboardingPageShell
|
||||
|
||||
hasMaxWidth={false}
|
||||
top={
|
||||
<FadeIn className='flex flex-col items-center'>
|
||||
<div className="text-5xl font-light text-center">AI Preferences</div>
|
||||
top={<></>}
|
||||
content={<div className='flex flex-col items-center -translate-y-[20vh]'>
|
||||
{/* <div className="text-5xl text-center mb-8">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="text-4xl text-void-fg-2 mb-8 text-center">Model Preferences</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 className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[800px] mx-auto mt-8">
|
||||
<button
|
||||
onClick={() => { setWantToUseOption('smart'); setPageIndex(pageIndex + 1); }}
|
||||
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:border-void-border-1 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
|
||||
>
|
||||
<div className="flex items-center mb-3">
|
||||
<Brain size={24} className="text-void-fg-2 mr-2" />
|
||||
<div className="text-lg font-medium text-void-fg-1">Intelligent</div>
|
||||
</div>
|
||||
<div className="text-sm text-void-fg-2 text-left">{basicDescOfWantToUseOption['smart']}</div>
|
||||
</button>
|
||||
|
||||
<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>
|
||||
<button
|
||||
onClick={() => { setWantToUseOption('private'); setPageIndex(pageIndex + 1); }}
|
||||
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:border-void-border-1 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
|
||||
>
|
||||
<div className="flex items-center mb-3">
|
||||
<Lock size={24} className="text-void-fg-2 mr-2" />
|
||||
<div className="text-lg font-medium text-void-fg-1">Private</div>
|
||||
</div>
|
||||
<div className="text-sm text-void-fg-2 text-left">{basicDescOfWantToUseOption['private']}</div>
|
||||
</button>
|
||||
|
||||
<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>
|
||||
<button
|
||||
onClick={() => { setWantToUseOption('cheap'); setPageIndex(pageIndex + 1); }}
|
||||
className="flex flex-col p-6 rounded bg-void-bg-2 border border-void-border-3 hover:border-void-border-1 transition-colors focus:outline-none focus:border-void-accent-border relative overflow-hidden min-h-[160px]"
|
||||
>
|
||||
<div className="flex items-center mb-3">
|
||||
<DollarSign size={24} className="text-void-fg-2 mr-2" />
|
||||
<div className="text-lg font-medium text-void-fg-1">Affordable</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-void-fg-2 text-left">{basicDescOfWantToUseOption['cheap']}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</FadeIn>
|
||||
}
|
||||
content={<></>}
|
||||
|
||||
</div>}
|
||||
/>,
|
||||
2: <OnboardingPageShell
|
||||
top={
|
||||
<div className='flex flex-col items-center'>
|
||||
<>
|
||||
{/* Title */}
|
||||
<div className="text-5xl font-light text-center">Choose a Provider</div>
|
||||
|
||||
<div className="text-5xl font-light text-center mt-[10vh] mb-6">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'
|
||||
|
||||
<div
|
||||
className="mb-6 w-fit mx-auto flex items-center overflow-hidden bg-zinc-700/5 dark:bg-zinc-300/5 rounded-md"
|
||||
>
|
||||
{[
|
||||
{ id: 'smart', label: 'Intelligent' },
|
||||
{ id: 'private', label: 'Private' },
|
||||
{ id: 'cheap', label: 'Affordable' },
|
||||
{ id: 'all', label: 'All' }
|
||||
].map(option => (
|
||||
<button
|
||||
key={option.id}
|
||||
onClick={() => setWantToUseOption(option.id as WantToUseOption)}
|
||||
className={`py-1 px-2 text-xs cursor-pointer whitespace-nowrap rounded-sm transition-colors ${wantToUseOption === option.id
|
||||
? 'dark:text-white text-black 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>
|
||||
}`}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content={`${option.label} providers`}
|
||||
data-tooltip-place='bottom'
|
||||
>
|
||||
{option.label}
|
||||
</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
|
||||
{/* Provider Buttons - Modified to use separate components for each tab */}
|
||||
<div className="mb-2 w-full">
|
||||
{/* Intelligent tab */}
|
||||
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'smart' ? 'flex' : 'hidden'}`}>
|
||||
{providerNamesOfWantToUseOption['smart'].map((providerName) => {
|
||||
const isSelected = selectedIntelligentProvider === providerName;
|
||||
return (
|
||||
<button
|
||||
key={providerName}
|
||||
onClick={() => setSelectedIntelligentProvider(providerName)}
|
||||
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
|
||||
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
|
||||
>
|
||||
{displayInfoOfProviderName(providerName).title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
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>
|
||||
)
|
||||
})}
|
||||
{/* Private tab */}
|
||||
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'private' ? 'flex' : 'hidden'}`}>
|
||||
{providerNamesOfWantToUseOption['private'].map((providerName) => {
|
||||
const isSelected = selectedPrivateProvider === providerName;
|
||||
return (
|
||||
<button
|
||||
key={providerName}
|
||||
onClick={() => setSelectedPrivateProvider(providerName)}
|
||||
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
|
||||
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
|
||||
>
|
||||
{displayInfoOfProviderName(providerName).title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Affordable tab */}
|
||||
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'cheap' ? 'flex' : 'hidden'}`}>
|
||||
{providerNamesOfWantToUseOption['cheap'].map((providerName) => {
|
||||
const isSelected = selectedAffordableProvider === providerName;
|
||||
return (
|
||||
<button
|
||||
key={providerName}
|
||||
onClick={() => setSelectedAffordableProvider(providerName)}
|
||||
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
|
||||
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
|
||||
>
|
||||
{displayInfoOfProviderName(providerName).title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* All tab */}
|
||||
<div className={`flex flex-wrap items-center w-full ${wantToUseOption === 'all' ? 'flex' : 'hidden'}`}>
|
||||
{providerNames.map((providerName) => {
|
||||
const isSelected = selectedAllProvider === providerName;
|
||||
return (
|
||||
<button
|
||||
key={providerName}
|
||||
onClick={() => setSelectedAllProvider(providerName)}
|
||||
className={`py-[2px] px-2 mx-0.5 my-0.5 text-xs font-medium cursor-pointer relative rounded-full transition-all duration-300
|
||||
${isSelected ? 'bg-zinc-100 text-zinc-900 shadow-sm border-white/80' : 'bg-zinc-100/40 hover:bg-zinc-100/50 text-zinc-900 border-white/20'}`}
|
||||
>
|
||||
{displayInfoOfProviderName(providerName).title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
|
|
@ -672,17 +690,18 @@ const VoidOnboardingContent = () => {
|
|||
{!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>}
|
||||
: <AnimatedCheckmarkButton className='text-xs text-void-fg-3 mt-2' text='Added' />}
|
||||
</div>
|
||||
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
bottom={
|
||||
prevAndNextButtons
|
||||
<FadeIn delayMs={50} durationMs={10}>
|
||||
{prevAndNextButtons}
|
||||
</FadeIn>
|
||||
|
||||
}
|
||||
|
||||
/>,
|
||||
|
|
@ -701,7 +720,8 @@ const VoidOnboardingContent = () => {
|
|||
// {prevAndNextButtons}
|
||||
// </div>,
|
||||
3: <OnboardingPageShell
|
||||
top={
|
||||
|
||||
content={
|
||||
<div>
|
||||
<div className="text-5xl font-light text-center">Settings and Themes</div>
|
||||
|
||||
|
|
@ -716,20 +736,21 @@ const VoidOnboardingContent = () => {
|
|||
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>
|
||||
content={
|
||||
<>
|
||||
<div className="text-5xl font-light text-center">Jump in</div>
|
||||
<div
|
||||
className="text-center"
|
||||
onClick={() => {
|
||||
// TODO make a fadeout effect
|
||||
voidSettingsService.setGlobalSetting('isOnboardingComplete', true)
|
||||
}}
|
||||
|
||||
>
|
||||
Enter the Void
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
bottom={
|
||||
<PreviousButton
|
||||
|
|
|
|||
|
|
@ -44,37 +44,6 @@ const ModelSelectBox = ({ options, featureName, className }: { options: ModelOpt
|
|||
matchInputWidth={false}
|
||||
/>
|
||||
}
|
||||
// const ModelSelectBox = ({ options, featureName }: { options: ModelOption[], featureName: FeatureName }) => {
|
||||
// const accessor = useAccessor()
|
||||
|
||||
// const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
|
||||
// let weChangedText = false
|
||||
|
||||
// return <VoidSelectBox
|
||||
// className='@@[&_select]:!void-text-xs text-void-fg-3'
|
||||
// options={options}
|
||||
// onChangeSelection={useCallback((newVal: ModelSelection) => {
|
||||
// if (weChangedText) return
|
||||
// voidSettingsService.setModelSelectionOfFeature(featureName, newVal)
|
||||
// }, [voidSettingsService, featureName])}
|
||||
// // we are responsible for setting the initial state here. always sync instance when state changes.
|
||||
// onCreateInstance={useCallback((instance: SelectBox) => {
|
||||
// const syncInstance = () => {
|
||||
// const modelsListRef = voidSettingsService.state._modelOptions // as a ref
|
||||
// const settingsAtProvider = voidSettingsService.state.modelSelectionOfFeature[featureName]
|
||||
// const selectionIdx = settingsAtProvider === null ? -1 : modelsListRef.findIndex(v => modelSelectionsEqual(v.value, settingsAtProvider))
|
||||
// weChangedText = true
|
||||
// instance.select(selectionIdx === -1 ? 0 : selectionIdx)
|
||||
// weChangedText = false
|
||||
// }
|
||||
// syncInstance()
|
||||
// const disposable = voidSettingsService.onDidChangeState(syncInstance)
|
||||
// return [disposable]
|
||||
// }, [voidSettingsService, featureName])}
|
||||
// />
|
||||
// }
|
||||
|
||||
|
||||
|
||||
const MemoizedModelDropdown = ({ featureName, className }: { featureName: FeatureName, className: string }) => {
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export const AnimatedCheckmarkButton = ({ text, className }: { text?: string, cl
|
|||
|
||||
return <div
|
||||
className={`flex items-center gap-1.5 w-fit
|
||||
${className ? className : `px-2 py-0.5 text-xs text-white bg-[#0e70c0] rounded-sm`}
|
||||
${className ? className : `px-2 py-0.5 text-xs text-zinc-900 bg-zinc-100 rounded-sm`}
|
||||
`}
|
||||
>
|
||||
<svg className="size-4" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
|
@ -146,7 +146,7 @@ const AddButton = ({ disabled, text = 'Add', ...props }: { disabled?: boolean, t
|
|||
|
||||
return <button
|
||||
disabled={disabled}
|
||||
className={`bg-[#0e70c0] px-3 py-1 text-white rounded-sm ${!disabled ? 'hover:bg-[#1177cb] cursor-pointer' : 'opacity-50 cursor-not-allowed bg-opacity-70'}`}
|
||||
className={`bg-[#0e70c0] px-3 py-1 text-white dark:text-black rounded-sm ${!disabled ? 'hover:bg-[#1177cb] cursor-pointer' : 'opacity-50 cursor-not-allowed bg-opacity-70'}`}
|
||||
{...props}
|
||||
>{text}</button>
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ export const AddModelInputBox = ({ providerName: permanentProviderName, classNam
|
|||
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}`} />
|
||||
return <AnimatedCheckmarkButton text='Added' className={`bg-[#0e70c0] text-white dark:text-black px-3 py-1 rounded-sm ${className}`} />
|
||||
}
|
||||
|
||||
if (!isOpen) {
|
||||
|
|
@ -549,7 +549,12 @@ export const ollamaSetupInstructions = <div className='prose-p:my-0 prose-ol:lis
|
|||
<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 flex items-center w-fit'
|
||||
data-tooltip-id='void-tooltip-ollama-settings'
|
||||
>
|
||||
<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>
|
||||
|
||||
|
|
@ -617,7 +622,7 @@ export const FeaturesTab = () => {
|
|||
<h4 className={`text-base`}>{displayInfoOfFeatureName('Autocomplete')}</h4>
|
||||
<div className='text-sm italic text-void-fg-3 mt-1 mb-4'>
|
||||
<span>
|
||||
Experimental. Only works with FIM models.
|
||||
Experimental.{' '}
|
||||
</span>
|
||||
<span
|
||||
className='hover:brightness-110'
|
||||
|
|
@ -625,7 +630,7 @@ export const FeaturesTab = () => {
|
|||
data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.'
|
||||
data-tooltip-class-name='void-max-w-[20px]'
|
||||
>
|
||||
*
|
||||
Only works with FIM models.*
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -707,7 +712,7 @@ export const FeaturesTab = () => {
|
|||
value={voidSettingsState.globalSettings.includeToolLintErrors}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('includeToolLintErrors', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Don't fix lint errors`}</span>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{voidSettingsState.globalSettings.includeToolLintErrors ? 'Fix lint errors' : `Fix lint errors`}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import '../styles.css'
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import 'react-tooltip/dist/react-tooltip.css';
|
||||
import { useIsDark } from '../util/services.js';
|
||||
|
|
@ -44,7 +45,7 @@ export const VoidTooltip = () => {
|
|||
<>
|
||||
<style>
|
||||
{`
|
||||
#void-tooltip, #void-tooltip-orange, #void-tooltip-green {
|
||||
#void-tooltip, #void-tooltip-orange, #void-tooltip-green, #void-tooltip-ollama-settings {
|
||||
font-size: 12px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 6px;
|
||||
|
|
@ -66,6 +67,11 @@ export const VoidTooltip = () => {
|
|||
color: white;
|
||||
}
|
||||
|
||||
#void-tooltip-ollama-settings {
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
}
|
||||
|
||||
.react-tooltip-arrow {
|
||||
z-index: -1 !important; /* Keep arrow behind content (somehow this isnt done automatically) */
|
||||
}
|
||||
|
|
@ -92,6 +98,29 @@ export const VoidTooltip = () => {
|
|||
opacity={1}
|
||||
delayShow={50}
|
||||
/>
|
||||
<Tooltip
|
||||
id="void-tooltip-ollama-settings"
|
||||
border='1px solid rgba(100,100,100,.2)'
|
||||
opacity={1}
|
||||
openOnClick
|
||||
openEvents={{ mouseover: true }}
|
||||
place='right'
|
||||
style={{ pointerEvents: 'all', userSelect: 'text', fontSize: 11 }}
|
||||
>
|
||||
<div style={{ padding: '8px 10px' }}>
|
||||
<div style={{ opacity: 0.8, textAlign: 'center', fontWeight: 'bold', marginBottom: 8 }}>
|
||||
Good starter models
|
||||
</div>
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<span style={{ opacity: 0.8 }}>For chat:{` `}</span>
|
||||
<span style={{ opacity: 0.8, fontWeight: 'bold' }}>llama3.1</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ opacity: 0.8 }}>For autocomplete:{` `}</span>
|
||||
<span style={{ opacity: 0.8, fontWeight: 'bold' }}>qwen2.5-coder:1.5b</span>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifSe
|
|||
secondary: [{
|
||||
id: 'void.updater.close',
|
||||
enabled: true,
|
||||
label: `Keep Void outdated`,
|
||||
label: `Keep current version`,
|
||||
tooltip: '',
|
||||
class: undefined,
|
||||
run: () => {
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export const availableTools = (chatMode: ChatMode) => {
|
|||
return tools
|
||||
}
|
||||
|
||||
const availableXMLToolsStr = (tools: InternalToolInfo[]) => {
|
||||
const toolCallDefinitionsXMLString = (tools: InternalToolInfo[]) => {
|
||||
return `${tools.map((t, i) => {
|
||||
const params = Object.keys(t.params).map(paramName => `<${paramName}>${t.params[paramName].description}</${paramName}>`).join('\n')
|
||||
return `\
|
||||
|
|
@ -217,7 +217,7 @@ Format:
|
|||
}).join('\n\n')}`
|
||||
}
|
||||
|
||||
export const toolCallXMLStr = (toolName: ToolName, toolParams: RawToolParamsObj) => {
|
||||
export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolParamsObj) => {
|
||||
const params = Object.keys(toolParams).map(paramName => `<${paramName}>${toolParams[paramName as ToolParamName]}</${paramName}>`).join('\n')
|
||||
return `\
|
||||
<${toolName}>${!params ? '' : `\n${params}`}
|
||||
|
|
@ -234,12 +234,12 @@ const systemToolsXMLPrompt = (chatMode: ChatMode) => {
|
|||
const toolXMLDefinitions = (`\
|
||||
Available tools:
|
||||
|
||||
${availableXMLToolsStr(tools)}`)
|
||||
${toolCallDefinitionsXMLString(tools)}`)
|
||||
|
||||
const toolCallXMLGuidelines = (`\
|
||||
Tool calling details:
|
||||
- Once you write a tool call, you must STOP and WAIT for the result.
|
||||
- To call a tool, write its name and parameters in one of the XML formats specified above at the BOTTOM of your response.
|
||||
- To call a tool, write its name and parameters in one of the XML formats specified above.
|
||||
- After you write the tool call, you must STOP and WAIT for the result.
|
||||
- All parameters are REQUIRED unless noted otherwise.
|
||||
- You are only allowed to output ONE tool call, and it must be at the END of your response.
|
||||
- Your tool call will be executed immediately, and the results will appear in the following user message.`)
|
||||
|
|
@ -341,9 +341,9 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`)
|
|||
const ansStrs: string[] = []
|
||||
ansStrs.push(header)
|
||||
ansStrs.push(sysInfo)
|
||||
ansStrs.push(fsInfo)
|
||||
if (toolDefinitions) ansStrs.push(toolDefinitions)
|
||||
ansStrs.push(importantDetails)
|
||||
ansStrs.push(fsInfo)
|
||||
|
||||
const fullSystemMsgStr = ansStrs
|
||||
.join('\n\n\n')
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
|
|||
}
|
||||
else if (providerName === 'deepseek') {
|
||||
return {
|
||||
title: 'DeepSeek.com API',
|
||||
// title: 'DeepSeek.com API',
|
||||
title: 'DeepSeek',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'openRouter') {
|
||||
|
|
@ -95,22 +96,26 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn
|
|||
}
|
||||
else if (providerName === 'gemini') {
|
||||
return {
|
||||
title: 'Gemini API',
|
||||
// title: 'Gemini API',
|
||||
title: 'Gemini',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'groq') {
|
||||
return {
|
||||
title: 'Groq.com API',
|
||||
// title: 'Groq.com API',
|
||||
title: 'Groq',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'xAI') {
|
||||
return {
|
||||
title: 'Grok (xAI)',
|
||||
// title: 'Grok (xAI)',
|
||||
title: 'xAI',
|
||||
}
|
||||
}
|
||||
else if (providerName === 'mistral') {
|
||||
return {
|
||||
title: 'Mistral API',
|
||||
// title: 'Mistral API',
|
||||
title: 'Mistral',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
// disable foreign import complaints
|
||||
/* eslint-disable */
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import { Ollama } from 'ollama';
|
||||
import OpenAI, { ClientOptions } from 'openai';
|
||||
import { MistralCore } from '@mistralai/mistralai/core.js';
|
||||
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
|
||||
|
||||
/* eslint-enable */
|
||||
|
||||
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
|
||||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
|
|
@ -192,7 +194,9 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
|
|||
|
||||
// tools
|
||||
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
|
||||
const nativeToolsObj = potentialTools ? { tools: potentialTools } as const : {}
|
||||
const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ?
|
||||
{ tools: potentialTools } as const
|
||||
: {}
|
||||
|
||||
// instance
|
||||
const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
|
||||
|
|
@ -374,7 +378,9 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
|
|||
|
||||
// tools
|
||||
const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null
|
||||
const nativeToolsObj = potentialTools ? { tools: potentialTools, tool_choice: { type: 'auto' } } as const : {}
|
||||
const nativeToolsObj = potentialTools && specialToolFormat === 'anthropic-style' ?
|
||||
{ tools: potentialTools, tool_choice: { type: 'auto' } } as const
|
||||
: {}
|
||||
|
||||
|
||||
// instance
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
|
|||
super()
|
||||
}
|
||||
|
||||
|
||||
async check(explicit: boolean): Promise<VoidCheckUpdateRespose> {
|
||||
|
||||
const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts
|
||||
|
|
@ -37,17 +38,17 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
|
|||
|
||||
if (this._updateService.state.type === StateType.Uninitialized) {
|
||||
// The update service hasn't been initialized yet
|
||||
return { message: explicit ? 'Not yet checking for updates...' : null, action: explicit ? 'reinstall' : undefined } as const
|
||||
return { message: explicit ? 'Checking for updates soon...' : null, action: explicit ? 'reinstall' : undefined } as const
|
||||
}
|
||||
|
||||
if (this._updateService.state.type === StateType.Idle) {
|
||||
// No updates currently available
|
||||
return { message: explicit ? 'No update found!' : null, action: explicit ? 'reinstall' : undefined } as const
|
||||
return { message: explicit ? 'No updates found!' : null, action: explicit ? 'reinstall' : undefined } as const
|
||||
}
|
||||
|
||||
if (this._updateService.state.type === StateType.CheckingForUpdates) {
|
||||
// Currently checking for updates
|
||||
return { message: explicit ? 'No updates found!' : null } as const
|
||||
return { message: explicit ? 'Checking for updates...' : null } as const
|
||||
}
|
||||
|
||||
if (this._updateService.state.type === StateType.AvailableForDownload) {
|
||||
|
|
@ -62,7 +63,7 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
|
|||
|
||||
if (this._updateService.state.type === StateType.Downloaded) {
|
||||
// Update has been downloaded but not yet ready
|
||||
return { message: explicit ? 'Got download, need to apply...' : null, action: 'apply' } as const
|
||||
return { message: explicit ? 'An update is ready to be applied!' : null, action: 'apply' } as const
|
||||
}
|
||||
|
||||
if (this._updateService.state.type === StateType.Updating) {
|
||||
|
|
@ -76,28 +77,69 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
|
|||
}
|
||||
|
||||
if (this._updateService.state.type === StateType.Disabled) {
|
||||
try {
|
||||
const res = await fetch(`https://updates.voideditor.dev/api/v0/${this._productService.commit}`)
|
||||
const resJSON = await res.json()
|
||||
return await this._manualCheckGHTagIfDisabled(explicit)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (!resJSON) return null // null means error
|
||||
|
||||
const { hasUpdate, downloadMessage } = resJSON ?? {}
|
||||
if (hasUpdate === undefined)
|
||||
return null
|
||||
|
||||
const after = (downloadMessage || '') + ''
|
||||
if (hasUpdate)
|
||||
return { message: after, action: 'reinstall' } as const
|
||||
return { message: 'Void is up-to-date!' } as const
|
||||
|
||||
|
||||
|
||||
private async _manualCheckGHTagIfDisabled(explicit: boolean): Promise<VoidCheckUpdateRespose> {
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/repos/voideditor/binaries/releases/latest');
|
||||
|
||||
const data = await response.json();
|
||||
const version = data.tag_name;
|
||||
|
||||
const myVersion = `${this._productService.voidVersion}.${this._productService.release}`
|
||||
const latestVersion = version
|
||||
|
||||
const isUpToDate = myVersion === latestVersion // only makes sense if response.ok
|
||||
|
||||
let message: string | null
|
||||
let action: 'reinstall' | undefined
|
||||
|
||||
// explicit
|
||||
if (explicit) {
|
||||
if (response.ok) {
|
||||
if (!isUpToDate) {
|
||||
message = 'A new version of Void is available! Please reinstall (auto-updates are disabled on this OS) - it only takes a second!'
|
||||
action = 'reinstall'
|
||||
}
|
||||
else {
|
||||
message = 'Void is up-to-date!'
|
||||
}
|
||||
}
|
||||
else {
|
||||
message = `An error occurred when fetching the latest GitHub release tag. Please try again in ~5 minutes, or reinstall.`
|
||||
action = 'reinstall'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
return null
|
||||
// not explicit
|
||||
else {
|
||||
if (response.ok && !isUpToDate) {
|
||||
message = 'A new version of Void is available! Please reinstall (auto-updates are disabled on this OS) - it only takes a second!'
|
||||
action = 'reinstall'
|
||||
}
|
||||
else {
|
||||
message = null
|
||||
}
|
||||
}
|
||||
return { message, action } as const
|
||||
}
|
||||
catch (e) {
|
||||
if (explicit) {
|
||||
return {
|
||||
message: `An error occurred when fetching the latest GitHub release tag: ${e}. Please try again in ~5 minutes.`,
|
||||
action: 'reinstall',
|
||||
}
|
||||
}
|
||||
else {
|
||||
return { message: null } as const
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue