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)
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'
type setFieldType = <K extends VoidConfigField>(field: K, param: keyof VoidConfigInfo[K], newVal: string) => Promise<void>;
export interface IVoidSettingsService {
readonly _serviceBrand: undefined;
onDidChange: Event<void>;
getPartialVoidConfig(): Promise<PartialVoidConfig>;
getVoidConfig(): Promise<VoidConfig>;
setField: setFieldType;
}
export const IVoidSettingsService = createDecorator<IVoidSettingsService>('voidSettingsService');
@ -248,7 +254,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
// 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 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 { IHoverService } from '../../../../platform/hover/browser/hover.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
@ -78,20 +80,40 @@ class VoidSidebarViewPane extends ViewPane {
protected override renderBody(parent: HTMLElement): void {
super.renderBody(parent);
const { root, history, chat, settings } = dom.h('div@root', [
dom.h('div@history', []),
// <div className={`flex flex-col h-screen w-full`}>
const { root, chat, history, settings } = dom.h('div@root', [
dom.h('div@chat', []),
dom.h('div@history', []),
dom.h('div@settings', []),
])
root.style.display = 'flex';
root.style.flexDirection = 'column';
root.style.height = '100vh';
root.style.width = '100%';
dom.append(parent, root);
this._renderChat(chat);
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,
) {
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);
}
@ -247,27 +263,26 @@ registerAction2(class extends Action2 {
const stateService = accessor.get(IVoidSidebarStateService)
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
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
registerAction2(class extends Action2 {
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
registerAction2(class extends Action2 {
constructor() {

View file

@ -39,7 +39,7 @@ type ChatMessage =
// a "thread" means a chat message history
const createNewThread = () => {
const newThreadObject = () => {
const now = new Date().toISOString()
return {
id: new Date().getTime().toString(),
@ -91,8 +91,17 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
startNewThread() {
const newThread = createNewThread()
// if a thread with 0 messages already exists, switch to it
const currentThreads = this.getAllThreads()
for (let threadId in currentThreads) {
if (currentThreads[threadId].messages.length === 0) {
this.switchToThread(threadId)
return
}
}
const newThread = newThreadObject()
this._storeAllThreads({
...currentThreads,
[newThread.id]: newThread
@ -110,7 +119,7 @@ class ThreadHistoryService extends Disposable implements IThreadHistoryService {
currentThread = allThreads[this._currentThreadId]
}
else {
currentThread = createNewThread()
currentThread = newThreadObject()
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 { SidebarThreadSelector } from './SidebarThreadSelector.js';
import { SidebarChat } from './SidebarChat.js';
import { SidebarSettings } from './SidebarSettings.js';
// import React, { useState, useRef } from '../void-imports/react.js'
// import { SidebarThreadSelector } from './SidebarThreadSelector.js';
// import { SidebarChat } from './SidebarChat.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
// useOnVSCodeMessage('startNewThread', (m) => {
// setTab('chat');
// chatInputRef.current?.focus();
// })
// // // if they pressed the + to add a new chat
// // useOnVSCodeMessage('startNewThread', (m) => {
// // setTab('chat');
// // chatInputRef.current?.focus();
// // })
// // ctrl+l should switch back to chat
// useOnVSCodeMessage('ctrl+l', (m) => {
// setTab('chat');
// chatInputRef.current?.focus();
// })
// // // ctrl+l should switch back to chat
// // useOnVSCodeMessage('ctrl+l', (m) => {
// // setTab('chat');
// // chatInputRef.current?.focus();
// // })
// // if they toggled thread selector
// useOnVSCodeMessage('toggleThreadSelector', (m) => {
// if (tab === 'threadSelector') {
// setTab('chat')
// chatInputRef.current?.blur();
// } else
// setTab('threadSelector')
// })
// // // if they toggled thread selector
// // useOnVSCodeMessage('toggleThreadSelector', (m) => {
// // if (tab === 'threadSelector') {
// // setTab('chat')
// // chatInputRef.current?.blur();
// // } else
// // setTab('threadSelector')
// // })
// // if they toggled settings
// useOnVSCodeMessage('toggleSettings', (m) => {
// if (tab === 'settings') {
// setTab('chat')
// chatInputRef.current?.blur();
// } else
// setTab('settings')
// })
// // // if they toggled settings
// // useOnVSCodeMessage('toggleSettings', (m) => {
// // if (tab === 'settings') {
// // setTab('chat')
// // chatInputRef.current?.blur();
// // } else
// // setTab('settings')
// // })
return <>
<div className={`flex flex-col h-screen w-full`}>
// return <>
// <div className={`flex flex-col h-screen w-full`}>
<div className={`mb-2 h-[30vh] ${tab !== 'threadSelector' ? 'hidden' : ''}`}>
<SidebarThreadSelector onClose={() => setTab('chat')} />
</div>
// <div className={`mb-2 h-[30vh] ${tab !== 'threadSelector' ? 'hidden' : ''}`}>
// <SidebarThreadSelector onClose={() => setTab('chat')} />
// </div>
<div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
<SidebarChat chatInputRef={chatInputRef} />
</div>
// <div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
// <SidebarChat chatInputRef={chatInputRef} />
// </div>
<div className={`${tab !== 'settings' ? 'hidden' : ''}`}>
<SidebarSettings />
</div>
// <div className={`${tab !== 'settings' ? 'hidden' : ''}`}>
// <SidebarSettings />
// </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> }) => {
// // 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
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

View file

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