This commit is contained in:
Andrew Pareles 2024-11-08 02:50:52 -08:00
parent 3fd4357503
commit 932e9fe9c6
14 changed files with 179 additions and 213 deletions

View file

@ -30,6 +30,12 @@ class MetricsService extends Disposable implements IMetricsService {
console.debug('deviceId', deviceId) console.debug('deviceId', deviceId)
posthog.identify(deviceId) posthog.identify(deviceId)
// export const captureEvent = (eventId: string, properties: object) => {
// posthog.capture(eventId, properties)
// }
} }
} }

View file

@ -205,8 +205,14 @@ export type VoidConfig = {
const VOID_CONFIG_KEY = 'void.partialVoidConfig' const VOID_CONFIG_KEY = 'void.partialVoidConfig'
type setFieldType = <K extends VoidConfigField>(field: K, param: keyof VoidConfigInfo[K], newVal: string) => Promise<void>;
export interface IVoidSettingsService { export interface IVoidSettingsService {
readonly _serviceBrand: undefined; readonly _serviceBrand: undefined;
onDidChange: Event<void>;
getPartialVoidConfig(): Promise<PartialVoidConfig>;
getVoidConfig(): Promise<VoidConfig>;
setField: setFieldType;
} }
export const IVoidSettingsService = createDecorator<IVoidSettingsService>('voidSettingsService'); export const IVoidSettingsService = createDecorator<IVoidSettingsService>('voidSettingsService');
@ -248,7 +254,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
// Set field on PartialVoidConfig // Set field on PartialVoidConfig
async setField<K extends VoidConfigField>(field: K, param: keyof VoidConfigInfo[K], newVal: string) { setField: setFieldType = async <K extends VoidConfigField>(field: K, param: keyof VoidConfigInfo[K], newVal: string) => {
const partialVoidConfig = await this.getPartialVoidConfig() const partialVoidConfig = await this.getPartialVoidConfig()
const newPartialConfig: PartialVoidConfig = { const newPartialConfig: PartialVoidConfig = {

View file

@ -37,6 +37,8 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { IVoidSettingsService } from './registerSettings.js'; import { IVoidSettingsService } from './registerSettings.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
// compare against search.contribution.ts and https://app.greptile.com/chat/w1nsmt3lauwzculipycpn?repo=github%3Amain%3Amicrosoft%2Fvscode // compare against search.contribution.ts and https://app.greptile.com/chat/w1nsmt3lauwzculipycpn?repo=github%3Amain%3Amicrosoft%2Fvscode
@ -78,20 +80,40 @@ class VoidSidebarViewPane extends ViewPane {
protected override renderBody(parent: HTMLElement): void { protected override renderBody(parent: HTMLElement): void {
super.renderBody(parent); super.renderBody(parent);
const { root, history, chat, settings } = dom.h('div@root', [ // <div className={`flex flex-col h-screen w-full`}>
dom.h('div@history', []),
const { root, chat, history, settings } = dom.h('div@root', [
dom.h('div@chat', []), dom.h('div@chat', []),
dom.h('div@history', []),
dom.h('div@settings', []), dom.h('div@settings', []),
]) ])
root.style.display = 'flex'; root.style.display = 'flex';
root.style.flexDirection = 'column'; root.style.flexDirection = 'column';
root.style.height = '100vh'; root.style.height = '100vh';
root.style.width = '100%'; root.style.width = '100%';
dom.append(parent, root); dom.append(parent, root);
this._renderChat(chat);
this._renderHistory(history); this._renderHistory(history);
this._renderSettings(settings);
}
private _renderChat(element: HTMLElement) {
// <div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
// <SidebarChat chatInputRef={chatInputRef} />
// </div>
this._voidSidebarStateService.onDidChange(() => {
})
this._voidSidebarStateService.onFocusChat(() => {
})
this._voidSidebarStateService.onBlurChat(() => {
})
} }
@ -116,12 +138,6 @@ class VoidSidebarViewPane extends ViewPane {
} }
private _renderChat(element: HTMLElement) {
// <div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
// <SidebarChat chatInputRef={chatInputRef} />
// </div>
}
} }
@ -209,7 +225,7 @@ class VoidSidebarStateService extends Disposable implements IVoidSidebarStateSer
@IViewsService private readonly _viewsService: IViewsService, @IViewsService private readonly _viewsService: IViewsService,
) { ) {
super() super()
// auto open the view on mount (can view this as initializing state...) // auto open the view on mount (if it bothers you this is here, this is technically just initializing the state of the view)
this._viewsService.openView(SIDEBAR_VIEW_ID); this._viewsService.openView(SIDEBAR_VIEW_ID);
} }
@ -247,27 +263,26 @@ registerAction2(class extends Action2 {
const stateService = accessor.get(IVoidSidebarStateService) const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' }) stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
stateService.focusChat() stateService.focusChat()
const selection = accessor.get(IEditorService).activeTextEditorControl?.getSelection()
// chat state:
// // if user pressed ctrl+l, add their selection to the sidebar
// useOnVSCodeMessage('ctrl+l', (m) => {
// setSelection(m.selection)
// const filepath = m.selection.filePath
// // add current file to the context if it's not already in the files array
// if (!files.find(f => f.fsPath === filepath.fsPath))
// setFiles(files => [...files, filepath])
// })
} }
}); });
// History menu button
registerAction2(class extends Action2 {
constructor() {
super({
id: 'void.historyAction',
title: 'View past chats',
icon: { id: 'history' },
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', SIDEBAR_VIEW_ID), }]
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen })
stateService.blurChat()
}
})
// New chat menu button // New chat menu button
registerAction2(class extends Action2 { registerAction2(class extends Action2 {
constructor() { constructor() {
@ -288,6 +303,23 @@ registerAction2(class extends Action2 {
} }
}) })
// History menu button
registerAction2(class extends Action2 {
constructor() {
super({
id: 'void.historyAction',
title: 'View past chats',
icon: { id: 'history' },
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', SIDEBAR_VIEW_ID), }]
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: !stateService.state.isHistoryOpen })
stateService.blurChat()
}
})
// Settings (API config) menu button // Settings (API config) menu button
registerAction2(class extends Action2 { registerAction2(class extends Action2 {
constructor() { constructor() {

View file

@ -39,7 +39,7 @@ type ChatMessage =
// a "thread" means a chat message history // a "thread" means a chat message history
const createNewThread = () => { const newThreadObject = () => {
const now = new Date().toISOString() const now = new Date().toISOString()
return { return {
id: new Date().getTime().toString(), id: new Date().getTime().toString(),
@ -91,8 +91,17 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
startNewThread() { startNewThread() {
const newThread = createNewThread()
// if a thread with 0 messages already exists, switch to it
const currentThreads = this.getAllThreads() const currentThreads = this.getAllThreads()
for (let threadId in currentThreads) {
if (currentThreads[threadId].messages.length === 0) {
this.switchToThread(threadId)
return
}
}
const newThread = newThreadObject()
this._storeAllThreads({ this._storeAllThreads({
...currentThreads, ...currentThreads,
[newThread.id]: newThread [newThread.id]: newThread
@ -110,7 +119,7 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
currentThread = allThreads[this._currentThreadId] currentThread = allThreads[this._currentThreadId]
} }
else { else {
currentThread = createNewThread() currentThread = newThreadObject()
this._currentThreadId = currentThread.id this._currentThreadId = currentThread.id
} }

View file

@ -1,8 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.extension-test-runner"
]
}

View file

@ -1,22 +0,0 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--enable-proposed-api=void.void",
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}

View file

@ -1,18 +0,0 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"out": false,
"**/node_modules": true
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
}

View file

@ -1,20 +0,0 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View file

@ -1,63 +1,63 @@
import React, { useState, useRef } from '../void-imports/react.js' // import React, { useState, useRef } from '../void-imports/react.js'
import { SidebarThreadSelector } from './SidebarThreadSelector.js'; // import { SidebarThreadSelector } from './SidebarThreadSelector.js';
import { SidebarChat } from './SidebarChat.js'; // import { SidebarChat } from './SidebarChat.js';
import { SidebarSettings } from './SidebarSettings.js'; // import { SidebarSettings } from './SidebarSettings.js';
const Sidebar = () => { // const Sidebar = () => {
const chatInputRef = useRef<HTMLTextAreaElement | null>(null) // const chatInputRef = useRef<HTMLTextAreaElement | null>(null)
const [tab, setTab] = useState<'threadSelector' | 'chat' | 'settings'>('chat') // const [tab, setTab] = useState<'threadSelector' | 'chat' | 'settings'>('chat')
// // if they pressed the + to add a new chat // // // if they pressed the + to add a new chat
// useOnVSCodeMessage('startNewThread', (m) => { // // useOnVSCodeMessage('startNewThread', (m) => {
// setTab('chat'); // // setTab('chat');
// chatInputRef.current?.focus(); // // chatInputRef.current?.focus();
// }) // // })
// // ctrl+l should switch back to chat // // // ctrl+l should switch back to chat
// useOnVSCodeMessage('ctrl+l', (m) => { // // useOnVSCodeMessage('ctrl+l', (m) => {
// setTab('chat'); // // setTab('chat');
// chatInputRef.current?.focus(); // // chatInputRef.current?.focus();
// }) // // })
// // if they toggled thread selector // // // if they toggled thread selector
// useOnVSCodeMessage('toggleThreadSelector', (m) => { // // useOnVSCodeMessage('toggleThreadSelector', (m) => {
// if (tab === 'threadSelector') { // // if (tab === 'threadSelector') {
// setTab('chat') // // setTab('chat')
// chatInputRef.current?.blur(); // // chatInputRef.current?.blur();
// } else // // } else
// setTab('threadSelector') // // setTab('threadSelector')
// }) // // })
// // if they toggled settings // // // if they toggled settings
// useOnVSCodeMessage('toggleSettings', (m) => { // // useOnVSCodeMessage('toggleSettings', (m) => {
// if (tab === 'settings') { // // if (tab === 'settings') {
// setTab('chat') // // setTab('chat')
// chatInputRef.current?.blur(); // // chatInputRef.current?.blur();
// } else // // } else
// setTab('settings') // // setTab('settings')
// }) // // })
return <> // return <>
<div className={`flex flex-col h-screen w-full`}> // <div className={`flex flex-col h-screen w-full`}>
<div className={`mb-2 h-[30vh] ${tab !== 'threadSelector' ? 'hidden' : ''}`}> // <div className={`mb-2 h-[30vh] ${tab !== 'threadSelector' ? 'hidden' : ''}`}>
<SidebarThreadSelector onClose={() => setTab('chat')} /> // <SidebarThreadSelector onClose={() => setTab('chat')} />
</div> // </div>
<div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}> // <div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
<SidebarChat chatInputRef={chatInputRef} /> // <SidebarChat chatInputRef={chatInputRef} />
</div> // </div>
<div className={`${tab !== 'settings' ? 'hidden' : ''}`}> // <div className={`${tab !== 'settings' ? 'hidden' : ''}`}>
<SidebarSettings /> // <SidebarSettings />
</div> // </div>
</div> // </div>
</> // </>
} // }
export default Sidebar // export default Sidebar

View file

@ -147,6 +147,29 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HTMLTextAreaElement> }) => { export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HTMLTextAreaElement> }) => {
// // if they pressed the + to add a new chat
// useOnVSCodeMessage('startNewThread', (m) => {
// const allThreads = getAllThreads()
// // find a thread with 0 messages and switch to it
// for (let threadId in allThreads) {
// if (allThreads[threadId].messages.length === 0) {
// switchToThread(threadId)
// return
// }
// }
// // start a new thread
// startNewThread()
// })
// // if user pressed ctrl+l, add their selection to the sidebar
// useOnVSCodeMessage('ctrl+l', (m) => {
// setSelection(m.selection)
// const filepath = m.selection.filePath
// // add current file to the context if it's not already in the files array
// if (!files.find(f => f.fsPath === filepath.fsPath))
// setFiles(files => [...files, filepath])
// })
// state of current message // state of current message
const [selection, setSelection] = useState<CodeSelection | null>(null) // the code the user is selecting const [selection, setSelection] = useState<CodeSelection | null>(null) // the code the user is selecting
@ -167,29 +190,6 @@ export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HT
// if they pressed the + to add a new chat
useOnVSCodeMessage('startNewThread', (m) => {
const allThreads = getAllThreads()
// find a thread with 0 messages and switch to it
for (let threadId in allThreads) {
if (allThreads[threadId].messages.length === 0) {
switchToThread(threadId)
return
}
}
// start a new thread
startNewThread()
})
// if user pressed ctrl+l, add their selection to the sidebar
useOnVSCodeMessage('ctrl+l', (m) => {
setSelection(m.selection)
const filepath = m.selection.filePath
// add current file to the context if it's not already in the files array
if (!files.find(f => f.fsPath === filepath.fsPath))
setFiles(files => [...files, filepath])
})
const isDisabled = !instructions const isDisabled = !instructions

View file

@ -1,30 +1,30 @@
// renders the code from `src/sidebar` // // renders the code from `src/sidebar`
import * as vscode from 'vscode'; // import * as vscode from 'vscode';
import { updateWebviewHTML as _updateWebviewHTML } from './src/extension/extensionLib/updateWebviewHTML'; // import { updateWebviewHTML as _updateWebviewHTML } from './src/extension/extensionLib/updateWebviewHTML';
export class SidebarWebviewProvider implements vscode.WebviewViewProvider { // export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
public static readonly viewId = 'void.viewnumberone'; // public static readonly viewId = 'void.viewnumberone';
public webview: Promise<vscode.Webview> // used to send messages to the webview, resolved by _res in resolveWebviewView // public webview: Promise<vscode.Webview> // used to send messages to the webview, resolved by _res in resolveWebviewView
private _res: (c: vscode.Webview) => void // used to resolve the webview // private _res: (c: vscode.Webview) => void // used to resolve the webview
private readonly _extensionUri: vscode.Uri // private readonly _extensionUri: vscode.Uri
constructor(context: vscode.ExtensionContext) { // constructor(context: vscode.ExtensionContext) {
// const extensionPath = context.extensionPath // the directory where the extension is installed, might be useful later... was included in webviewProvider code // // const extensionPath = context.extensionPath // the directory where the extension is installed, might be useful later... was included in webviewProvider code
this._extensionUri = context.extensionUri // this._extensionUri = context.extensionUri
let temp_res: typeof this._res | undefined = undefined // let temp_res: typeof this._res | undefined = undefined
this.webview = new Promise((res, rej) => { temp_res = res }) // this.webview = new Promise((res, rej) => { temp_res = res })
if (!temp_res) throw new Error("Void sidebar provider: resolver was undefined") // if (!temp_res) throw new Error("Void sidebar provider: resolver was undefined")
this._res = temp_res // this._res = temp_res
} // }
// called internally by vscode // // called internally by vscode
resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken,) { // resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken,) {
const webview = webviewView.webview; // const webview = webviewView.webview;
_updateWebviewHTML(webview, this._extensionUri, { jsOutLocation: 'dist/webviews/sidebar/index.js', cssOutLocation: 'dist/webviews/styles.css' }) // _updateWebviewHTML(webview, this._extensionUri, { jsOutLocation: 'dist/webviews/sidebar/index.js', cssOutLocation: 'dist/webviews/styles.css' })
this._res(webview); // resolve webview and _webviewView // this._res(webview); // resolve webview and _webviewView
} // }
} // }

View file

@ -1,19 +0,0 @@
import posthog from '../void-imports/posthog-js.js'
export const identifyUser = (id: string) => {
posthog.identify(id)
}
export const captureEvent = (eventId: string, properties: object) => {
posthog.capture(eventId, properties)
}
export const initPosthog = () => {
// We send absolutely no code to the server. We only track usage metrics like button clicks, etc. This might change and we might eventually add an opt-in or opt-out.
posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2',
{
api_host: 'https://us.i.posthog.com',
person_profiles: 'identified_only' // we only track events from identified users. We identify them in Sidebar
}
)
}