mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
links draft 1
This commit is contained in:
parent
009dfbef37
commit
4a9c0c864f
4 changed files with 405 additions and 59 deletions
|
|
@ -21,7 +21,11 @@ import { generateUuid } from '../../../../base/common/uuid.js';
|
|||
import { getErrorMessage } from '../../../../base/common/errors.js';
|
||||
import { ChatMode, FeatureName } from '../common/voidSettingsTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
|
||||
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
||||
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
|
||||
import { LocationLink, SymbolKind } from '../../../../editor/common/languages.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { Position } from '../../../../editor/common/core/position.js';
|
||||
|
||||
const findLastIndex = <T>(arr: T[], condition: (t: T) => boolean): number => {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
|
|
@ -78,6 +82,15 @@ export type FileSelection = {
|
|||
|
||||
export type StagingSelectionItem = CodeSelection | FileSelection
|
||||
|
||||
export type CodespanLocationLink = {
|
||||
uri: URI, // we handle serialization for this
|
||||
selection?: { // store as JSON so dont have to worry about serialization
|
||||
startLineNumber: number
|
||||
startColumn: number,
|
||||
endLineNumber: number
|
||||
endColumn: number,
|
||||
} | undefined
|
||||
} | null
|
||||
|
||||
export type ToolMessage<T extends ToolName> = {
|
||||
role: 'tool';
|
||||
|
|
@ -133,6 +146,13 @@ export type ChatThreads = {
|
|||
state: {
|
||||
stagingSelections: StagingSelectionItem[];
|
||||
focusedMessageIdx: number | undefined; // index of the message that is being edited (undefined if none)
|
||||
|
||||
linksOfMessageIdx: { // eg. link = linksOfMessageIdx[4]['RangeFunction']
|
||||
[messageIdx: number]: {
|
||||
[codespanName: string]: CodespanLocationLink
|
||||
}
|
||||
}
|
||||
|
||||
isCheckedOfSelectionId: { [selectionId: string]: boolean }; // TODO
|
||||
}
|
||||
};
|
||||
|
|
@ -143,7 +163,8 @@ type ThreadType = ChatThreads[string]
|
|||
const defaultThreadState: ThreadType['state'] = {
|
||||
stagingSelections: [],
|
||||
focusedMessageIdx: undefined,
|
||||
isCheckedOfSelectionId: {}
|
||||
isCheckedOfSelectionId: {},
|
||||
linksOfMessageIdx: {},
|
||||
}
|
||||
|
||||
export type ThreadsState = {
|
||||
|
|
@ -199,12 +220,19 @@ export interface IChatThreadService {
|
|||
isFocusingMessage(): boolean;
|
||||
setFocusedMessageIdx(messageIdx: number | undefined): void;
|
||||
|
||||
|
||||
|
||||
getCodespanLink({ codespanStr, messageIdx, threadId }: { codespanStr: string, messageIdx: number, threadId: string }): CodespanLocationLink | undefined;
|
||||
addCodespanLink({ newLinkText, newLinkLocation, messageIdx, threadId }: { newLinkText: string, newLinkLocation: CodespanLocationLink, messageIdx: number, threadId: string }): void;
|
||||
generateCodespanLink(codespanStr: string): Promise<CodespanLocationLink>
|
||||
|
||||
// exposed getters/setters
|
||||
getCurrentMessageState: (messageIdx: number) => UserMessageState
|
||||
setCurrentMessageState: (messageIdx: number, newState: Partial<UserMessageState>) => void
|
||||
getCurrentThreadState: () => ThreadType['state']
|
||||
setCurrentThreadState: (newState: Partial<ThreadType['state']>) => void
|
||||
|
||||
|
||||
closeStagingSelectionsInCurrentThread(): void;
|
||||
closeStagingSelectionsInMessage(messageIdx: number): void;
|
||||
|
||||
|
|
@ -243,6 +271,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
@IToolsService private readonly _toolsService: IToolsService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@ITextModelService private readonly _textModelService: ITextModelService,
|
||||
) {
|
||||
super()
|
||||
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
|
||||
|
|
@ -260,6 +290,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
}
|
||||
|
||||
// !!! 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 {
|
||||
return JSON.parse(threadsStr, (key, value) => {
|
||||
if (value && typeof value === 'object' && value.$mid === 1) { //$mid is the MarshalledId. $mid === 1 means it is a URI
|
||||
|
|
@ -551,6 +582,220 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// ---------- the rest ----------
|
||||
|
||||
// gets the location of codespan link so the user can click on it
|
||||
async generateCodespanLink(_codespanStr: string): Promise<CodespanLocationLink> {
|
||||
|
||||
// process codespan to understand what we are searching for
|
||||
// TODO account for more complicated patterns eg `ITextEditorService.openEditor()`
|
||||
const filePattern = /^[^\s.]+\.[^\s.]+$/;
|
||||
const functionPattern = /^[^\s(]+\([^)]*\)$/;
|
||||
|
||||
let target = _codespanStr // the string to search for
|
||||
let codespanType: 'file' | 'function-or-class' | 'unsearchable' = 'unsearchable';
|
||||
if (filePattern.test(target)) {
|
||||
|
||||
codespanType = 'file'
|
||||
target = _codespanStr
|
||||
|
||||
} else if (functionPattern.test(target)) {
|
||||
const match = target.match(functionPattern)
|
||||
if (match && match[0]) {
|
||||
|
||||
codespanType = 'function-or-class'
|
||||
target = match[0]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (codespanType === 'unsearchable') {
|
||||
return null
|
||||
}
|
||||
|
||||
// get history of all AI and user added files in conversation + store in reverse order (MRU)
|
||||
const prevUris = this._getAllSelections()
|
||||
.map(s => s.fileURI)
|
||||
.filter((uri, index, array) => array.findIndex(u => u.toString() === uri.toString()) === index) // O(n^2) but this is small
|
||||
.reverse()
|
||||
|
||||
|
||||
|
||||
if (codespanType === 'file') {
|
||||
|
||||
|
||||
const doesUriMatchTarget = (uri: URI) => uri.path.includes(target)
|
||||
|
||||
// check if any prevFiles are the `codespanSearch`
|
||||
for (const uri of prevUris) {
|
||||
if (doesUriMatchTarget(uri)) return { uri }
|
||||
}
|
||||
|
||||
// else search codebase for file
|
||||
const { uris } = await this._toolsService.callTool['pathname_search']({ queryStr: target, pageNumber: 0 })
|
||||
|
||||
for (const uri of uris) {
|
||||
if (doesUriMatchTarget(uri)) return { uri }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (codespanType === 'function-or-class') {
|
||||
|
||||
|
||||
// check all prevUris for the target
|
||||
for (const uri of prevUris) {
|
||||
|
||||
const modelRef = await this._textModelService.createModelReference(uri);
|
||||
const model = modelRef.object.textEditorModel;
|
||||
|
||||
try {
|
||||
const matches = model.findMatches(
|
||||
target.split('(')[0].trim(), // remove parameters
|
||||
false, // searchOnlyEditableRange
|
||||
false, // isRegex
|
||||
true, // matchCase
|
||||
null, // wordSeparators
|
||||
true // captureMatches
|
||||
);
|
||||
|
||||
const firstThree = matches.slice(0, 3);
|
||||
|
||||
// take first 3 occurences, attempt to goto definition on them
|
||||
|
||||
for (const match of firstThree) {
|
||||
const position = new Position(match.range.startLineNumber, match.range.startColumn);
|
||||
const definitionProviders = this._languageFeaturesService.definitionProvider.ordered(model);
|
||||
|
||||
for (const provider of definitionProviders) {
|
||||
const definitions = await provider.provideDefinition(model, position, CancellationToken.None);
|
||||
if (!definitions) continue;
|
||||
|
||||
const locationLinks: LocationLink[] = [];
|
||||
|
||||
if (Array.isArray(definitions)) {
|
||||
// Handle Location[] or LocationLink[]
|
||||
for (const def of definitions) {
|
||||
if ('uri' in def) {
|
||||
locationLinks.push({
|
||||
uri: def.uri,
|
||||
range: def.range,
|
||||
targetSelectionRange: def.range,
|
||||
originSelectionRange: undefined
|
||||
});
|
||||
} else {
|
||||
locationLinks.push(def);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle single Location
|
||||
locationLinks.push({
|
||||
uri: definitions.uri,
|
||||
range: definitions.range,
|
||||
targetSelectionRange: definitions.range,
|
||||
originSelectionRange: undefined
|
||||
});
|
||||
}
|
||||
|
||||
const definition = locationLinks[0];
|
||||
if (!definition) continue;
|
||||
|
||||
// Load definition file model
|
||||
const defModelRef = await this._textModelService.createModelReference(definition.uri);
|
||||
const defModel = defModelRef.object.textEditorModel;
|
||||
|
||||
try {
|
||||
const symbolProviders = this._languageFeaturesService.documentSymbolProvider.ordered(defModel);
|
||||
|
||||
for (const symbolProvider of symbolProviders) {
|
||||
const symbols = await symbolProvider.provideDocumentSymbols(
|
||||
defModel,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
if (symbols) {
|
||||
const symbol = symbols.find(s => {
|
||||
const symbolRange = s.range;
|
||||
return symbolRange.startLineNumber <= definition.range.startLineNumber &&
|
||||
symbolRange.endLineNumber >= definition.range.endLineNumber &&
|
||||
(symbolRange.startLineNumber !== definition.range.startLineNumber || symbolRange.startColumn <= definition.range.startColumn) &&
|
||||
(symbolRange.endLineNumber !== definition.range.endLineNumber || symbolRange.endColumn >= definition.range.endColumn);
|
||||
});
|
||||
|
||||
|
||||
console.log('@@@ symbol', symbol?.name, symbol?.kind)
|
||||
|
||||
// if we got to a class/function get the full range and return
|
||||
if (symbol?.kind === SymbolKind.Function || symbol?.kind === SymbolKind.Class) {
|
||||
return {
|
||||
uri: definition.uri,
|
||||
selection: {
|
||||
startLineNumber: definition.range.startLineNumber,
|
||||
startColumn: definition.range.startColumn,
|
||||
endLineNumber: definition.range.endLineNumber,
|
||||
endColumn: definition.range.endColumn,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
defModelRef.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// unlike above do not search codebase (doesnt make sense)
|
||||
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
getCodespanLink({ codespanStr, messageIdx, threadId }: { codespanStr: string, messageIdx: number, threadId: string }): CodespanLocationLink | undefined {
|
||||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return undefined;
|
||||
|
||||
const links = thread.state.linksOfMessageIdx?.[messageIdx]
|
||||
if (!links) return undefined;
|
||||
|
||||
const location = links[codespanStr]
|
||||
if (!location) return undefined;
|
||||
|
||||
return location
|
||||
}
|
||||
|
||||
async addCodespanLink({ newLinkText, newLinkLocation, messageIdx, threadId }: { newLinkText: string, newLinkLocation: CodespanLocationLink, messageIdx: number, threadId: string }) {
|
||||
const thread = this.state.allThreads[threadId]
|
||||
if (!thread) return
|
||||
|
||||
this._setState({
|
||||
|
||||
allThreads: {
|
||||
...this.state.allThreads,
|
||||
[threadId]: {
|
||||
...thread,
|
||||
state: {
|
||||
...thread.state,
|
||||
linksOfMessageIdx: {
|
||||
...thread.state.linksOfMessageIdx,
|
||||
[messageIdx]: {
|
||||
...thread.state.linksOfMessageIdx?.[messageIdx],
|
||||
[newLinkText]: newLinkLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
|
||||
|
||||
getCurrentThread(): ChatThreads[string] {
|
||||
const state = this.state
|
||||
const thread = state.allThreads[state.currentThreadId]
|
||||
|
|
@ -721,7 +966,9 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
|
||||
getCurrentThreadState = () => {
|
||||
|
||||
const currentThread = this.getCurrentThread()
|
||||
|
||||
return currentThread.state
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,17 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { JSX } from 'react'
|
||||
import React, { JSX, useState } from 'react'
|
||||
import { marked, MarkedToken, Token } from 'marked'
|
||||
import { BlockCode } from './BlockCode.js'
|
||||
import { nameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'
|
||||
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
|
||||
import { useAccessor, useChatThreadsState } from '../util/services.js'
|
||||
import { Range } from '../../../../../../services/search/common/searchExtTypes.js'
|
||||
import { CodespanLocationLink } from '../../../chatThreadService.js'
|
||||
import { IRange } from '../../../../../../../base/common/range.js'
|
||||
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'
|
||||
|
||||
|
||||
export type ChatMessageLocation = {
|
||||
threadId: string;
|
||||
|
|
@ -20,7 +26,87 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) =>
|
|||
return `${threadId}-${messageIdx}-${tokenIdx}`
|
||||
}
|
||||
|
||||
const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {
|
||||
|
||||
const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => {
|
||||
|
||||
return <code
|
||||
className={`font-mono font-medium rounded-sm bg-void-bg-1 px-1 ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</code>
|
||||
|
||||
}
|
||||
|
||||
const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string, rawText: string, chatMessageLocation: ChatMessageLocation }) => {
|
||||
|
||||
const accessor = useAccessor()
|
||||
|
||||
const chatThreadService = accessor.get('IChatThreadService')
|
||||
const commandSerivce = accessor.get('ICommandService')
|
||||
const editorService = accessor.get('ICodeEditorService')
|
||||
|
||||
const { messageIdx, threadId } = chatMessageLocation
|
||||
|
||||
const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)
|
||||
|
||||
console.log('rerender', didComputeCodespanLink ? 1 : 0)
|
||||
|
||||
let link = undefined
|
||||
|
||||
if (rawText.endsWith("`")) { // if codespan was completed
|
||||
|
||||
// get link from cache
|
||||
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })
|
||||
|
||||
if (link === undefined) {
|
||||
|
||||
// generate link and add to cache
|
||||
chatThreadService.generateCodespanLink(text)
|
||||
.then(link => {
|
||||
|
||||
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
|
||||
|
||||
setDidComputeCodespanLink(true)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const onClick = () => {
|
||||
|
||||
if (!link) return;
|
||||
const selection = link.selection
|
||||
|
||||
// open the file
|
||||
commandSerivce.executeCommand('vscode.open', link.uri).then(() => {
|
||||
|
||||
console.log('click:', selection, link.uri)
|
||||
|
||||
// select the text
|
||||
if (!selection) return;
|
||||
|
||||
const editor = editorService.getActiveCodeEditor()
|
||||
if (!editor) return;
|
||||
|
||||
editor.setSelection(selection)
|
||||
editor.revealRange(selection, ScrollType.Immediate)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
return <Codespan
|
||||
text={text}
|
||||
onClick={onClick}
|
||||
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
|
||||
/>
|
||||
}
|
||||
|
||||
const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {
|
||||
|
||||
// deal with built-in tokens first (assume marked token)
|
||||
const t = token as MarkedToken
|
||||
|
|
@ -35,12 +121,14 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
|
|||
|
||||
if (t.type === "code") {
|
||||
|
||||
const applyBoxId = chatMessageLocationForApply ? getApplyBoxId({
|
||||
threadId: chatMessageLocationForApply.threadId,
|
||||
messageIdx: chatMessageLocationForApply.messageIdx,
|
||||
const applyBoxId = chatMessageLocation ? getApplyBoxId({
|
||||
threadId: chatMessageLocation.threadId,
|
||||
messageIdx: chatMessageLocation.messageIdx,
|
||||
tokenIdx: tokenIdx,
|
||||
}) : null
|
||||
|
||||
// TODO user should only be able to apply this when the code has been closed (t.raw ends with "```")
|
||||
|
||||
return <div>
|
||||
<BlockCode
|
||||
initValue={t.text}
|
||||
|
|
@ -132,7 +220,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
|
|||
return <li>
|
||||
<input type="checkbox" checked={t.checked} readOnly />
|
||||
<span>
|
||||
<ChatMarkdownRender chatMessageLocationForApply={chatMessageLocationForApply} string={t.text} nested={true} />
|
||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} nested={true} />
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
|
|
@ -148,7 +236,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
|
|||
<input type="checkbox" checked={item.checked} readOnly />
|
||||
)}
|
||||
<span>
|
||||
<ChatMarkdownRender chatMessageLocationForApply={chatMessageLocationForApply} string={item.text} nested={true} />
|
||||
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -162,6 +250,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
|
|||
<RenderToken key={index}
|
||||
token={token}
|
||||
tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} // assign a unique tokenId to nested components
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
@ -221,11 +310,19 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
|
|||
|
||||
// inline code
|
||||
if (t.type === "codespan") {
|
||||
return (
|
||||
<code className="font-mono font-medium rounded-sm bg-void-bg-1 px-1">
|
||||
{t.text}
|
||||
</code>
|
||||
)
|
||||
|
||||
|
||||
console.log('chatmessagelocation', chatMessageLocation)
|
||||
|
||||
if (chatMessageLocation) {
|
||||
return <CodespanWithLink
|
||||
text={t.text}
|
||||
rawText={t.raw}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
/>
|
||||
}
|
||||
|
||||
return <Codespan text={t.text} />
|
||||
}
|
||||
|
||||
if (t.type === "br") {
|
||||
|
|
@ -244,12 +341,12 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
|
|||
)
|
||||
}
|
||||
|
||||
export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocationForApply }: { string: string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation }) => {
|
||||
export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocation }: { string: string, nested?: boolean, chatMessageLocation: ChatMessageLocation | undefined }) => {
|
||||
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
|
||||
return (
|
||||
<>
|
||||
{tokens.map((token, index) => (
|
||||
<RenderToken key={index} token={token} nested={nested} chatMessageLocationForApply={chatMessageLocationForApply} tokenIdx={index + ''} />
|
||||
<RenderToken key={index} token={token} nested={nested} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -936,19 +936,6 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx }: ChatB
|
|||
if (isEmpty) return null
|
||||
|
||||
return <>
|
||||
|
||||
{/* reasoning token */}
|
||||
{hasReasoning && <DropdownComponent
|
||||
title="Reasoning"
|
||||
desc1=""
|
||||
icon={<Dot className='stroke-blue-500' />}
|
||||
>
|
||||
<ChatMarkdownRender
|
||||
string={reasoningStr}
|
||||
chatMessageLocationForApply={chatMessageLocation}
|
||||
/>
|
||||
</DropdownComponent>}
|
||||
|
||||
<div
|
||||
className='
|
||||
text-void-fg-2
|
||||
|
|
@ -977,14 +964,26 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx }: ChatB
|
|||
'
|
||||
>
|
||||
|
||||
{/* reasoning token */}
|
||||
{hasReasoning && <DropdownComponent
|
||||
title="Reasoning"
|
||||
desc1=""
|
||||
icon={<Dot className='stroke-blue-500' />}
|
||||
>
|
||||
<ChatMarkdownRender
|
||||
string={reasoningStr}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
/>
|
||||
</DropdownComponent>}
|
||||
|
||||
{/* assistant message */}
|
||||
<ChatMarkdownRender
|
||||
string={chatMessage.content || ''}
|
||||
chatMessageLocationForApply={chatMessageLocation}
|
||||
chatMessageLocation={chatMessageLocation}
|
||||
/>
|
||||
|
||||
{isLoading && <IconLoading className='opacity-50 text-sm mx-4' />}
|
||||
|
||||
{/* loading indicator */}
|
||||
{isLoading && <IconLoading className='opacity-50 text-sm' />}
|
||||
</div>
|
||||
</>
|
||||
|
||||
|
|
@ -1122,25 +1121,28 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
numResults={value.uris.length}
|
||||
icon={<Dot className={`stroke-orange-500`} />}
|
||||
>
|
||||
{value.uris.map((uri, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
|
||||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', uri, { preview: true })
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
|
||||
{uri.fsPath.split('/').pop()}
|
||||
</div>
|
||||
))
|
||||
{
|
||||
value.uris.map((uri, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
|
||||
onClick={() => {
|
||||
commandService.executeCommand('vscode.open', uri, { preview: true })
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
|
||||
{uri.fsPath.split('/').pop()}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{value.hasNextPage && (
|
||||
<div className="italic">
|
||||
More results available...
|
||||
</div>
|
||||
)}
|
||||
</DropdownComponent>
|
||||
{
|
||||
value.hasNextPage && (
|
||||
<div className="italic">
|
||||
More results available...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</DropdownComponent >
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -1232,8 +1234,8 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
return <DropdownComponent title={title} desc1={getBasename(params.uri.fsPath)} icon={<Dot className={`stroke-orange-500`} />}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
>
|
||||
<ChatMarkdownRender string={params.changeDescription} />
|
||||
</DropdownComponent>
|
||||
<ChatMarkdownRender string={params.changeDescription} chatMessageLocation={undefined} />
|
||||
</DropdownComponent >
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -1278,7 +1280,7 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
// TODO!!! open terminal
|
||||
>
|
||||
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
|
||||
<ChatMarkdownRender string={''} />
|
||||
<ChatMarkdownRender string={''} chatMessageLocation={undefined} />
|
||||
</div>
|
||||
</DropdownComponent>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
|
|||
isPasswordField={isPasswordField}
|
||||
/>
|
||||
{subTextMd === undefined ? null : <div className='py-1 px-3 opacity-50 text-sm'>
|
||||
<ChatMarkdownRender string={subTextMd} />
|
||||
<ChatMarkdownRender string={subTextMd} chatMessageLocation={undefined} />
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
|
@ -421,11 +421,11 @@ export const FeaturesTab = () => {
|
|||
{/* <h3 className={`mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3> */}
|
||||
<h3 className={`text-void-fg-3 mb-2`}>{`Void can access any model that you host locally. We automatically detect your local models by default.`}</h3>
|
||||
<div className='pl-4 prose-ol:list-decimal opacity-80'>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`2. Open your terminal.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender string={`3. Run \`ollama run llama3.1:8b\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`1. Download [Ollama](https://ollama.com/download).`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`2. Open your terminal.`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender string={`3. Run \`ollama run llama3.1:8b\`. This installs Meta's llama3.1 model which is best for chat and inline edits. Requires 5GB of memory.`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2 select-text`}><ChatMarkdownRender string={`4. Run \`ollama run qwen2.5-coder:1.5b\`. This installs a faster autocomplete model. Requires 1GB of memory.`} chatMessageLocation={undefined} /></span>
|
||||
<span className={`text-sm mb-2`}><ChatMarkdownRender string={`Void automatically detects locally running models and enables them.`} chatMessageLocation={undefined} /></span>
|
||||
{/* TODO we should create UI for downloading models without user going into terminal */}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue