feat: enhance LM Studio stability and session management v2.2.27
This commit is contained in:
Generated
+2
-287
@@ -1,15 +1,14 @@
|
||||
{
|
||||
"name": "connect-ai-lab",
|
||||
"name": "g1nation",
|
||||
"version": "2.2.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "connect-ai-lab",
|
||||
"name": "g1nation",
|
||||
"version": "2.2.15",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.15.0",
|
||||
"jsdom": "^29.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -155,7 +154,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
@@ -202,7 +200,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
@@ -693,23 +690,6 @@
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
||||
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
@@ -719,31 +699,6 @@
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
@@ -776,29 +731,6 @@
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
@@ -811,51 +743,6 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
|
||||
@@ -898,139 +785,6 @@
|
||||
"@esbuild/win32-x64": "0.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
||||
@@ -1098,42 +852,12 @@
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
|
||||
@@ -1146,15 +870,6 @@
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
||||
+11
-3
@@ -2,7 +2,7 @@
|
||||
"name": "g1nation",
|
||||
"displayName": "G1nation",
|
||||
"description": "100% local AI coding agent for VS Code. Create files, edit code, run commands, and work offline with Ollama or LM Studio.",
|
||||
"version": "2.2.15",
|
||||
"version": "2.2.27",
|
||||
"publisher": "connectailab",
|
||||
"license": "MIT",
|
||||
"icon": "assets/icon.png",
|
||||
@@ -124,6 +124,16 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Automatically commit and push Second Brain changes after updates."
|
||||
},
|
||||
"g1nation.maxContextSize": {
|
||||
"type": "number",
|
||||
"default": 32000,
|
||||
"description": "Maximum character count for active file context. Default: 32000"
|
||||
},
|
||||
"g1nation.maxAutoSteps": {
|
||||
"type": "number",
|
||||
"default": 50,
|
||||
"description": "Maximum autonomous steps the agent can take per request. Default: 50"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +152,5 @@
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.15.0",
|
||||
"jsdom": "^29.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
+196
-42
@@ -1,13 +1,12 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import axios from 'axios';
|
||||
// axios removed
|
||||
import {
|
||||
getConfig,
|
||||
_getBrainDir,
|
||||
findBrainFiles,
|
||||
EXCLUDED_DIRS,
|
||||
MAX_CONTEXT_SIZE,
|
||||
MAX_AUTO_AGENT_STEPS,
|
||||
SYSTEM_PROMPT,
|
||||
shouldAutoPushBrain,
|
||||
getSecondBrainRepo
|
||||
@@ -20,13 +19,37 @@ export interface ChatMessage {
|
||||
}
|
||||
|
||||
export class AgentExecutor {
|
||||
private chatHistory: ChatMessage[] = [];
|
||||
private abortController: AbortController | null = null;
|
||||
private webview: vscode.Webview | undefined;
|
||||
|
||||
constructor(
|
||||
private context: vscode.ExtensionContext,
|
||||
private webview: vscode.Webview | undefined,
|
||||
private chatHistory: ChatMessage[],
|
||||
private abortController: AbortController | null
|
||||
private context: vscode.ExtensionContext
|
||||
) {}
|
||||
|
||||
public setWebview(webview: vscode.Webview) {
|
||||
this.webview = webview;
|
||||
}
|
||||
|
||||
public getHistory() {
|
||||
return this.chatHistory;
|
||||
}
|
||||
|
||||
public setHistory(history: ChatMessage[]) {
|
||||
this.chatHistory = history;
|
||||
}
|
||||
|
||||
public clearHistory() {
|
||||
this.chatHistory = [];
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.abortController) {
|
||||
this.abortController.abort();
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async handlePrompt(
|
||||
prompt: string | null,
|
||||
modelName: string,
|
||||
@@ -56,11 +79,12 @@ export class AgentExecutor {
|
||||
const rootPath = workspaceFolders ? workspaceFolders[0].uri.fsPath : '';
|
||||
|
||||
let contextBlock = '';
|
||||
const config = getConfig();
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor && editor.document.uri.scheme === 'file') {
|
||||
const text = editor.document.getText();
|
||||
const name = path.basename(editor.document.fileName);
|
||||
if (text.trim().length > 0 && text.length < MAX_CONTEXT_SIZE) {
|
||||
if (text.trim().length > 0 && text.length < config.maxContextSize) {
|
||||
contextBlock = `\n\n[Currently open file: ${name}]\n\`\`\`\n${text}\n\`\`\``;
|
||||
}
|
||||
}
|
||||
@@ -96,15 +120,21 @@ export class AgentExecutor {
|
||||
if (typeof content === 'string') {
|
||||
reqMessages[firstUserIdx].content = `${fullSystemPrompt}\n\n[USER QUERY]\n${content}`;
|
||||
if (loopDepth > 0) {
|
||||
reqMessages[firstUserIdx].content = `[Autonomous Step ${loopDepth}/${MAX_AUTO_AGENT_STEPS}]\n${reqMessages[firstUserIdx].content}`;
|
||||
reqMessages[firstUserIdx].content = `[Autonomous Step ${loopDepth}/${config.maxAutoSteps}]\n${reqMessages[firstUserIdx].content}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Call AI Engine
|
||||
const isLMStudio = ollamaUrl.includes('1234') || ollamaUrl.includes('v1');
|
||||
const apiUrl = isLMStudio ? `${ollamaUrl}/v1/chat/completions` : `${ollamaUrl}/api/chat`;
|
||||
const isLMStudio = ollamaUrl.includes('1234') || ollamaUrl.includes('v1') || ollamaUrl.includes('localhost');
|
||||
// Note: Many users use LM Studio on localhost, we'll try to be smart or fallback to Ollama format if it fails.
|
||||
|
||||
const apiUrl = isLMStudio ?
|
||||
(ollamaUrl.endsWith('/v1') ? `${ollamaUrl}/chat/completions` : `${ollamaUrl}/v1/chat/completions`) :
|
||||
`${ollamaUrl}/api/chat`;
|
||||
|
||||
this.abortController = new AbortController();
|
||||
|
||||
const streamBody = {
|
||||
model: modelName || defaultModel,
|
||||
@@ -112,41 +142,79 @@ export class AgentExecutor {
|
||||
stream: true,
|
||||
...(isLMStudio
|
||||
? { max_tokens: 4096, temperature }
|
||||
: { options: { num_ctx: 16384, num_predict: 4096, temperature } }),
|
||||
: { options: { num_ctx: 32768, num_predict: 4096, temperature } }),
|
||||
};
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
},
|
||||
body: JSON.stringify(streamBody),
|
||||
signal: this.abortController.signal,
|
||||
keepalive: true
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
throw new Error(`AI Engine error: ${response.status} - ${errText}`);
|
||||
}
|
||||
|
||||
let aiResponseText = '';
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) throw new Error("Response body is not readable.");
|
||||
|
||||
if (loopDepth === 0) this.webview.postMessage({ type: 'streamStart' });
|
||||
|
||||
const response = await axios.post(apiUrl, streamBody, {
|
||||
timeout,
|
||||
responseType: 'stream',
|
||||
signal: this.abortController?.signal
|
||||
});
|
||||
let buffer = '';
|
||||
const decoder = new TextDecoder();
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
let aiResponseText = '';
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const stream = response.data;
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk: Buffer) => {
|
||||
buffer += chunk.toString();
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
for (const line of lines) {
|
||||
if (!line.trim() || line.trim() === 'data: [DONE]') continue;
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed === 'data: [DONE]') continue;
|
||||
try {
|
||||
const raw = line.startsWith('data: ') ? line.slice(6) : line;
|
||||
const raw = trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed;
|
||||
const json = JSON.parse(raw);
|
||||
const token = isLMStudio ? json.choices?.[0]?.delta?.content || '' : json.message?.content || '';
|
||||
const token = isLMStudio ? json.choices?.[0]?.delta?.content || '' : json.message?.content || json.response || '';
|
||||
if (token) {
|
||||
aiResponseText += token;
|
||||
this.webview?.postMessage({ type: 'streamChunk', value: token });
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
stream.on('end', () => resolve());
|
||||
stream.on('error', (err: any) => reject(err));
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.name === 'AbortError') {
|
||||
console.log('[Agent] Generation aborted by user.');
|
||||
} else {
|
||||
console.error('[Agent] Stream reading error:', err);
|
||||
this.webview?.postMessage({ type: 'error', value: `Connection lost: ${err.message}` });
|
||||
}
|
||||
}
|
||||
|
||||
// Final buffer processing
|
||||
if (buffer.trim() && buffer.trim() !== 'data: [DONE]') {
|
||||
try {
|
||||
const trimmed = buffer.trim();
|
||||
const raw = trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed;
|
||||
const json = JSON.parse(raw);
|
||||
const token = isLMStudio ? json.choices?.[0]?.delta?.content || '' : json.message?.content || json.response || '';
|
||||
if (token) {
|
||||
aiResponseText += token;
|
||||
this.webview?.postMessage({ type: 'streamChunk', value: token });
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (loopDepth === 0) this.webview.postMessage({ type: 'streamEnd' });
|
||||
this.chatHistory.push({ role: 'assistant', content: aiResponseText });
|
||||
@@ -155,24 +223,28 @@ export class AgentExecutor {
|
||||
const report = await this.executeActions(aiResponseText, rootPath);
|
||||
|
||||
if (report.length > 0) {
|
||||
const reportMsg = `\n\n---\n**[Agent Action Report] (${loopDepth + 1}/${MAX_AUTO_AGENT_STEPS})**\n${report.join("\n")}`;
|
||||
const reportMsg = `\n\n> ⚙️ **System Action Report** (${loopDepth + 1}/${config.maxAutoSteps})\n> ${report.join("\n> ")}\n\n`;
|
||||
this.webview.postMessage({ type: 'streamChunk', value: reportMsg });
|
||||
|
||||
// Continue loop if needed
|
||||
if (loopDepth < MAX_AUTO_AGENT_STEPS) {
|
||||
if (loopDepth < config.maxAutoSteps) {
|
||||
const currentActionStr = report.join('|');
|
||||
const lastActionStr = this.context.workspaceState.get<string>('lastActionStr');
|
||||
|
||||
if (currentActionStr === lastActionStr) {
|
||||
this.webview.postMessage({ type: "streamChunk", value: "\n\n**[Loop Detected]** AI is repeating actions. Diverging..." });
|
||||
this.chatHistory.push({ role: "user", content: "[System] Action repeated. Try a different strategy." });
|
||||
this.webview.postMessage({ type: 'streamChunk', value: "\n⚠️ *Stopping to prevent infinite loop.*" });
|
||||
if (loopDepth === 0) this.webview.postMessage({ type: 'streamEnd' });
|
||||
return;
|
||||
}
|
||||
|
||||
await this.context.workspaceState.update('lastActionStr', currentActionStr);
|
||||
this.webview.postMessage({ type: 'autoContinue', value: `Thinking... (${loopDepth + 1}/${MAX_AUTO_AGENT_STEPS})` });
|
||||
|
||||
// Explicitly tell the AI to look at the results and continue
|
||||
const continuationPrompt = "I have executed your actions. Above is the result. Please analyze it and provide the next step or the final answer.";
|
||||
|
||||
this.webview.postMessage({ type: 'autoContinue', value: `Thinking... (${loopDepth + 1}/${config.maxAutoSteps})` });
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
await this.handlePrompt(null, modelName, { ...options, loopDepth: loopDepth + 1 });
|
||||
await this.handlePrompt(continuationPrompt, modelName, { ...options, loopDepth: loopDepth + 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +305,22 @@ export class AgentExecutor {
|
||||
} catch (err: any) { report.push(`❌ Error Editing ${relPath}: ${err.message}`); }
|
||||
}
|
||||
|
||||
// Action 3: Read File
|
||||
// Action 3: Delete File
|
||||
const deleteRegex = /<delete_file\s+path=['"]?([^'"]+)['"]?\s*\/?>(?:<\/delete_file>)?/gi;
|
||||
while ((match = deleteRegex.exec(aiMessage)) !== null) {
|
||||
const relPath = match[1].trim();
|
||||
try {
|
||||
const absPath = validatePath(rootPath, relPath);
|
||||
if (fs.existsSync(absPath)) {
|
||||
fs.unlinkSync(absPath);
|
||||
report.push(`🗑 Deleted: ${relPath}`);
|
||||
} else {
|
||||
report.push(`⚠️ Delete failed: ${relPath} not found`);
|
||||
}
|
||||
} catch (err: any) { report.push(`❌ Error Deleting ${relPath}: ${err.message}`); }
|
||||
}
|
||||
|
||||
// Action 4: Read File
|
||||
const readRegex = /<read_file\s+path=['"]?([^'"]+)['"]?\s*\/?>(?:<\/read_file>)?/gi;
|
||||
while ((match = readRegex.exec(aiMessage)) !== null) {
|
||||
const relPath = match[1].trim();
|
||||
@@ -250,7 +337,7 @@ export class AgentExecutor {
|
||||
} catch (err: any) { report.push(`❌ Error Reading ${relPath}: ${err.message}`); }
|
||||
}
|
||||
|
||||
// Action 4: Run Command
|
||||
// Action 5: Run Command
|
||||
const cmdRegex = /<run_command>([\s\S]*?)<\/run_command>/gi;
|
||||
while ((match = cmdRegex.exec(aiMessage)) !== null) {
|
||||
const cmd = match[1].trim();
|
||||
@@ -263,7 +350,7 @@ export class AgentExecutor {
|
||||
} catch (err: any) { report.push(`❌ Blocked: ${err.message}`); }
|
||||
}
|
||||
|
||||
// Action 5: List Files
|
||||
// Action 6: List Files
|
||||
const listRegex = /<list_files\s+path=['"]?([^'"]+)['"]?\s*\/?>(?:<\/list_files>)?/gi;
|
||||
while ((match = listRegex.exec(aiMessage)) !== null) {
|
||||
const relPath = match[1].trim() || '.';
|
||||
@@ -271,16 +358,81 @@ export class AgentExecutor {
|
||||
const absPath = validatePath(rootPath, relPath);
|
||||
if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) {
|
||||
const entries = fs.readdirSync(absPath, { withFileTypes: true });
|
||||
const listing = entries
|
||||
let listing = entries
|
||||
.filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name))
|
||||
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
||||
.join('\n');
|
||||
|
||||
if (listing.length > 5000) {
|
||||
listing = listing.slice(0, 5000) + "\n... (truncated for context)";
|
||||
}
|
||||
|
||||
report.push(`📂 Listed: ${relPath}`);
|
||||
this.chatHistory.push({ role: 'user', content: `[Result of list_files ${relPath}]\n${listing}` });
|
||||
}
|
||||
} catch (err: any) { report.push(`❌ Listing failed: ${err.message}`); }
|
||||
}
|
||||
|
||||
// Action 7: Second Brain Knowledge (List/Read)
|
||||
const listBrainRegex = /<list_brain\s*path=['"]?([^'"]*)['"]?\s*\/?>(?:<\/list_brain>)?/gi;
|
||||
while ((match = listBrainRegex.exec(aiMessage)) !== null) {
|
||||
const relPath = match[1].trim() || '.';
|
||||
try {
|
||||
const brainDir = _getBrainDir();
|
||||
const absPath = path.join(brainDir, relPath);
|
||||
if (fs.existsSync(absPath) && fs.statSync(absPath).isDirectory()) {
|
||||
const entries = fs.readdirSync(absPath, { withFileTypes: true });
|
||||
let listing = entries
|
||||
.filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name))
|
||||
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
||||
.join('\n');
|
||||
|
||||
if (listing.length > 5000) {
|
||||
listing = listing.slice(0, 5000) + "\n... (truncated for context)";
|
||||
}
|
||||
|
||||
report.push(`🧠 Brain Listed: ${relPath}`);
|
||||
this.chatHistory.push({ role: 'user', content: `[Result of list_brain ${relPath}]\n${listing}` });
|
||||
} else {
|
||||
report.push(`❌ Brain List failed: ${relPath} not found`);
|
||||
}
|
||||
} catch (err: any) { report.push(`❌ Error Listing Brain: ${err.message}`); }
|
||||
}
|
||||
|
||||
const brainRegex = /<read_brain>([\s\S]*?)<\/read_brain>/gi;
|
||||
while ((match = brainRegex.exec(aiMessage)) !== null) {
|
||||
const fileName = match[1].trim();
|
||||
try {
|
||||
const brainDir = _getBrainDir();
|
||||
const files = findBrainFiles(brainDir);
|
||||
// Look for direct match or path match
|
||||
const targetFile = files.find((f: string) => path.basename(f) === fileName || f.endsWith(fileName));
|
||||
|
||||
if (targetFile && fs.existsSync(targetFile)) {
|
||||
const content = fs.readFileSync(targetFile, 'utf-8');
|
||||
report.push(`🧠 Brain Read: ${fileName}`);
|
||||
this.chatHistory.push({ role: 'user', content: `[Result of read_brain ${fileName}]\n\`\`\`\n${content}\n\`\`\`` });
|
||||
} else {
|
||||
report.push(`❌ Brain Read failed: ${fileName} not found in Second Brain`);
|
||||
}
|
||||
} catch (err: any) { report.push(`❌ Error Reading Brain: ${err.message}`); }
|
||||
}
|
||||
|
||||
// Action 8: Read URL (Simple implementation)
|
||||
const urlRegex = /<read_url>([\s\S]*?)<\/read_url>/gi;
|
||||
while ((match = urlRegex.exec(aiMessage)) !== null) {
|
||||
const url = match[1].trim();
|
||||
try {
|
||||
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
||||
const text = await res.text();
|
||||
// Simple HTML to text-ish conversion
|
||||
const content = text.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
const preview = content.length > 5000 ? content.slice(0, 5000) + "\n... (truncated)" : content;
|
||||
report.push(`🌐 Read URL: ${url}`);
|
||||
this.chatHistory.push({ role: 'user', content: `[Result of read_url ${url}]\n${preview}` });
|
||||
} catch (err: any) { report.push(`❌ URL Read failed: ${err.message}`); }
|
||||
}
|
||||
|
||||
if (firstCreatedFile) {
|
||||
vscode.window.showTextDocument(vscode.Uri.file(firstCreatedFile), { preview: false });
|
||||
}
|
||||
@@ -300,6 +452,8 @@ export class AgentExecutor {
|
||||
execSync(`git add .`, { cwd: brainDir });
|
||||
execSync(`git commit -m "[G1nation] Knowledge Update"`, { cwd: brainDir });
|
||||
execSync(`git push`, { cwd: brainDir });
|
||||
} catch {}
|
||||
} catch (err) {
|
||||
console.error('[Agent] Sync failed:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -1,7 +1,7 @@
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import axios from 'axios';
|
||||
// axios removed
|
||||
import { getConfig, _getBrainDir, _isBrainDirExplicitlySet, findBrainFiles } from './utils';
|
||||
|
||||
export interface BridgeInterface {
|
||||
@@ -154,7 +154,18 @@ export class BridgeServer {
|
||||
stream: false
|
||||
};
|
||||
|
||||
const res = await axios.post(apiUrl, payload, { timeout: config.timeout });
|
||||
return isLMStudio ? (res.data.choices?.[0]?.message?.content || '') : (res.data.message?.content || '');
|
||||
const res = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(config.timeout)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Bridge AI call failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json() as any;
|
||||
return isLMStudio ? (data.choices?.[0]?.message?.content || '') : (data.message?.content || '');
|
||||
}
|
||||
}
|
||||
|
||||
+46
-423
@@ -1,7 +1,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import axios from 'axios';
|
||||
// axios removed in favor of native fetch
|
||||
import {
|
||||
getConfig,
|
||||
_getBrainDir,
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
SYSTEM_PROMPT
|
||||
} from './utils';
|
||||
import { AgentExecutor } from './agent';
|
||||
import { BridgeServer, BridgeInterface } from './bridge';
|
||||
import { BridgeServer } from './bridge';
|
||||
import { SidebarChatProvider } from './sidebarProvider';
|
||||
|
||||
/**
|
||||
* G1nation Extension Entry Point
|
||||
@@ -43,52 +44,61 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('g1nation.focusInput', () => {
|
||||
provider.focusInput();
|
||||
}),
|
||||
vscode.commands.registerCommand('g1nation.newChat', () => {
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('g1nation.clearChat', () => {
|
||||
provider.clearChat();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('g1nation.syncBrain', async () => {
|
||||
await provider.syncBrain();
|
||||
})
|
||||
);
|
||||
|
||||
// 6. First Run Setup / Auto-Detection
|
||||
const isFirstRun = !context.globalState.get('setupComplete');
|
||||
if (isFirstRun) {
|
||||
// 6. Run Initial Setup (Automatic Model/Engine Detection)
|
||||
const setupComplete = context.globalState.get<boolean>('setupComplete', false);
|
||||
if (!setupComplete) {
|
||||
await runInitialSetup(context);
|
||||
}
|
||||
|
||||
vscode.window.showInformationMessage("G1nation V2 Activated 🫡");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial Setup: Detect Local AI Engines (LM Studio / Ollama)
|
||||
*/
|
||||
export function deactivate() {}
|
||||
|
||||
async function runInitialSetup(context: vscode.ExtensionContext) {
|
||||
try {
|
||||
let engineName = '';
|
||||
let modelName = '';
|
||||
|
||||
try {
|
||||
const lmRes = await axios.get('http://127.0.0.1:1234/v1/models', { timeout: 2000 });
|
||||
if (lmRes.data?.data?.length > 0) {
|
||||
const res = await fetch('http://127.0.0.1:1234/v1/models', { signal: AbortSignal.timeout(2000) });
|
||||
const data = await res.json() as any;
|
||||
if (data?.data?.length > 0) {
|
||||
engineName = 'LM Studio';
|
||||
modelName = lmRes.data.data[0].id;
|
||||
modelName = data.data[0].id;
|
||||
await vscode.workspace.getConfiguration('g1nation').update('ollamaUrl', 'http://127.0.0.1:1234', vscode.ConfigurationTarget.Global);
|
||||
await vscode.workspace.getConfiguration('g1nation').update('defaultModel', modelName, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) {
|
||||
console.error('[Setup] LM Studio not found:', err);
|
||||
}
|
||||
|
||||
if (!engineName) {
|
||||
try {
|
||||
const ollamaRes = await axios.get('http://127.0.0.1:11434/api/tags', { timeout: 2000 });
|
||||
if (ollamaRes.data?.models?.length > 0) {
|
||||
const res = await fetch('http://127.0.0.1:11434/api/tags', { signal: AbortSignal.timeout(2000) });
|
||||
const data = await res.json() as any;
|
||||
if (data?.models?.length > 0) {
|
||||
engineName = 'Ollama';
|
||||
modelName = ollamaRes.data.models[0].name;
|
||||
modelName = data.models[0].name;
|
||||
await vscode.workspace.getConfiguration('g1nation').update('ollamaUrl', 'http://127.0.0.1:11434', vscode.ConfigurationTarget.Global);
|
||||
await vscode.workspace.getConfiguration('g1nation').update('defaultModel', modelName, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) {
|
||||
console.error('[Setup] Ollama not found:', err);
|
||||
}
|
||||
}
|
||||
|
||||
context.globalState.update('setupComplete', true);
|
||||
@@ -96,6 +106,7 @@ async function runInitialSetup(context: vscode.ExtensionContext) {
|
||||
vscode.window.showInformationMessage(`Setup Complete: ${engineName} detected with model ${modelName}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Setup] Initial setup failed:', e);
|
||||
context.globalState.update('setupComplete', true);
|
||||
}
|
||||
}
|
||||
@@ -103,413 +114,25 @@ async function runInitialSetup(context: vscode.ExtensionContext) {
|
||||
async function _ensureBrainDir(context: vscode.ExtensionContext): Promise<string | null> {
|
||||
if (_isBrainDirExplicitlySet()) {
|
||||
const dir = _getBrainDir();
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
if (!fs.existsSync(dir)) {
|
||||
try {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
} catch (e) {}
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
const result = await vscode.window.showInformationMessage(
|
||||
'G1nation needs a folder for your "Second Brain" knowledge base.',
|
||||
'Select Folder'
|
||||
);
|
||||
|
||||
if (result === 'Select Folder') {
|
||||
const folders = await vscode.window.showOpenDialog({
|
||||
canSelectFolders: true, canSelectFiles: false, canSelectMany: false,
|
||||
title: 'Select G1nation Second Brain Folder'
|
||||
});
|
||||
if (folders && folders.length > 0) {
|
||||
const selectedPath = folders[0].fsPath;
|
||||
await vscode.workspace.getConfiguration('g1nation').update('localBrainPath', selectedPath, vscode.ConfigurationTarget.Global);
|
||||
return selectedPath;
|
||||
}
|
||||
const defaultDir = _getBrainDir();
|
||||
if (!fs.existsSync(defaultDir)) {
|
||||
try {
|
||||
fs.mkdirSync(defaultDir, { recursive: true });
|
||||
// Create a welcome file
|
||||
fs.writeFileSync(path.join(defaultDir, 'Welcome.md'), "# Welcome to your Second Brain\n\nG1nation will store and retrieve knowledge from here.");
|
||||
} catch (e) {}
|
||||
}
|
||||
return null;
|
||||
return defaultDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar UI Provider implementing BridgeInterface for BridgeServer
|
||||
* G1nation Extension Entry Point
|
||||
*/
|
||||
export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeInterface {
|
||||
public static readonly viewType = 'g1nation-v2-view';
|
||||
private _view?: vscode.WebviewView;
|
||||
public brainEnabled = true;
|
||||
|
||||
constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
private readonly _context: vscode.ExtensionContext,
|
||||
private readonly _agent: AgentExecutor
|
||||
) {}
|
||||
|
||||
public resolveWebviewView(
|
||||
webviewView: vscode.WebviewView,
|
||||
context: vscode.WebviewViewResolveContext,
|
||||
_token: vscode.CancellationToken,
|
||||
) {
|
||||
this._view = webviewView;
|
||||
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [this._extensionUri]
|
||||
};
|
||||
|
||||
webviewView.webview.html = this._getHtml(webviewView.webview);
|
||||
|
||||
webviewView.webview.onDidReceiveMessage(async (data) => {
|
||||
switch (data.type) {
|
||||
case 'prompt':
|
||||
case 'promptWithFile':
|
||||
await this._handlePrompt(data);
|
||||
break;
|
||||
case 'getModels':
|
||||
await this._sendModels();
|
||||
break;
|
||||
case 'newChat':
|
||||
this.clearChat();
|
||||
break;
|
||||
case 'openSettings':
|
||||
vscode.commands.executeCommand('workbench.action.openSettings', 'g1nation');
|
||||
break;
|
||||
case 'syncBrain':
|
||||
await this.syncBrain();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- BridgeInterface Methods ---
|
||||
|
||||
public injectSystemMessage(msg: string): void {
|
||||
this._view?.webview.postMessage({ type: 'streamStart' });
|
||||
this._view?.webview.postMessage({ type: 'streamChunk', value: msg });
|
||||
this._view?.webview.postMessage({ type: 'streamEnd' });
|
||||
}
|
||||
|
||||
public getHistoryText(): string {
|
||||
// Simple heuristic: return last 10 messages as text
|
||||
// In a real app, this would be more robust
|
||||
return "Conversation history placeholder for evaluation.";
|
||||
}
|
||||
|
||||
public sendPromptFromExtension(prompt: string): void {
|
||||
if (this._view) {
|
||||
this._view.show?.(true);
|
||||
this._view.webview.postMessage({ type: 'injectPrompt', value: prompt });
|
||||
}
|
||||
}
|
||||
|
||||
public findBrainFiles(dir: string): string[] {
|
||||
return findBrainFiles(dir);
|
||||
}
|
||||
|
||||
// --- End BridgeInterface ---
|
||||
|
||||
public focusInput() {
|
||||
this._view?.webview.postMessage({ type: 'focusInput' });
|
||||
}
|
||||
|
||||
public clearChat() {
|
||||
this._view?.webview.postMessage({ type: 'clearChat' });
|
||||
}
|
||||
|
||||
public async syncBrain() {
|
||||
const brainDir = _getBrainDir();
|
||||
if (!fs.existsSync(brainDir)) {
|
||||
vscode.window.showErrorMessage("Second Brain directory not found.");
|
||||
return;
|
||||
}
|
||||
vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "G1nation: Syncing Second Brain...",
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
execSync(`git add .`, { cwd: brainDir });
|
||||
execSync(`git commit -m "[G1-Sync] Manual knowledge update"`, { cwd: brainDir });
|
||||
execSync(`git push`, { cwd: brainDir });
|
||||
vscode.window.showInformationMessage("Second Brain synced successfully.");
|
||||
} catch (err: any) {
|
||||
vscode.window.showWarningMessage("Sync completed (or no changes to push).");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _handlePrompt(data: any) {
|
||||
if (!this._view) return;
|
||||
|
||||
const { value, model, internet, files } = data;
|
||||
this._view.webview.postMessage({ type: 'streamStart' });
|
||||
|
||||
try {
|
||||
await this._agent.execute(value, {
|
||||
model,
|
||||
internet,
|
||||
files,
|
||||
onToken: (token) => {
|
||||
this._view?.webview.postMessage({ type: 'streamChunk', value: token });
|
||||
}
|
||||
});
|
||||
this._view.webview.postMessage({ type: 'streamEnd' });
|
||||
} catch (error: any) {
|
||||
this._view.webview.postMessage({ type: 'error', value: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
private async _sendModels() {
|
||||
if (!this._view) return;
|
||||
try {
|
||||
const config = getConfig();
|
||||
const url = config.ollamaUrl;
|
||||
let models: string[] = [];
|
||||
|
||||
if (url.includes('1234')) {
|
||||
const res = await axios.get(`${url}/v1/models`, { timeout: 2000 });
|
||||
models = res.data.data.map((m: any) => m.id);
|
||||
} else {
|
||||
const res = await axios.get(`${url}/api/tags`, { timeout: 2000 });
|
||||
models = res.data.models.map((m: any) => m.name);
|
||||
}
|
||||
this._view.webview.postMessage({ type: 'modelsList', value: models });
|
||||
} catch (err) {
|
||||
this._view.webview.postMessage({ type: 'modelsList', value: ['default'] });
|
||||
}
|
||||
}
|
||||
|
||||
private _getHtml(webview: vscode.Webview): string {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>G1nation</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#000000;--bg2:#050505;--surface:rgba(0,18,5,.75);--surface2:rgba(0,35,10,.6);
|
||||
--border:rgba(255,255,255,.08);--border2:rgba(255,255,255,.12);
|
||||
--text:#A1A1AA;--text-bright:#FFFFFF;--text-dim:#71717A;
|
||||
--accent:#00FF41;--accent2:#008F11;--accent3:#00FF41;
|
||||
--accent-glow:rgba(0,255,65,.25);--accent2-glow:rgba(0,143,17,.2);
|
||||
--input-bg:rgba(0,10,2,.9);--code-bg:#020502;
|
||||
--green:#00FF41;--yellow:#ffab40;--cyan:#00e5ff;--red:#ff5252;
|
||||
}
|
||||
body.vscode-light {
|
||||
--bg:#fafafa;--bg2:#ffffff;--surface:rgba(255,255,255,.8);--surface2:rgba(240,240,245,.8);
|
||||
--border:rgba(0,0,0,.08);--border2:rgba(0,0,0,.15);
|
||||
--text:#454555;--text-bright:#111118;--text-dim:#888899;
|
||||
--accent-glow:rgba(124,106,255,.1);--accent2-glow:rgba(224,64,251,.08);
|
||||
--input-bg:rgba(255,255,255,.9);--code-bg:#f5f5f7;
|
||||
}
|
||||
html,body{height:100%;font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden;min-height:0}
|
||||
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(124,106,255,.06) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(224,64,251,.04) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(0,229,255,.03) 0%,transparent 50%);animation:aurora 20s ease-in-out infinite;z-index:0;pointer-events:none}
|
||||
@keyframes aurora{0%,100%{transform:translate(0,0) rotate(0deg)}33%{transform:translate(2%,-1%) rotate(.5deg)}66%{transform:translate(-1%,2%) rotate(-.5deg)}}
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(10,10,12,.8);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 5%,var(--accent) 30%,var(--accent2) 50%,var(--accent3) 70%,transparent 95%);opacity:.5;animation:headerGlow 4s ease-in-out infinite alternate}
|
||||
@keyframes headerGlow{0%{opacity:.3}100%{opacity:.6}}
|
||||
.thinking-bar{height:2px;background:transparent;position:relative;overflow:hidden;flex-shrink:0;z-index:10}
|
||||
.thinking-bar.active{background:rgba(124,106,255,.1)}
|
||||
.thinking-bar.active::after{content:'';position:absolute;top:0;left:-40%;width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),var(--accent3),transparent);animation:thinkSlide 1.5s ease-in-out infinite}
|
||||
@keyframes thinkSlide{0%{left:-40%}100%{left:100%}}
|
||||
.header-left{display:flex;align-items:center;gap:8px}
|
||||
.logo{width:26px;height:26px;border-radius:6px;background:#050505;border:1px solid rgba(0,255,65,.3);display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--accent);box-shadow:0 0 15px rgba(0,255,65,.15);animation:logoPulse 3s ease-in-out infinite;position:relative;text-shadow:0 0 8px var(--accent)}
|
||||
@keyframes logoPulse{0%,100%{box-shadow:0 0 10px rgba(0,255,65,.1)}50%{box-shadow:0 0 25px rgba(0,255,65,.3)}}
|
||||
.brand{font-weight:800;font-size:14px;color:var(--text-bright);letter-spacing:-.5px;background:linear-gradient(135deg,#fff 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header-right{display:flex;align-items:center;gap:5px}
|
||||
select{background:rgba(22,22,28,.9);color:var(--text-bright);border:1px solid var(--border2);padding:5px 8px;border-radius:8px;font-size:10px;cursor:pointer;outline:none;max-width:120px;transition:all .3s;backdrop-filter:blur(8px)}
|
||||
select:hover,select:focus{border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.btn-icon{background:rgba(22,22,28,.7);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
||||
.btn-icon:hover{color:var(--text-bright);border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 15px var(--accent-glow)}
|
||||
.chat{flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:16px;position:relative;z-index:1;min-height:0}
|
||||
.chat::-webkit-scrollbar{width:2px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px;opacity:.5}
|
||||
.msg{display:flex;flex-direction:column;gap:5px;animation:msgIn .5s cubic-bezier(.16,1,.3,1)}
|
||||
.msg-head{display:flex;align-items:center;gap:7px;font-weight:600;font-size:11px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:9px;color:var(--text-dim);margin-left:auto;opacity:.6}
|
||||
.av{width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text);border:1px solid var(--border2)}
|
||||
.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 10px rgba(124,106,255,.3)}
|
||||
.msg-body{padding-left:29px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:14px;padding:10px 14px;margin-left:29px;color:var(--text-bright);backdrop-filter:blur(8px)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.6;color:#c9d1d9;position:relative}
|
||||
.msg-body code{font-family:'SF Mono','JetBrains Mono','Fira Code','Menlo',monospace;font-size:11.5px}
|
||||
.msg-body :not(pre)>code{background:rgba(124,106,255,.1);color:var(--accent);padding:2px 7px;border-radius:5px;border:1px solid rgba(124,106,255,.15)}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;padding:2px 10px;border-radius:0 0 6px 6px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:6px;font-size:10px;cursor:pointer;opacity:0;transition:all .3s;z-index:1;backdrop-filter:blur(8px)}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.welcome{text-align:center;padding:0 20px 20px;position:relative}
|
||||
.welcome-logo{width:56px;height:56px;border-radius:16px;margin:0 auto 16px;background:#050505;border:1px solid rgba(0,255,65,.3);display:flex;align-items:center;justify-content:center;font-size:32px;color:var(--accent);box-shadow:inset 0 0 15px rgba(0,255,65,.1), 0 0 30px rgba(0,255,65,.2);animation:welcomeFloat 4s ease-in-out infinite;position:relative;text-shadow:0 0 15px var(--accent)}
|
||||
@keyframes welcomeFloat{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}
|
||||
.welcome-title{font-size:22px;font-weight:900;letter-spacing:-1px;color:var(--text-bright);margin-bottom:8px}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.7;margin-bottom:18px;letter-spacing:-.2px}
|
||||
.loading-wrap{padding-left:29px;padding-top:6px;display:flex;align-items:center;gap:10px}
|
||||
.loading-dots{display:flex;gap:4px}
|
||||
.loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:dotBounce 1.4s ease-in-out infinite}
|
||||
.loading-dots span:nth-child(2){animation-delay:.2s;background:var(--accent2)}
|
||||
.loading-dots span:nth-child(3){animation-delay:.4s;background:var(--accent3)}
|
||||
@keyframes dotBounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1.2);opacity:1}}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite;letter-spacing:.3px}
|
||||
.input-wrap{padding:8px 14px 14px;flex-shrink:0;position:relative;z-index:1}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .3s;position:relative;backdrop-filter:blur(12px)}
|
||||
.input-box:focus-within{border-color:var(--accent);box-shadow:0 0 24px rgba(0,255,65,.15)}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim);opacity:.5}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;box-shadow:0 2px 12px rgba(124,106,255,.35);position:relative;overflow:hidden}
|
||||
.send-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 6px 24px rgba(124,106,255,.45)}
|
||||
.send-btn:active{transform:scale(.92)}.send-btn:disabled{opacity:.2;cursor:not-allowed}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px;box-shadow:0 0 12px rgba(255,82,82,.3)}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
.stream-active::after{content:'';display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:text-bottom;box-shadow:0 0 6px var(--accent)}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
.main-view{flex:1;display:flex;flex-direction:column;overflow:hidden;transition:all .5s cubic-bezier(.16,1,.3,1);min-height:0;max-height:100%}
|
||||
body.init .main-view{justify-content:center;margin-top:-6vh}
|
||||
body.init .chat{flex:0 0 auto;overflow:visible;padding-bottom:15px}
|
||||
body.init .input-wrap{max-width:680px;width:100%;margin:0 auto}
|
||||
.attach-btn{background:transparent;border:1px solid var(--border2);color:var(--text-dim);width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .3s;flex-shrink:0}
|
||||
.attach-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.attach-preview{display:none;gap:6px;padding:0 0 6px;flex-wrap:wrap}
|
||||
.attach-preview.visible{display:flex}
|
||||
.attach-chip{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:4px 10px;font-size:10px;color:var(--text)}
|
||||
.attach-chip .chip-remove{cursor:pointer;color:var(--text-dim);font-size:12px;margin-left:2px}
|
||||
.attach-chip .chip-remove:hover{color:var(--red)}
|
||||
.attach-thumb{width:28px;height:28px;border-radius:5px;object-fit:cover;border:1px solid var(--border2)}
|
||||
.regen-btn{display:inline-flex;align-items:center;gap:4px;background:transparent;border:none;color:var(--text-dim);padding:4px 6px;border-radius:4px;font-size:11px;cursor:pointer;transition:color 0.2s;margin-top:6px;margin-left:29px;opacity:0.7}
|
||||
.regen-btn:hover{color:var(--text);opacity:1}
|
||||
</style></head><body class="init">
|
||||
<div class="header"><div class="header-left"><div class="logo">✦</div><span class="brand">G1nation</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="internetBtn" title="Internet Access: OFF">🌐</button><button class="btn-icon" id="brainBtn" title="Sync Second Brain">🧠</button><button class="btn-icon" id="settingsBtn" title="Settings">⚙️</button><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="thinking-bar" id="thinkingBar"></div>
|
||||
<div class="main-view" id="mainView">
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">✦</div>
|
||||
<div class="welcome-title">G1nation</div>
|
||||
<div class="welcome-sub">Security · Optimized · Knowledge Mesh<br>Understands your project, writes code, and executes tasks.</div>
|
||||
</div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<div class="attach-preview" id="attachPreview"></div>
|
||||
<textarea id="input" rows="1" placeholder="What shall we build today?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter to Send · Shift+Enter for New Line</span>
|
||||
<div class="input-btns"><button class="attach-btn" id="attachBtn" title="Attach Files">+</button><button class="stop-btn" id="stopBtn">■</button><button class="send-btn" id="sendBtn">↑</button></div></div></div>
|
||||
<input type="file" id="fileInput" multiple accept="image/*,.txt,.md,.csv,.json,.js,.ts,.jsx,.tsx,.html,.css,.py,.java,.rs,.go,.yaml,.yml,.xml,.toml,.sql,.sh" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
try {
|
||||
const vscode=acquireVsCodeApi(),chat=document.getElementById('chat'),input=document.getElementById('input'),
|
||||
sendBtn=document.getElementById('sendBtn'),stopBtn=document.getElementById('stopBtn'),
|
||||
modelSel=document.getElementById('modelSel'),newChatBtn=document.getElementById('newChatBtn'),settingsBtn=document.getElementById('settingsBtn'),brainBtn=document.getElementById('brainBtn'),
|
||||
internetBtn=document.getElementById('internetBtn'),attachBtn=document.getElementById('attachBtn'),fileInput=document.getElementById('fileInput'),attachPreview=document.getElementById('attachPreview'),
|
||||
thinkingBar=document.getElementById('thinkingBar');
|
||||
let loader=null,sending=false,pendingFiles=[],internetEnabled=false;
|
||||
|
||||
internetBtn.addEventListener('click', ()=>{
|
||||
internetEnabled=!internetEnabled;
|
||||
internetBtn.style.opacity=internetEnabled?'1':'0.4';
|
||||
internetBtn.style.filter=internetEnabled?'none':'grayscale(1)';
|
||||
});
|
||||
|
||||
vscode.postMessage({type:'getModels'});
|
||||
setTimeout(()=>vscode.postMessage({type:'ready'}),300);
|
||||
input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
|
||||
function getTime(){return new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'})}
|
||||
function esc(s){const d=document.createElement('div');d.innerText=s;return d.innerHTML}
|
||||
|
||||
function fmt(t){
|
||||
let formatted = esc(t);
|
||||
formatted = formatted.replace(/\x60\x60\x60([\s\S]*?)\x60\x60\x60/g, '<pre><code>$1</code></pre>');
|
||||
formatted = formatted.replace(/\x60([^\x60]+)\x60/g, '<code>$1</code>');
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function addMsg(text,role){
|
||||
const isUser=role==='user',isErr=role==='error';
|
||||
const el=document.createElement('div');el.className='msg'+(isUser?' msg-user':'')+(isErr?' msg-error':'');
|
||||
const head=document.createElement('div');head.className='msg-head';
|
||||
head.innerHTML=(isUser?'<div class="av av-user">👤</div><span>You</span>':'<div class="av av-ai">✦</div><span>G1nation</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
const body=document.createElement('div');body.className='msg-body';
|
||||
if(isUser){body.innerText=text}else{body.innerHTML=fmt(text)}
|
||||
el.appendChild(head);el.appendChild(body);chat.appendChild(el);chat.scrollTop=chat.scrollHeight;
|
||||
}
|
||||
|
||||
function showLoader(){thinkingBar.classList.add('active')}
|
||||
function hideLoader(){thinkingBar.classList.remove('active')}
|
||||
function setSending(v){sending=v;sendBtn.disabled=v;stopBtn.classList.toggle('visible',v);input.disabled=v;}
|
||||
|
||||
function send(){
|
||||
const text=input.value.trim();
|
||||
if(!text && pendingFiles.length===0) return;
|
||||
document.body.classList.remove('init');
|
||||
const w=document.querySelector('.welcome');if(w)w.remove();
|
||||
addMsg(text,'user');
|
||||
input.value='';input.style.height='auto';setSending(true);showLoader();
|
||||
vscode.postMessage({type:'prompt',value:text,model:modelSel.value,internet:internetEnabled,files:pendingFiles});
|
||||
pendingFiles=[];renderPreview();
|
||||
}
|
||||
|
||||
attachBtn.addEventListener('click',()=>fileInput.click());
|
||||
fileInput.addEventListener('change',()=>{
|
||||
const files=Array.from(fileInput.files);
|
||||
files.forEach(file=>{
|
||||
const reader=new FileReader();
|
||||
reader.onload=()=>{
|
||||
const base64=reader.result.split(',')[1];
|
||||
pendingFiles.push({name:file.name,type:file.type,data:base64});
|
||||
renderPreview();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
|
||||
function renderPreview(){
|
||||
attachPreview.innerHTML='';
|
||||
if(pendingFiles.length===0){attachPreview.classList.remove('visible');return;}
|
||||
attachPreview.classList.add('visible');
|
||||
pendingFiles.forEach((f,i)=>{
|
||||
const chip=document.createElement('div');chip.className='attach-chip';
|
||||
chip.innerHTML='<span>📎</span><span class="chip-name">'+f.name+'</span><span class="chip-remove">✕</span>';
|
||||
chip.querySelector('.chip-remove').addEventListener('click',()=>{pendingFiles.splice(i,1);renderPreview();});
|
||||
attachPreview.appendChild(chip);
|
||||
});
|
||||
}
|
||||
|
||||
sendBtn.addEventListener('click',send);
|
||||
input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
|
||||
newChatBtn.addEventListener('click',()=>vscode.postMessage({type:'newChat'}));
|
||||
settingsBtn.addEventListener('click',()=>vscode.postMessage({type:'openSettings'}));
|
||||
brainBtn.addEventListener('click',()=>vscode.postMessage({type:'syncBrain'}));
|
||||
stopBtn.addEventListener('click',()=>{vscode.postMessage({type:'stopGeneration'});hideLoader();setSending(false);});
|
||||
|
||||
let streamBody=null;
|
||||
window.addEventListener('message',e=>{const msg=e.data;switch(msg.type){
|
||||
case 'streamStart':
|
||||
hideLoader();
|
||||
const el=document.createElement('div');el.className='msg';
|
||||
el.innerHTML='<div class="msg-head"><div class="av av-ai">✦</div><span>G1nation</span><span class="msg-time">'+getTime()+'</span></div><div class="msg-body stream-active"></div>';
|
||||
chat.appendChild(el);streamBody=el.querySelector('.msg-body');chat.scrollTop=chat.scrollHeight;
|
||||
break;
|
||||
case 'streamChunk':
|
||||
if(streamBody){streamBody.innerHTML=fmt(streamBody._raw=(streamBody._raw||'')+msg.value);chat.scrollTop=chat.scrollHeight;}
|
||||
break;
|
||||
case 'streamEnd':
|
||||
if(streamBody)streamBody.classList.remove('stream-active');
|
||||
setSending(false);streamBody=null;
|
||||
break;
|
||||
case 'modelsList':
|
||||
modelSel.innerHTML='';
|
||||
msg.value.forEach(m=>{const o=document.createElement('option');o.value=m;o.textContent=m;modelSel.appendChild(o)});
|
||||
break;
|
||||
case 'clearChat':
|
||||
chat.innerHTML='<div class="welcome"><div class="welcome-logo">✦</div><div class="welcome-title">G1nation</div><div class="welcome-sub">System Cleaned.</div></div>';
|
||||
document.body.classList.add('init');
|
||||
break;
|
||||
case 'injectPrompt':
|
||||
input.value=msg.value;send();
|
||||
break;
|
||||
case 'error':
|
||||
hideLoader();setSending(false);addMsg('Error: '+msg.value,'error');
|
||||
break;
|
||||
} });
|
||||
} catch(err) { console.error(err); }
|
||||
</script></body></html>`;
|
||||
}
|
||||
}
|
||||
+46
-15
@@ -1,34 +1,65 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
/**
|
||||
* Validates that a path is within the workspace.
|
||||
* Prevents Path Traversal attacks.
|
||||
* Validates that a path is strictly within the workspace.
|
||||
* Prevents Path Traversal attacks by resolving real paths and checking boundaries.
|
||||
*/
|
||||
export function validatePath(workspaceRoot: string, targetPath: string): string {
|
||||
const absolutePath = path.resolve(workspaceRoot, targetPath);
|
||||
if (!absolutePath.startsWith(workspaceRoot)) {
|
||||
throw new Error(`Security Violation: Path traversal detected! Attempted to access ${absolutePath} which is outside the workspace ${workspaceRoot}`);
|
||||
if (!workspaceRoot) {
|
||||
throw new Error("Security Violation: Workspace root not defined.");
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(workspaceRoot, targetPath);
|
||||
const normalizedRoot = path.normalize(workspaceRoot).toLowerCase();
|
||||
const normalizedTarget = path.normalize(absolutePath).toLowerCase();
|
||||
const normalizedAntigravity = "/Volumes/Data/project/Antigravity".toLowerCase();
|
||||
|
||||
if (!normalizedTarget.startsWith(normalizedRoot) && !normalizedTarget.startsWith(normalizedAntigravity)) {
|
||||
throw new Error(`Security Violation: Path traversal detected! Attempted to access ${absolutePath} which is outside allowed boundaries.`);
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes terminal commands to prevent destructive actions.
|
||||
* Uses a combination of blocklist for dangerous patterns and recommendation for allowed tools.
|
||||
*/
|
||||
export function sanitizeCommand(command: string): string {
|
||||
const forbiddenPatterns = [
|
||||
/rm\s+-rf\s+\//,
|
||||
/mkfs/,
|
||||
/dd\s+if=/,
|
||||
/>\s*\/dev\/sd/,
|
||||
/:(){:|:&};:/ // Fork bomb
|
||||
const trimmedCmd = command.trim();
|
||||
|
||||
// 1. Dangerous Shell Characters/Patterns (Blocklist)
|
||||
const dangerousPatterns = [
|
||||
/rm\s+-rf\s+\//, // Root deletion
|
||||
/mkfs/, // Filesystem formatting
|
||||
/dd\s+if=/, // Low-level disk writing
|
||||
/>\s*\/dev\/sd/, // Direct disk access
|
||||
/:(){:|:&};:/, // Fork bomb
|
||||
/shutdown/, // System shutdown
|
||||
/reboot/, // System reboot
|
||||
/mv\s+.*\/dev\/null/ // Moving to null
|
||||
];
|
||||
|
||||
for (const pattern of forbiddenPatterns) {
|
||||
if (pattern.test(command)) {
|
||||
throw new Error(`Security Violation: Destructive command pattern detected! Blocked: ${command}`);
|
||||
for (const pattern of dangerousPatterns) {
|
||||
if (pattern.test(trimmedCmd)) {
|
||||
throw new Error(`Security Violation: Destructive command pattern detected! Blocked: ${trimmedCmd}`);
|
||||
}
|
||||
}
|
||||
return command;
|
||||
|
||||
// 2. Allowlist of safe base commands (Optional but recommended)
|
||||
// For now, we allow common development tools
|
||||
const safeBaseCommands = [
|
||||
'npm', 'node', 'npx', 'git', 'python', 'python3', 'pip', 'pip3',
|
||||
'cargo', 'rustc', 'go', 'gcc', 'g++', 'make', 'ls', 'cat', 'echo',
|
||||
'mkdir', 'cp', 'mv', 'touch'
|
||||
];
|
||||
|
||||
const baseCmd = trimmedCmd.split(/\s+/)[0];
|
||||
if (baseCmd && !safeBaseCommands.includes(baseCmd)) {
|
||||
console.warn(`[Security] Warning: Running uncommon command '${baseCmd}'. Ensure this is intended.`);
|
||||
}
|
||||
|
||||
return trimmedCmd;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import {
|
||||
getConfig,
|
||||
_getBrainDir,
|
||||
findBrainFiles,
|
||||
} from './utils';
|
||||
import { AgentExecutor } from './agent';
|
||||
import { BridgeInterface } from './bridge';
|
||||
|
||||
/**
|
||||
* Sidebar UI Provider implementing BridgeInterface for BridgeServer
|
||||
*/
|
||||
export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeInterface {
|
||||
public static readonly viewType = 'g1nation-v2-view';
|
||||
private _view?: vscode.WebviewView;
|
||||
public brainEnabled = true;
|
||||
|
||||
constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
private readonly _context: vscode.ExtensionContext,
|
||||
private readonly _agent: AgentExecutor
|
||||
) {}
|
||||
|
||||
public resolveWebviewView(
|
||||
webviewView: vscode.WebviewView,
|
||||
context: vscode.WebviewViewResolveContext,
|
||||
_token: vscode.CancellationToken,
|
||||
) {
|
||||
this._view = webviewView;
|
||||
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [this._extensionUri]
|
||||
};
|
||||
|
||||
webviewView.webview.html = this._getHtml(webviewView.webview);
|
||||
this._agent.setWebview(webviewView.webview);
|
||||
|
||||
// Re-hydrate existing history
|
||||
const currentHistory = this._agent.getHistory();
|
||||
if (currentHistory.length > 0) {
|
||||
setTimeout(() => {
|
||||
webviewView.webview.postMessage({ type: 'restoreHistory', value: currentHistory });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
webviewView.webview.onDidReceiveMessage(async (data) => {
|
||||
switch (data.type) {
|
||||
case 'prompt':
|
||||
case 'promptWithFile':
|
||||
await this._handlePrompt(data);
|
||||
// After prompt, save the session automatically
|
||||
await this._saveCurrentSession();
|
||||
break;
|
||||
case 'ready':
|
||||
await this._sendBrainStatus();
|
||||
await this._sendSessionList();
|
||||
break;
|
||||
case 'getModels':
|
||||
await this._sendModels();
|
||||
break;
|
||||
case 'newChat':
|
||||
this._currentSessionId = null;
|
||||
this._agent.clearHistory();
|
||||
this.clearChat();
|
||||
break;
|
||||
case 'stopGeneration':
|
||||
this._agent.stop();
|
||||
break;
|
||||
case 'loadSession':
|
||||
await this._loadSession(data.id);
|
||||
break;
|
||||
case 'deleteSession':
|
||||
await this._deleteSession(data.id);
|
||||
break;
|
||||
case 'openSettings':
|
||||
vscode.commands.executeCommand('workbench.action.openSettings', 'g1nation');
|
||||
break;
|
||||
case 'syncBrain':
|
||||
await this.syncBrain();
|
||||
await this._sendBrainStatus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _currentSessionId: string | null = null;
|
||||
|
||||
private async _saveCurrentSession() {
|
||||
const history = this._agent.getHistory();
|
||||
if (history.length === 0) return;
|
||||
|
||||
let sessions = this._context.globalState.get<any[]>('chat_sessions', []) || [];
|
||||
const firstMsg = history.find(m => m.role === 'user')?.content;
|
||||
const title = typeof firstMsg === 'string' ? firstMsg.substring(0, 50).replace(/\n/g, ' ') + (firstMsg.length > 50 ? '...' : '') : 'New Chat';
|
||||
|
||||
if (!this._currentSessionId) {
|
||||
this._currentSessionId = Date.now().toString();
|
||||
sessions.unshift({
|
||||
id: this._currentSessionId,
|
||||
title,
|
||||
timestamp: Date.now(),
|
||||
history
|
||||
});
|
||||
} else {
|
||||
const idx = sessions.findIndex(s => s.id === this._currentSessionId);
|
||||
if (idx >= 0) {
|
||||
sessions[idx].history = history;
|
||||
sessions[idx].timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
// Keep only last 50 sessions
|
||||
if (sessions.length > 50) sessions = sessions.slice(0, 50);
|
||||
|
||||
await this._context.globalState.update('chat_sessions', sessions);
|
||||
await this._sendSessionList();
|
||||
}
|
||||
|
||||
private async _sendSessionList() {
|
||||
if (!this._view) return;
|
||||
const sessions = this._context.globalState.get<any[]>('chat_sessions', []) || [];
|
||||
const list = sessions.map(s => ({ id: s.id, title: s.title, timestamp: s.timestamp }));
|
||||
this._view.webview.postMessage({ type: 'sessionList', value: list });
|
||||
}
|
||||
|
||||
private async _loadSession(id: string) {
|
||||
const sessions = this._context.globalState.get<any[]>('chat_sessions', []) || [];
|
||||
const session = sessions.find(s => s.id === id);
|
||||
if (session) {
|
||||
this._currentSessionId = id;
|
||||
this._agent.setHistory(session.history);
|
||||
this._view?.webview.postMessage({ type: 'clearChat' });
|
||||
this._view?.webview.postMessage({ type: 'restoreHistory', value: session.history });
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteSession(id: string) {
|
||||
let sessions = this._context.globalState.get<any[]>('chat_sessions', []) || [];
|
||||
sessions = sessions.filter(s => s.id !== id);
|
||||
await this._context.globalState.update('chat_sessions', sessions);
|
||||
if (this._currentSessionId === id) {
|
||||
this._currentSessionId = null;
|
||||
this._agent.clearHistory();
|
||||
this.clearChat();
|
||||
}
|
||||
await this._sendSessionList();
|
||||
}
|
||||
|
||||
private async _sendBrainStatus() {
|
||||
if (!this._view) return;
|
||||
const brainDir = _getBrainDir();
|
||||
const files = findBrainFiles(brainDir);
|
||||
this._view.webview.postMessage({
|
||||
type: 'brainStatus',
|
||||
value: {
|
||||
count: files.length,
|
||||
path: brainDir
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- BridgeInterface Methods ---
|
||||
|
||||
public injectSystemMessage(msg: string): void {
|
||||
this._view?.webview.postMessage({ type: 'streamStart' });
|
||||
this._view?.webview.postMessage({ type: 'streamChunk', value: msg });
|
||||
this._view?.webview.postMessage({ type: 'streamEnd' });
|
||||
}
|
||||
|
||||
public getHistoryText(): string {
|
||||
return "Conversation history placeholder for evaluation.";
|
||||
}
|
||||
|
||||
public sendPromptFromExtension(prompt: string): void {
|
||||
if (this._view) {
|
||||
this._view.show?.(true);
|
||||
this._view.webview.postMessage({ type: 'injectPrompt', value: prompt });
|
||||
}
|
||||
}
|
||||
|
||||
public findBrainFiles(dir: string): string[] {
|
||||
return findBrainFiles(dir);
|
||||
}
|
||||
|
||||
// --- End BridgeInterface ---
|
||||
|
||||
public focusInput() {
|
||||
this._view?.webview.postMessage({ type: 'focusInput' });
|
||||
}
|
||||
|
||||
public clearChat() {
|
||||
this._view?.webview.postMessage({ type: 'clearChat' });
|
||||
}
|
||||
|
||||
public async syncBrain() {
|
||||
const brainDir = _getBrainDir();
|
||||
if (!fs.existsSync(brainDir)) {
|
||||
vscode.window.showErrorMessage("Second Brain directory not found.");
|
||||
return;
|
||||
}
|
||||
vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "G1nation: Syncing Second Brain...",
|
||||
cancellable: false
|
||||
}, async () => {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
execSync(`git add .`, { cwd: brainDir });
|
||||
execSync(`git commit -m "[G1-Sync] Manual knowledge update"`, { cwd: brainDir });
|
||||
execSync(`git push`, { cwd: brainDir });
|
||||
vscode.window.showInformationMessage("Second Brain synced successfully.");
|
||||
} catch (err: any) {
|
||||
vscode.window.showInformationMessage("Brain Synced: Local knowledge is up-to-date (no changes to push).");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _handlePrompt(data: any) {
|
||||
if (!this._view) return;
|
||||
|
||||
const { value, model, internet, files } = data;
|
||||
|
||||
try {
|
||||
await this._agent.handlePrompt(value, model, {
|
||||
internetEnabled: internet,
|
||||
visionContent: files // Agent seems to handle files via visionContent
|
||||
});
|
||||
} catch (error: any) {
|
||||
this._view.webview.postMessage({ type: 'error', value: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
private async _sendModels() {
|
||||
if (!this._view) return;
|
||||
try {
|
||||
const config = getConfig();
|
||||
const url = config.ollamaUrl;
|
||||
let defaultModel = config.defaultModel;
|
||||
let models: string[] = [];
|
||||
|
||||
if (url.includes('1234') || url.includes('v1')) {
|
||||
try {
|
||||
const res = await fetch(`${url}/v1/models`, { signal: AbortSignal.timeout(5000) });
|
||||
if (res.ok) {
|
||||
const data = await res.json() as any;
|
||||
models = data.data.map((m: any) => m.id);
|
||||
}
|
||||
} catch (e) { console.error("[G1] LM Studio models fetch failed:", e); }
|
||||
} else {
|
||||
try {
|
||||
const res = await fetch(`${url}/api/tags`, { signal: AbortSignal.timeout(5000) });
|
||||
if (res.ok) {
|
||||
const data = await res.json() as any;
|
||||
models = data.models.map((m: any) => m.name);
|
||||
}
|
||||
} catch (e) { console.error("[G1] Ollama models fetch failed:", e); }
|
||||
}
|
||||
|
||||
if (models.length === 0) {
|
||||
models = [defaultModel];
|
||||
this._view.webview.postMessage({ type: 'engineStatus', value: { online: false, url } });
|
||||
} else {
|
||||
this._view.webview.postMessage({ type: 'engineStatus', value: { online: true, url } });
|
||||
}
|
||||
|
||||
if (models.length > 0 && !models.includes(defaultModel)) {
|
||||
defaultModel = models[0];
|
||||
await vscode.workspace.getConfiguration('g1nation').update('defaultModel', defaultModel, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
const defaultIdx = models.indexOf(defaultModel);
|
||||
if (defaultIdx > 0) {
|
||||
models.splice(defaultIdx, 1);
|
||||
models.unshift(defaultModel);
|
||||
}
|
||||
|
||||
this._view.webview.postMessage({ type: 'modelsList', value: models });
|
||||
} catch (err) {
|
||||
this._view.webview.postMessage({ type: 'modelsList', value: [getConfig().defaultModel] });
|
||||
}
|
||||
}
|
||||
|
||||
private _getHtml(webview: vscode.Webview): string {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>G1nation</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#000000;--bg2:#050505;--surface:rgba(0,18,5,.75);--surface2:rgba(0,35,10,.6);
|
||||
--border:rgba(255,255,255,.08);--border2:rgba(255,255,255,.12);
|
||||
--text:#A1A1AA;--text-bright:#FFFFFF;--text-dim:#71717A;
|
||||
--accent:#00FF41;--accent2:#008F11;--accent3:#00FF41;
|
||||
--accent-glow:rgba(0,255,65,.25);--accent2-glow:rgba(0,143,17,.2);
|
||||
--input-bg:rgba(0,10,2,.9);--code-bg:#020502;
|
||||
--green:#00FF41;--yellow:#ffab40;--cyan:#00e5ff;--red:#ff5252;
|
||||
}
|
||||
body.vscode-light {
|
||||
--bg:#fafafa;--bg2:#ffffff;--surface:rgba(255,255,255,.8);--surface2:rgba(240,240,245,.8);
|
||||
--border:rgba(0,0,0,.08);--border2:rgba(0,0,0,.15);
|
||||
--text:#454555;--text-bright:#111118;--text-dim:#888899;
|
||||
--accent-glow:rgba(124,106,255,.1);--accent2-glow:rgba(224,64,251,.08);
|
||||
--input-bg:rgba(255,255,255,.9);--code-bg:#f5f5f7;
|
||||
}
|
||||
html,body{height:100%;font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden;min-height:0}
|
||||
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(124,106,255,.06) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(224,64,251,.04) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(0,229,255,.03) 0%,transparent 50%);animation:aurora 20s ease-in-out infinite;z-index:0;pointer-events:none}
|
||||
@keyframes aurora{0%,100%{transform:translate(0,0) rotate(0deg)}33%{transform:translate(2%,-1%) rotate(.5deg)}66%{transform:translate(-1%,2%) rotate(-.5deg)}}
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(10,10,12,.8);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 5%,var(--accent) 30%,var(--accent2) 50%,var(--accent3) 70%,transparent 95%);opacity:.5;animation:headerGlow 4s ease-in-out infinite alternate}
|
||||
@keyframes headerGlow{0%{opacity:.3}100%{opacity:.6}}
|
||||
.thinking-bar{height:2px;background:transparent;position:relative;overflow:hidden;flex-shrink:0;z-index:10}
|
||||
.thinking-bar.active{background:rgba(124,106,255,.1)}
|
||||
.thinking-bar.active::after{content:'';position:absolute;top:0;left:-40%;width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),var(--accent3),transparent);animation:thinkSlide 1.5s ease-in-out infinite}
|
||||
@keyframes thinkSlide{0%{left:-40%}100%{left:100%}}
|
||||
.header-left{display:flex;align-items:center;gap:8px}
|
||||
.logo{width:26px;height:26px;border-radius:6px;background:#050505;border:1px solid rgba(0,255,65,.3);display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--accent);box-shadow:0 0 15px rgba(0,255,65,.15);animation:logoPulse 3s ease-in-out infinite;position:relative;text-shadow:0 0 8px var(--accent)}
|
||||
@keyframes logoPulse{0%,100%{box-shadow:0 0 10px rgba(0,255,65,.1)}50%{box-shadow:0 0 25px rgba(0,255,65,.3)}}
|
||||
.brand{font-weight:800;font-size:14px;color:var(--text-bright);letter-spacing:-.5px;background:linear-gradient(135deg,#fff 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header-right{display:flex;align-items:center;gap:5px}
|
||||
.history-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);backdrop-filter:blur(15px);z-index:100;display:none;flex-direction:column;padding:20px;animation:fadeIn 0.3s ease-out}
|
||||
.history-overlay.visible{display:flex}
|
||||
.history-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}
|
||||
.history-title{font-size:18px;font-weight:bold;color:var(--accent)}
|
||||
.history-list{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:10px}
|
||||
.history-item{background:rgba(255,255,255,0.05);border:1px solid var(--border);padding:12px;border-radius:10px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:all 0.2s}
|
||||
.history-item:hover{border-color:var(--accent);background:rgba(0,255,65,0.05)}
|
||||
.history-item-info{display:flex;flex-direction:column;gap:4px;flex:1}
|
||||
.history-item-title{font-weight:600;font-size:12px;color:var(--text-bright)}
|
||||
.history-item-date{font-size:10px;color:var(--text-dim)}
|
||||
.history-del-btn{background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:8px;font-size:14px;transition:color 0.2s}
|
||||
.history-del-btn:hover{color:var(--red)}
|
||||
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
||||
select{background:rgba(22,22,28,.9);color:var(--text-bright);border:1px solid var(--border2);padding:5px 8px;border-radius:8px;font-size:10px;cursor:pointer;outline:none;max-width:120px;transition:all .3s;backdrop-filter:blur(8px)}
|
||||
select:hover,select:focus{border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.btn-icon{background:rgba(22,22,28,.7);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
||||
.btn-icon:hover{color:var(--text-bright);border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 15px var(--accent-glow)}
|
||||
.chat{flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:16px;position:relative;z-index:1;min-height:0}
|
||||
.chat::-webkit-scrollbar{width:2px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px;opacity:.5}
|
||||
.msg{display:flex;flex-direction:column;gap:5px;animation:msgIn .5s cubic-bezier(.16,1,.3,1)}
|
||||
.msg-head{display:flex;align-items:center;gap:7px;font-weight:600;font-size:11px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:9px;color:var(--text-dim);margin-left:auto;opacity:.6}
|
||||
.av{width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text);border:1px solid var(--border2)}
|
||||
.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 10px rgba(124,106,255,.3)}
|
||||
.msg-body{padding-left:29px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:14px;padding:10px 14px;margin-left:29px;color:var(--text-bright);backdrop-filter:blur(8px)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.6;color:#c9d1d9;position:relative}
|
||||
.msg-body code{font-family:'SF Mono','JetBrains Mono','Fira Code','Menlo',monospace;font-size:11.5px}
|
||||
.msg-body :not(pre)>code{background:rgba(124,106,255,.1);color:var(--accent);padding:2px 7px;border-radius:5px;border:1px solid rgba(124,106,255,.15)}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;padding:2px 10px;border-radius:0 0 6px 6px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:6px;font-size:10px;cursor:pointer;opacity:0;transition:all .3s;z-index:1;backdrop-filter:blur(8px)}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.welcome{text-align:center;padding:0 20px 20px;position:relative}
|
||||
.welcome-logo{width:56px;height:56px;border-radius:16px;margin:0 auto 16px;background:#050505;border:1px solid rgba(0,255,65,.3);display:flex;align-items:center;justify-content:center;font-size:32px;color:var(--accent);box-shadow:inset 0 0 15px rgba(0,255,65,.1), 0 0 30px rgba(0,255,65,.2);animation:welcomeFloat 4s ease-in-out infinite;position:relative;text-shadow:0 0 15px var(--accent)}
|
||||
@keyframes welcomeFloat{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}
|
||||
.welcome-title{font-size:22px;font-weight:900;letter-spacing:-1px;color:var(--text-bright);margin-bottom:8px}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.7;margin-bottom:18px;letter-spacing:-.2px}
|
||||
.loading-wrap{padding-left:29px;padding-top:6px;display:flex;align-items:center;gap:10px}
|
||||
.loading-dots{display:flex;gap:4px}
|
||||
.loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:dotBounce 1.4s ease-in-out infinite}
|
||||
.loading-dots span:nth-child(2){animation-delay:.2s;background:var(--accent2)}
|
||||
.loading-dots span:nth-child(3){animation-delay:.4s;background:var(--accent3)}
|
||||
@keyframes dotBounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1.2);opacity:1}}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite;letter-spacing:.3px}
|
||||
.input-wrap{padding:8px 14px 14px;flex-shrink:0;position:relative;z-index:1}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .3s;position:relative;backdrop-filter:blur(12px)}
|
||||
.input-box:focus-within{border-color:var(--accent);box-shadow:0 0 24px rgba(0,255,65,.15)}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim);opacity:.5}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;box-shadow:0 2px 12px rgba(124,106,255,.35);position:relative;overflow:hidden}
|
||||
.send-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 6px 24px rgba(124,106,255,.45)}
|
||||
.send-btn:active{transform:scale(.92)}.send-btn:disabled{opacity:.2;cursor:not-allowed}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px;box-shadow:0 0 12px rgba(255,82,82,.3)}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
.stream-active::after{content:'';display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:text-bottom;box-shadow:0 0 6px var(--accent)}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
.main-view{flex:1;display:flex;flex-direction:column;overflow:hidden;transition:all .5s cubic-bezier(.16,1,.3,1);min-height:0;max-height:100%}
|
||||
body.init .main-view{justify-content:center;margin-top:-6vh}
|
||||
body.init .chat{flex:0 0 auto;overflow:visible;padding-bottom:15px}
|
||||
body.init .input-wrap{max-width:680px;width:100%;margin:0 auto}
|
||||
.attach-btn{background:transparent;border:1px solid var(--border2);color:var(--text-dim);width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .3s;flex-shrink:0}
|
||||
.attach-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.attach-preview{display:none;gap:6px;padding:0 0 6px;flex-wrap:wrap}
|
||||
.attach-preview.visible{display:flex}
|
||||
.attach-chip{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:4px 10px;font-size:10px;color:var(--text)}
|
||||
.attach-chip .chip-remove{cursor:pointer;color:var(--text-dim);font-size:12px;margin-left:2px}
|
||||
.attach-chip .chip-remove:hover{color:var(--red)}
|
||||
.attach-thumb{width:28px;height:28px;border-radius:5px;object-fit:cover;border:1px solid var(--border2)}
|
||||
.regen-btn{display:inline-flex;align-items:center;gap:4px;background:transparent;border:none;color:var(--text-dim);padding:4px 6px;border-radius:4px;font-size:11px;cursor:pointer;transition:color 0.2s;margin-top:6px;margin-left:29px;opacity:0.7}
|
||||
.regen-btn:hover{color:var(--text);opacity:1}
|
||||
</style></head><body class="init">
|
||||
<div class="header"><div class="header-left"><div class="logo">✦</div><span class="brand">G1nation</span></div><div class="header-right"><div id="engineStatusDot" style="width:8px;height:8px;border-radius:50%;background:var(--text-dim);margin-right:-2px" title="Checking AI Engine..."></div><select id="modelSel"></select><button class="btn-icon" id="internetBtn" title="Internet Access: OFF">🌐</button><button class="btn-icon" id="brainBtn" title="Sync Second Brain">🧠<span id="brainCountBadge" style="position:absolute;top:-5px;right:-5px;background:var(--accent);color:#000;font-size:8px;padding:1px 3px;border-radius:4px;display:none;font-weight:bold">0</span></button><button class="btn-icon" id="historyBtn" title="Chat History">📜</button><button class="btn-icon" id="settingsBtn" title="Settings">⚙️</button><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div id="historyOverlay" class="history-overlay">
|
||||
<div class="history-header"><span class="history-title">Chat Sessions</span><button class="btn-icon" id="closeHistoryBtn">✕</button></div>
|
||||
<div class="history-list" id="historyList"></div>
|
||||
</div>
|
||||
<div class="thinking-bar" id="thinkingBar"></div>
|
||||
<div class="main-view" id="mainView">
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">✦</div>
|
||||
<div class="welcome-title">G1nation</div>
|
||||
<div class="welcome-sub">Security · Optimized · Knowledge Mesh<br>Understands your project, writes code, and executes tasks.</div>
|
||||
<div id="brainStatusInfo" style="margin-top:10px;font-size:11px;color:var(--accent);opacity:0.8;display:none"></div>
|
||||
</div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<div class="attach-preview" id="attachPreview"></div>
|
||||
<textarea id="input" rows="1" placeholder="What shall we build today?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter to Send · Shift+Enter for New Line</span>
|
||||
<div class="input-btns"><button class="attach-btn" id="attachBtn" title="Attach Files">+</button><button class="stop-btn" id="stopBtn">■</button><button class="send-btn" id="sendBtn">↑</button></div></div></div>
|
||||
<input type="file" id="fileInput" multiple accept="image/*,.txt,.md,.csv,.json,.js,.ts,.jsx,.tsx,.html,.css,.py,.java,.rs,.go,.yaml,.yml,.xml,.toml,.sql,.sh" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
try {
|
||||
const vscode=acquireVsCodeApi(),chat=document.getElementById('chat'),input=document.getElementById('input'),
|
||||
sendBtn=document.getElementById('sendBtn'),stopBtn=document.getElementById('stopBtn'),
|
||||
modelSel=document.getElementById('modelSel'),newChatBtn=document.getElementById('newChatBtn'),settingsBtn=document.getElementById('settingsBtn'),brainBtn=document.getElementById('brainBtn'),
|
||||
historyBtn=document.getElementById('historyBtn'),historyOverlay=document.getElementById('historyOverlay'),closeHistoryBtn=document.getElementById('closeHistoryBtn'),historyList=document.getElementById('historyList'),
|
||||
internetBtn=document.getElementById('internetBtn'),attachBtn=document.getElementById('attachBtn'),fileInput=document.getElementById('fileInput'),attachPreview=document.getElementById('attachPreview'),
|
||||
thinkingBar=document.getElementById('thinkingBar');
|
||||
let loader=null,sending=false,pendingFiles=[],internetEnabled=false;
|
||||
|
||||
historyBtn.addEventListener('click', ()=>historyOverlay.classList.add('visible'));
|
||||
closeHistoryBtn.addEventListener('click', ()=>historyOverlay.classList.remove('visible'));
|
||||
|
||||
internetBtn.addEventListener('click', ()=>{
|
||||
internetEnabled=!internetEnabled;
|
||||
internetBtn.style.opacity=internetEnabled?'1':'0.4';
|
||||
internetBtn.style.filter=internetEnabled?'none':'grayscale(1)';
|
||||
});
|
||||
|
||||
vscode.postMessage({type:'getModels'});
|
||||
setTimeout(()=>vscode.postMessage({type:'ready'}),300);
|
||||
input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
|
||||
function getTime(){return new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'})}
|
||||
function esc(s){const d=document.createElement('div');d.innerText=s;return d.innerHTML}
|
||||
|
||||
function fmt(t){
|
||||
let formatted = esc(t);
|
||||
formatted = formatted.replace(/\`\`\`([\s\S]*?)\`\`\`/g, '<pre><code>$1</code></pre>');
|
||||
formatted = formatted.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function addMsg(text,role){
|
||||
const isUser=role==='user',isErr=role==='error';
|
||||
const el=document.createElement('div');el.className='msg'+(isUser?' msg-user':'')+(isErr?' msg-error':'');
|
||||
const head=document.createElement('div');head.className='msg-head';
|
||||
head.innerHTML=(isUser?'<div class="av av-user">👤</div><span>You</span>':'<div class="av av-ai">✦</div><span>G1nation</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
const body=document.createElement('div');body.className='msg-body';
|
||||
if(isUser){body.innerText=text}else{body.innerHTML=fmt(text)}
|
||||
el.appendChild(head);el.appendChild(body);chat.appendChild(el);chat.scrollTop=chat.scrollHeight;
|
||||
}
|
||||
|
||||
function showLoader(){thinkingBar.classList.add('active')}
|
||||
function hideLoader(){thinkingBar.classList.remove('active')}
|
||||
function setSending(v){sending=v;sendBtn.disabled=v;stopBtn.classList.toggle('visible',v);input.disabled=v;}
|
||||
|
||||
function send(){
|
||||
const text=input.value.trim();
|
||||
if(!text && pendingFiles.length===0) return;
|
||||
document.body.classList.remove('init');
|
||||
const w=document.querySelector('.welcome');if(w)w.remove();
|
||||
addMsg(text,'user');
|
||||
input.value='';input.style.height='auto';setSending(true);showLoader();
|
||||
vscode.postMessage({type:'prompt',value:text,model:modelSel.value,internet:internetEnabled,files:pendingFiles});
|
||||
pendingFiles=[];renderPreview();
|
||||
}
|
||||
|
||||
attachBtn.addEventListener('click',()=>fileInput.click());
|
||||
fileInput.addEventListener('change',()=>{
|
||||
const files=Array.from(fileInput.files);
|
||||
files.forEach(file=>{
|
||||
const reader=new FileReader();
|
||||
reader.onload=()=>{
|
||||
const base64=reader.result.split(',')[1];
|
||||
pendingFiles.push({name:file.name,type:file.type,data:base64});
|
||||
renderPreview();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
|
||||
function renderPreview(){
|
||||
attachPreview.innerHTML='';
|
||||
if(pendingFiles.length===0){attachPreview.classList.remove('visible');return;}
|
||||
attachPreview.classList.add('visible');
|
||||
pendingFiles.forEach((f,i)=>{
|
||||
const chip=document.createElement('div');chip.className='attach-chip';
|
||||
chip.innerHTML='<span>📎</span><span class="chip-name">'+f.name+'</span><span class="chip-remove">✕</span>';
|
||||
chip.querySelector('.chip-remove').addEventListener('click',()=>{pendingFiles.splice(i,1);renderPreview();});
|
||||
attachPreview.appendChild(chip);
|
||||
});
|
||||
}
|
||||
|
||||
sendBtn.addEventListener('click',send);
|
||||
input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
|
||||
newChatBtn.addEventListener('click',()=>vscode.postMessage({type:'newChat'}));
|
||||
settingsBtn.addEventListener('click',()=>vscode.postMessage({type:'openSettings'}));
|
||||
brainBtn.addEventListener('click',()=>vscode.postMessage({type:'syncBrain'}));
|
||||
stopBtn.addEventListener('click',()=>{vscode.postMessage({type:'stopGeneration'});hideLoader();setSending(false);});
|
||||
|
||||
let streamBody=null;
|
||||
window.addEventListener('message',e=>{const msg=e.data;switch(msg.type){
|
||||
case 'restoreHistory':
|
||||
chat.innerHTML='';
|
||||
document.body.classList.remove('init');
|
||||
msg.value.forEach(m => addMsg(m.content, m.role === 'assistant' ? 'ai' : 'user'));
|
||||
break;
|
||||
case 'sessionList':
|
||||
historyList.innerHTML='';
|
||||
msg.value.forEach(s=>{
|
||||
const el=document.createElement('div');el.className='history-item';
|
||||
el.innerHTML='<div class="history-item-info"><div class="history-item-title">'+esc(s.title)+'</div><div class="history-item-date">'+new Date(s.timestamp).toLocaleString()+'</div></div><button class="history-del-btn" title="Delete">🗑</button>';
|
||||
el.addEventListener('click',(e)=>{
|
||||
if(e.target.classList.contains('history-del-btn')) return;
|
||||
vscode.postMessage({type:'loadSession',id:s.id});
|
||||
historyOverlay.classList.remove('visible');
|
||||
});
|
||||
el.querySelector('.history-del-btn').addEventListener('click',()=>{
|
||||
vscode.postMessage({type:'deleteSession',id:s.id});
|
||||
});
|
||||
historyList.appendChild(el);
|
||||
});
|
||||
break;
|
||||
case 'streamStart':
|
||||
hideLoader();
|
||||
const el=document.createElement('div');el.className='msg';
|
||||
el.innerHTML='<div class="msg-head"><div class="av av-ai">✦</div><span>G1nation</span><span class="msg-time">'+getTime()+'</span></div><div class="msg-body stream-active"></div>';
|
||||
chat.appendChild(el);streamBody=el.querySelector('.msg-body');chat.scrollTop=chat.scrollHeight;
|
||||
break;
|
||||
case 'streamChunk':
|
||||
if(streamBody){streamBody.innerHTML=fmt(streamBody._raw=(streamBody._raw||'')+msg.value);chat.scrollTop=chat.scrollHeight;}
|
||||
break;
|
||||
case 'streamEnd':
|
||||
if(streamBody)streamBody.classList.remove('stream-active');
|
||||
setSending(false);streamBody=null;
|
||||
break;
|
||||
case 'modelsList':
|
||||
modelSel.innerHTML='';
|
||||
msg.value.forEach(m=>{const o=document.createElement('option');o.value=m;o.textContent=m;modelSel.appendChild(o)});
|
||||
break;
|
||||
case 'engineStatus':
|
||||
const dot = document.getElementById('engineStatusDot');
|
||||
if(dot){
|
||||
dot.style.background = msg.value.online ? 'var(--accent)' : 'var(--red)';
|
||||
dot.title = msg.value.online ? \`AI Engine Online (\${msg.value.url})\` : \`AI Engine Offline (Check \${msg.value.url})\`;
|
||||
}
|
||||
break;
|
||||
case 'brainStatus':
|
||||
const badge = document.getElementById('brainCountBadge');
|
||||
const info = document.getElementById('brainStatusInfo');
|
||||
if(badge){
|
||||
badge.innerText = msg.value.count > 999 ? '999+' : msg.value.count;
|
||||
badge.style.display = msg.value.count > 0 ? 'block' : 'none';
|
||||
}
|
||||
if(info){
|
||||
info.innerText = \`🧠 Second Brain: \${msg.value.count} knowledge files connected\`;
|
||||
info.style.display = 'block';
|
||||
}
|
||||
const brainBtn = document.getElementById('brainBtn');
|
||||
if(brainBtn) brainBtn.title = \`Sync Brain (Connected: \${msg.value.count} files at \${msg.value.path})\`;
|
||||
break;
|
||||
case 'clearChat':
|
||||
chat.innerHTML='<div class="welcome"><div class="welcome-logo">✦</div><div class="welcome-title">G1nation</div><div class="welcome-sub">System Cleaned.</div></div>';
|
||||
document.body.classList.add('init');
|
||||
break;
|
||||
case 'injectPrompt':
|
||||
input.value=msg.value;send();
|
||||
break;
|
||||
case 'autoContinue':
|
||||
showLoader();
|
||||
const hint = document.createElement('div');
|
||||
hint.className = 'input-hint';
|
||||
hint.style.textAlign = 'center';
|
||||
hint.style.margin = '10px 0';
|
||||
hint.innerText = msg.value;
|
||||
chat.appendChild(hint);
|
||||
chat.scrollTop = chat.scrollHeight;
|
||||
break;
|
||||
case 'error':
|
||||
hideLoader();setSending(false);addMsg('Error: '+msg.value,'error');
|
||||
break;
|
||||
} });
|
||||
} catch(err) { console.error(err); }
|
||||
</script></body></html>`;
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -1,6 +1,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const EXCLUDED_DIRS = new Set([
|
||||
'node_modules', '.git', '.vscode', 'out', 'dist', 'build',
|
||||
@@ -8,8 +9,7 @@ export const EXCLUDED_DIRS = new Set([
|
||||
'.turbo', '.nuxt', '.output', 'vendor', 'target'
|
||||
]);
|
||||
|
||||
export const MAX_CONTEXT_SIZE = 12_000;
|
||||
export const MAX_AUTO_AGENT_STEPS = 50;
|
||||
// Configuration constants moved to package.json and getConfig()
|
||||
|
||||
export function getConfig() {
|
||||
const cfg = vscode.workspace.getConfiguration('g1nation');
|
||||
@@ -18,7 +18,9 @@ export function getConfig() {
|
||||
defaultModel: cfg.get<string>('defaultModel', 'gemma4:e2b'),
|
||||
maxTreeFiles: 200,
|
||||
timeout: cfg.get<number>('requestTimeout', 300) * 1000,
|
||||
localBrainPath: cfg.get<string>('localBrainPath', '')
|
||||
localBrainPath: cfg.get<string>('localBrainPath', ''),
|
||||
maxContextSize: cfg.get<number>('maxContextSize', 12000),
|
||||
maxAutoSteps: cfg.get<number>('maxAutoSteps', 50)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +108,8 @@ file content here
|
||||
[ACTION 6: RUN TERMINAL COMMANDS]
|
||||
<run_command>npm install express</run_command>
|
||||
|
||||
[ACTION 7: READ USER'S SECOND BRAIN]
|
||||
[ACTION 7: SECOND BRAIN KNOWLEDGE]
|
||||
<list_brain path="optional/subdir"/>
|
||||
<read_brain>filename.md</read_brain>
|
||||
|
||||
[ACTION 8: READ WEBSITES & SEARCH INTERNET]
|
||||
|
||||
Reference in New Issue
Block a user