diff --git a/.vscode/settings.json b/.vscode/settings.json index 00762ef..5bf2d1e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -75,12 +75,14 @@ "langchain", "langgraph", "Mhchem", + "multistream", "Nicepkg", "nodir", "Nolebase", "Ollama", "onnxruntime", "openai", + "pino", "Pipfile", "Pluggable", "pyproject", diff --git a/package.json b/package.json index 46361f5..4b98833 100644 --- a/package.json +++ b/package.json @@ -403,6 +403,7 @@ "apache-arrow": "^17.0.0", "autoprefixer": "^10.4.20", "babel-plugin-react-compiler": "latest", + "chalk": "^5.3.0", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a44982..d34be1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,6 +168,9 @@ importers: babel-plugin-react-compiler: specifier: latest version: 0.0.0-experimental-6067d4e-20240919 + chalk: + specifier: ^5.3.0 + version: 5.3.0 cheerio: specifier: ^1.0.0 version: 1.0.0 @@ -11870,7 +11873,7 @@ snapshots: '@vue/shared': 3.4.36 entities: 5.0.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 '@vue/compiler-dom@3.4.36': dependencies: @@ -11887,7 +11890,7 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.11 postcss: 8.4.47 - source-map-js: 1.2.0 + source-map-js: 1.2.1 '@vue/compiler-ssr@3.4.36': dependencies: diff --git a/src/extension/file-utils/paths.ts b/src/extension/file-utils/paths.ts index 5348378..cc6f5d5 100644 --- a/src/extension/file-utils/paths.ts +++ b/src/extension/file-utils/paths.ts @@ -1,49 +1,93 @@ +import crypto from 'crypto' import os from 'os' import path from 'path' -import JSONC from 'comment-json' +import { getWorkspaceFolder } from '@extension/utils' import fs from 'fs-extra' +import { VsCodeFS } from './vscode-fs' + const AIDE_DIR = process.env.AIDE_GLOBAL_DIR ?? path.join(os.homedir(), '.aide') -export class PathManager { - static ensureDir(dirPath: string): string { - fs.ensureDirSync(dirPath) - return dirPath - } +export const getExt = (filePath: string): string => + path.extname(filePath).slice(1) + +export class AidePaths { + private static instance: AidePaths - static getPath(...segments: string[]): string { - return PathManager.ensureDir(path.join(AIDE_DIR, ...segments)) + private aideDir: string + + private constructor() { + this.aideDir = AIDE_DIR } - static getFilePath(...segments: string[]): string { - return path.join(AIDE_DIR, ...segments) + public static getInstance(): AidePaths { + if (!AidePaths.instance) { + AidePaths.instance = new AidePaths() + } + return AidePaths.instance } - static writeJsonFile(filePath: string, data: any): void { - fs.writeFileSync(filePath, JSON.stringify(data, null, 2)) + private ensurePath(pathToEnsure: string, isDirectory: boolean): string { + if (isDirectory) { + fs.ensureDirSync(pathToEnsure) + } else { + fs.ensureFileSync(pathToEnsure) + } + return pathToEnsure } - static readJsonFile(filePath: string): any { - return JSONC.parse(fs.readFileSync(filePath, 'utf8')) + private joinAideGlobalPath( + isDirectory: boolean, + ...segments: string[] + ): string { + const fullPath = path.join(this.aideDir, ...segments) + return this.ensurePath(fullPath, isDirectory) } - static getExt(filePath: string): string { - return path.extname(filePath).slice(1) + private joinAideNamespacePath( + isDirectory: boolean, + ...segments: string[] + ): string { + const fullPath = path.join(this.aideDir, this.getNamespace(), ...segments) + return this.ensurePath(fullPath, isDirectory) } -} -export const Paths = { - aideDir: AIDE_DIR, - config: () => PathManager.getFilePath('index', 'config.json'), - sessionFile: (sessionId: string) => - PathManager.getFilePath('sessions', `${sessionId}.json`), - sessionsList: () => { - const filePath = PathManager.getFilePath('sessions', 'sessions.json') + getSessionFilePath = (sessionId: string) => + this.joinAideNamespacePath(false, 'sessions', `${sessionId}.json`) + + getSessionsListPath = async () => { + const filePath = this.joinAideNamespacePath( + false, + 'sessions', + 'sessions.json' + ) + if (!fs.existsSync(filePath)) { - PathManager.writeJsonFile(filePath, []) + await VsCodeFS.writeJsonFile(filePath, []) } + return filePath - }, - lanceDb: () => PathManager.getPath('index', 'lancedb'), - logs: () => PathManager.getPath('logs') + } + + getLanceDbPath = () => this.joinAideNamespacePath(true, 'lancedb') + + getLogsPath = () => this.joinAideNamespacePath(true, 'logs') + + getNamespace = () => { + const workspacePath = getWorkspaceFolder().uri.fsPath + + const workspaceName = path + .basename(workspacePath) + .replace(/[^a-zA-Z0-9]/g, '_') + + const workspaceFullPathHash = crypto + .createHash('md5') + .update(workspacePath) + .digest('hex') + .substring(0, 8) + + return `${workspaceName}_${workspaceFullPathHash}`.toLowerCase() + } } + +export const aidePaths = AidePaths.getInstance() diff --git a/src/extension/file-utils/vscode-fs.ts b/src/extension/file-utils/vscode-fs.ts index 82058b1..58c4986 100644 --- a/src/extension/file-utils/vscode-fs.ts +++ b/src/extension/file-utils/vscode-fs.ts @@ -1,4 +1,5 @@ /* eslint-disable unused-imports/no-unused-vars */ +import JSONC from 'comment-json' import * as vscode from 'vscode' export class VsCodeFS { @@ -105,4 +106,12 @@ export class VsCodeFS { return false } } + + static async writeJsonFile(filePath: string, data: any): Promise { + await this.writeFile(filePath, JSON.stringify(data, null, 2)) + } + + static async readJsonFile(filePath: string): Promise { + return JSONC.parse(await this.readFile(filePath, 'utf8')) as T + } } diff --git a/src/extension/logger.ts b/src/extension/logger.ts index 8132193..681d72a 100644 --- a/src/extension/logger.ts +++ b/src/extension/logger.ts @@ -1,250 +1,53 @@ +import * as path from 'path' +import { BaseLogger, type BaseLoggerOptions } from '@shared/utils/base-logger' import * as vscode from 'vscode' import { getContext } from './context' +import { aidePaths } from './file-utils/paths' +import { VsCodeFS } from './file-utils/vscode-fs' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -const isServer = typeof window === 'undefined' +export class VSCodeLogger extends BaseLogger { + private vscodeOutputChannel: vscode.OutputChannel -// Helper function to convert HSL to RGB -const hslToRgb = ( - h: number, - s: number, - l: number -): [number, number, number] => { - s /= 100 - l /= 100 + private _isDev: boolean | undefined - const c = (1 - Math.abs(2 * l - 1)) * s - const x = c * (1 - Math.abs(((h / 60) % 2) - 1)) - const m = l - c / 2 - - let r = 0 - let g = 0 - let b = 0 - - if (h >= 0 && h < 60) { - r = c - g = x - b = 0 - } else if (h >= 60 && h < 120) { - r = x - g = c - b = 0 - } else if (h >= 120 && h < 180) { - r = 0 - g = c - b = x - } else if (h >= 180 && h < 240) { - r = 0 - g = x - b = c - } else if (h >= 240 && h < 300) { - r = x - g = 0 - b = c - } else if (h >= 300 && h < 360) { - r = c - g = 0 - b = x + constructor(options: BaseLoggerOptions) { + super(options) + this.vscodeOutputChannel = vscode.window.createOutputChannel(options.name, { + log: true + }) } - r = Math.round((r + m) * 255) - g = Math.round((g + m) * 255) - b = Math.round((b + m) * 255) - - return [r, g, b] -} - -interface HSLColor { - h: number - s: number - l: number -} - -export interface LoggerOptions { - vscodeOutputName: string - label?: string - enableDebug?: boolean - logStorageFlagName?: string - isDevLogger?: boolean - hslColor?: HSLColor -} -/** - * Logger class to log messages with a label and color - * @example - * const logger = new Logger('MyComponent'); - * logger.log('This is a log message'); - */ -export class Logger { - label: string | undefined - - hslColor: HSLColor | undefined - - color: string | undefined - - vscodeOutputChannel: vscode.OutputChannel - - protected enableDebug: boolean - - protected isDevLogger: boolean - - private _isDev!: boolean - - protected getIsDev() { + protected isDev(): boolean { if (this._isDev === undefined) { const context = getContext() this._isDev = context ? context.extensionMode !== vscode.ExtensionMode.Production - : (undefined as any) + : false } - return this._isDev } - constructor(optionsOrLabel: LoggerOptions | string) { - const options: LoggerOptions = - (typeof optionsOrLabel === 'string' - ? { label: optionsOrLabel, vscodeOutputName: optionsOrLabel } - : optionsOrLabel) || {} - - const { enableDebug, label, vscodeOutputName, isDevLogger, hslColor } = - options - - this.vscodeOutputChannel = vscode.window.createOutputChannel( - vscodeOutputName, - { - log: true - } - ) - this.label = label - this.color = this.calculateColor(label, hslColor) - this.hslColor = hslColor - this.enableDebug = enableDebug ?? true - this.isDevLogger = isDevLogger ?? false - } - - private calculateColor = ( - label?: string, - hslConfig?: HSLColor - ): string | undefined => { - if (!label) return undefined - - let hash = 0 - for (let i = 0; i < label.length; i++) { - hash = (hash << 5) - hash + label.charCodeAt(i) - hash |= 0 // Convert to 32bit integer - } - - const hue = hslConfig?.h ?? hash % 360 - const saturation = hslConfig?.s ?? 50 - const lightness = hslConfig?.l ?? 50 - - if (isServer) { - // For Node.js, return ANSI color code - return `\x1b[38;2;${hslToRgb(hue, saturation, lightness).join(';')}m` - } - // For browser, return CSS color string - return `hsl(${hue}, ${saturation}%, ${lightness}%)` - } - - private getLabelWithColor = (): string => { - if (isServer) { - return `${this.color}[${this.label}]\x1b[0m` - } - return `[${this.label}]` + protected outputLog(message: string): void { + this.vscodeOutputChannel.appendLine(message) } - private logToVscodeOutputChannel: typeof this.logWithColor = ( - method, - ...args - ) => { - this.vscodeOutputChannel.appendLine( - args - .map(arg => - typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg) - ) - .join(' ') - ) - } + async saveLogsToFile(): Promise { + const logsDir = aidePaths.getLogsPath() + const timestamp = new Date().toISOString().replace(/[:.]/g, '-') + const fileName = `aide_logs_${timestamp}.log` + const filePath = path.join(logsDir, fileName) - private logToConsole: typeof this.logWithColor = (method, ...args) => { - if (!this.label) { - ;(console as any)[method](...args) - return - } + const logContent = this.logBuffer.join('\n') - const labelWithColor = this.getLabelWithColor() - const logArgs = [labelWithColor, ...args] + await VsCodeFS.writeFile(filePath, logContent) - if (isServer) { - ;(console as any)[method](...logArgs) - } else { - ;(console as any)[method]( - `%c${labelWithColor}`, - `color: ${this.color}`, - ...args - ) - } - } - - // Check if logging should occur - shouldLog = () => this.enableDebug && (!this.isDevLogger || this.getIsDev()) - - // Enable logging - enable = () => { - this.enableDebug = true - } - - // Disable logging - disable = () => { - this.enableDebug = false - } - - private logWithColor = ( - method: 'log' | 'warn' | 'error' | 'debug', - ...args: any[] - ) => { - if (!this.shouldLog()) return - this.logToVscodeOutputChannel(method, ...args) - this.logToConsole(method, ...args) - } - - log = (...args: any[]) => { - this.logWithColor('log', ...args) - } - - warn = (...args: any[]) => { - this.logWithColor('warn', ...args) - } - - error = (...args: any[]) => { - this.logWithColor('error', ...args) - } - - verbose = (...args: any[]) => { - this.logWithColor('debug', ...args) - } - - get dev(): Logger { - if (this.isDevLogger) return this - return new Logger({ - label: this.label ? `${this.label}:dev` : 'dev', - vscodeOutputName: this.vscodeOutputChannel.name, - enableDebug: this.enableDebug, - isDevLogger: true, - hslColor: this.hslColor - }) + this.log(`Logs saved to: ${filePath}`) } - destroy = () => { + destroy(): void { this.vscodeOutputChannel.dispose() - this.dev.vscodeOutputChannel.dispose() } } -export const logger = new Logger({ - label: 'Aide', - vscodeOutputName: 'Aide', - hslColor: { h: 260, s: 80, l: 68 } -}) +export const logger = new VSCodeLogger({ name: 'Aide', level: 'info' }) diff --git a/src/extension/webview-api/chat-context-processor/tree-sitter/code-chunker.ts b/src/extension/webview-api/chat-context-processor/tree-sitter/code-chunker.ts index 0c53031..7219cc6 100644 --- a/src/extension/webview-api/chat-context-processor/tree-sitter/code-chunker.ts +++ b/src/extension/webview-api/chat-context-processor/tree-sitter/code-chunker.ts @@ -1,5 +1,5 @@ import path from 'path' -import { PathManager } from '@extension/file-utils/paths' +import { getExt } from '@extension/file-utils/paths' import { encodingForModel, type Tiktoken } from 'js-tiktoken' import Parser from 'web-tree-sitter' @@ -25,6 +25,7 @@ export interface TextChunk { interface ChunkOptions { maxTokenLength: number + removeDuplicates?: boolean } export class CodeChunkerManager { @@ -55,7 +56,7 @@ export class CodeChunkerManager { } async getChunkerFromFilePath(filePath: string): Promise { - const ext = PathManager.getExt(filePath).toLowerCase() + const ext = getExt(filePath).toLowerCase() const language = treeSitterExtLanguageMap[ext] if (language && copilotQueriesSupportedExt.includes(ext)) { @@ -155,14 +156,20 @@ export class CodeChunker { } } + let processedChunks = chunks + // Remove duplicates if the option is set + if (options.removeDuplicates) { + processedChunks = this.removeDuplicateChunks(chunks) + } + // Sort chunks by their position in the original code - chunks.sort( + processedChunks.sort( (a, b) => a.range.startLine - b.range.startLine || a.range.startColumn - b.range.startColumn ) - return chunks + return processedChunks } // for other ext files @@ -275,6 +282,35 @@ export class CodeChunker { } } + private removeDuplicateChunks(chunks: TextChunk[]): TextChunk[] { + // Sort chunks by size (text length) in descending order + const sortedChunks = [...chunks].sort( + (a, b) => b.text.length - a.text.length + ) + + const result: TextChunk[] = [] + for (const chunk of sortedChunks) { + const isContained = result.some(existingChunk => + this.isRangeContained(chunk.range, existingChunk.range) + ) + if (!isContained) { + result.push(chunk) + } + } + + return result + } + + private isRangeContained(inner: Range, outer: Range): boolean { + return ( + (inner.startLine > outer.startLine || + (inner.startLine === outer.startLine && + inner.startColumn >= outer.startColumn)) && + (inner.endLine < outer.endLine || + (inner.endLine === outer.endLine && inner.endColumn <= outer.endColumn)) + ) + } + private countTokens(text: string): number { return this.tokenizer.encode(text).length } diff --git a/src/extension/webview-api/chat-context-processor/vectordb/code-chunks-index-table.ts b/src/extension/webview-api/chat-context-processor/vectordb/code-chunks-index-table.ts index 996d6a3..278887b 100644 --- a/src/extension/webview-api/chat-context-processor/vectordb/code-chunks-index-table.ts +++ b/src/extension/webview-api/chat-context-processor/vectordb/code-chunks-index-table.ts @@ -1,4 +1,4 @@ -import { Paths } from '@extension/file-utils/paths' +import { aidePaths } from '@extension/file-utils/paths' import { logger } from '@extension/logger' import { Field, @@ -31,7 +31,8 @@ export class CodeChunksIndexTable { async initialize() { try { - this.lanceDb = await connect(Paths.lanceDb()) + const lanceDbDir = aidePaths.getLanceDbPath() + this.lanceDb = await connect(lanceDbDir) logger.log('LanceDB initialized successfully') } catch (error) { logger.error('Failed to initialize LanceDB:', error) diff --git a/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts b/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts index d54d7dd..0eef58c 100644 --- a/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts +++ b/src/extension/webview-api/chat-context-processor/vectordb/codebase-indexer.ts @@ -1,7 +1,7 @@ import crypto from 'crypto' import { MAX_EMBEDDING_TOKENS } from '@extension/constants' import { createShouldIgnore } from '@extension/file-utils/ignore-patterns' -import { PathManager } from '@extension/file-utils/paths' +import { getExt } from '@extension/file-utils/paths' import { traverseFileOrFolders } from '@extension/file-utils/traverse-fs' import { VsCodeFS } from '@extension/file-utils/vscode-fs' import { logger } from '@extension/logger' @@ -114,7 +114,7 @@ export class CodebaseIndexer { ...Object.keys(treeSitterExtLanguageMap), ...languageIdExts ]) - return allowExt.has(PathManager.getExt(filePath)!.toLowerCase()) + return allowExt.has(getExt(filePath)!.toLowerCase()) } // for handle change use @@ -188,7 +188,8 @@ export class CodebaseIndexer { const chunker = await manager.getChunkerFromFilePath(filePath) const content = await VsCodeFS.readFile(filePath) const chunks = await chunker.chunkCode(content, { - maxTokenLength: MAX_EMBEDDING_TOKENS + maxTokenLength: MAX_EMBEDDING_TOKENS, + removeDuplicates: true }) return chunks @@ -197,7 +198,9 @@ export class CodebaseIndexer { private async createCodeChunkRows(filePath: string): Promise { const chunks = await this.chunkCodeFile(filePath) - // console.log('chunks', filePath, chunks) + const table = await this.databaseManager.getOrCreateTable() + + logger.log('chunks', chunks, await table.filter('true').execute()) const chunkRowsPromises = chunks.map(async chunk => { const embedding = await this.embeddings.embedQuery(chunk.text) @@ -220,6 +223,8 @@ export class CodebaseIndexer { chunkRowsResults.forEach(result => { if (result.status === 'fulfilled') { chunkRows.push(result.value) + } else { + logger.warn('Error creating code chunk row:', result.reason) } }) diff --git a/src/shared/utils/base-logger.ts b/src/shared/utils/base-logger.ts new file mode 100644 index 0000000..a1caaf8 --- /dev/null +++ b/src/shared/utils/base-logger.ts @@ -0,0 +1,142 @@ +import chalk from 'chalk' + +chalk.level = 3 + +export interface BaseLoggerOptions { + name: string + level?: string + isDevLogger?: boolean +} + +export abstract class BaseLogger { + protected loggerName: string + + protected level: string + + protected isDevLogger: boolean + + protected logBuffer: string[] = [] + + constructor(options: BaseLoggerOptions) { + const { name, level = 'info', isDevLogger = false } = options + this.loggerName = name + this.level = level + this.isDevLogger = isDevLogger + } + + protected abstract isDev(): boolean + protected abstract outputLog(message: string): void + + private getColoredLevel(level: string): string { + switch (level) { + case 'info': + return chalk.green('INFO') + case 'warn': + return chalk.yellow('WARN') + case 'error': + return chalk.red('ERROR') + case 'debug': + return chalk.blue('DEBUG') + default: + return level.toUpperCase() + } + } + + private stringifyIfObject(value: any): string { + if (typeof value === 'object' && value !== null) { + return JSON.stringify(value) + } + return String(value) + } + + private shouldLog(): boolean { + return this.level !== 'silent' && (!this.isDevLogger || this.isDev()) + } + + protected formatLogMetadata(level: string): { + coloredLevel: string + dateTime: string + loggerName: string + } { + const coloredLevel = this.getColoredLevel(level) + const dateTime = new Date().toISOString().split('T')[1]?.split('.')[0] + const loggerName = chalk.magenta(`[${this.loggerName}]`) + return { coloredLevel, dateTime: `[${dateTime}]`, loggerName } + } + + protected formatLogForSave(level: string, ...messages: any[]): string { + const { coloredLevel, dateTime, loggerName } = this.formatLogMetadata(level) + + return `${loggerName} ${coloredLevel} ${chalk.green(dateTime)} ${messages + .map(msg => this.stringifyIfObject(msg)) + .join(' ')}` + } + + protected formatLogForConsoleLog(level: string, ...messages: any[]): any[] { + const { coloredLevel, dateTime, loggerName } = this.formatLogMetadata(level) + + return [ + `${loggerName} ${coloredLevel} ${chalk.green(dateTime)}`, + ...messages + ] + } + + private logMethod(level: string, ...messages: any[]): void { + if (this.shouldLog()) { + const formattedLogForSave = this.formatLogForSave(level, ...messages) + const formattedLogForConsole = this.formatLogForConsoleLog( + level, + ...messages + ) + + // eslint-disable-next-line no-console + console.log(...formattedLogForConsole) + this.outputLog(formattedLogForSave) + this.logBuffer.push(formattedLogForSave) + + // Keep only last 30 minutes of logs + const thirtyMinutesAgo = Date.now() - 30 * 60 * 1000 + this.logBuffer = this.logBuffer.filter(log => { + const timeStr = log.split('[')[1]?.split(']')[0] + if (!timeStr) return false + const logTime = new Date(`1970-01-01T${timeStr}Z`).getTime() + return logTime >= thirtyMinutesAgo + }) + } + } + + log(...messages: any[]): void { + this.logMethod('info', ...messages) + } + + warn(...messages: any[]): void { + this.logMethod('warn', ...messages) + } + + error(...messages: any[]): void { + this.logMethod('error', ...messages) + } + + verbose(...messages: any[]): void { + this.logMethod('debug', ...messages) + } + + setLevel(level: string): void { + this.level = level + } + + get dev(): BaseLogger { + if (this.isDevLogger) return this + const DevLogger = this.constructor as new ( + options: BaseLoggerOptions + ) => BaseLogger + return new DevLogger({ + name: `${this.loggerName}:dev`, + level: this.level, + isDevLogger: true + }) + } + + abstract saveLogsToFile(): Promise + abstract destroy(): void +} diff --git a/src/webview/components/chat/sidebar/chat-sidebar.tsx b/src/webview/components/chat/sidebar/chat-sidebar.tsx index aff6eaf..aad3df3 100644 --- a/src/webview/components/chat/sidebar/chat-sidebar.tsx +++ b/src/webview/components/chat/sidebar/chat-sidebar.tsx @@ -3,6 +3,7 @@ import React from 'react' import { ArchiveIcon, PlusIcon } from '@radix-ui/react-icons' import { ButtonWithTooltip } from '@webview/components/button-with-tooltip' import { Button } from '@webview/components/ui/button' +import { logger } from '@webview/utils/logger' interface ChatHistoryItem { id: string @@ -19,7 +20,7 @@ export const ChatSidebar: React.FC = () => { const chatHistory = useChatHistory() const handleNewChat = () => { - console.log('New chat created') + logger.log('New chat created') } const handleArchiveChat = (id: string) => { diff --git a/src/webview/components/theme-sync.tsx b/src/webview/components/theme-sync.tsx index 6d66aef..4dfb579 100644 --- a/src/webview/components/theme-sync.tsx +++ b/src/webview/components/theme-sync.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react' +import { logger } from '@webview/utils/logger' import { hsla, parseToHsla } from 'color2k' const THEME_CHECK_INTERVAL = 100 @@ -99,7 +100,7 @@ const syncTheme = () => { ) } } catch (error) { - console.warn(`Failed to parse color for ${key}: ${value}`, error) + logger.warn(`Failed to parse color for ${key}: ${value}`, error) } } }) diff --git a/src/webview/lexical/hooks/use-mention-manager.ts b/src/webview/lexical/hooks/use-mention-manager.ts index 0ad21af..e2d0fb1 100644 --- a/src/webview/lexical/hooks/use-mention-manager.ts +++ b/src/webview/lexical/hooks/use-mention-manager.ts @@ -4,6 +4,7 @@ import type { Conversation, IMentionStrategy } from '@webview/types/chat' +import { logger } from '@webview/utils/logger' import type { Updater } from 'use-immer' export interface UseMentionManagerProps { @@ -47,7 +48,7 @@ export function useMentionManager(props: UseMentionManagerProps) { updateCurrentAttachments(updatedAttachments) } } catch (error) { - console.warn('Error adding mention:', error) + logger.warn('Error adding mention:', error) } } diff --git a/src/webview/utils/logger.ts b/src/webview/utils/logger.ts new file mode 100644 index 0000000..876274f --- /dev/null +++ b/src/webview/utils/logger.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { BaseLogger } from '@shared/utils/base-logger' + +export class WebviewLogger extends BaseLogger { + protected isDev(): boolean { + return process.env.NODE_ENV !== 'production' + } + + // eslint-disable-next-line unused-imports/no-unused-vars + protected outputLog(message: string): void { + // send the log back to the VSCode extension + // this.vscode.postMessage({ + // type: 'log', + // message + // }) + } + + async saveLogsToFile(): Promise {} + + destroy(): void {} +} + +export const logger = new WebviewLogger({ name: 'Aide.webview', level: 'info' })