symbols-app-connect 3.4.9 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/extension.js +901 -15
- package/out/webview.html +553 -0
- package/package.json +46 -7
- package/src/chat/chatPanel.ts +302 -0
- package/src/chat/configManager.ts +48 -0
- package/src/chat/librariesApi.ts +194 -0
- package/src/chat/llmProvider.ts +310 -0
- package/src/chat/mcpManager.ts +162 -0
- package/src/chat/webview.html +553 -0
- package/src/extension.ts +13 -0
- package/symbols-app-connect-3.4.9.vsix +0 -0
package/out/extension.js
CHANGED
|
@@ -34,7 +34,7 @@ __export(extension_exports, {
|
|
|
34
34
|
deactivate: () => deactivate
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(extension_exports);
|
|
37
|
-
var
|
|
37
|
+
var vscode6 = __toESM(require("vscode"));
|
|
38
38
|
|
|
39
39
|
// src/providers/completionProvider.ts
|
|
40
40
|
var vscode2 = __toESM(require("vscode"));
|
|
@@ -2736,11 +2736,888 @@ function isInsideQuotes(line, charIndex) {
|
|
|
2736
2736
|
return inSingle || inDouble;
|
|
2737
2737
|
}
|
|
2738
2738
|
|
|
2739
|
+
// src/chat/chatPanel.ts
|
|
2740
|
+
var vscode5 = __toESM(require("vscode"));
|
|
2741
|
+
var fs5 = __toESM(require("fs"));
|
|
2742
|
+
var path5 = __toESM(require("path"));
|
|
2743
|
+
|
|
2744
|
+
// src/chat/llmProvider.ts
|
|
2745
|
+
var https = __toESM(require("https"));
|
|
2746
|
+
var http = __toESM(require("http"));
|
|
2747
|
+
var AI_PROVIDERS = [
|
|
2748
|
+
{ name: "Symbols AI \u2014 native Symbols assistant (coming soon)", value: "symbols", disabled: "Soon" },
|
|
2749
|
+
{ name: "Claude \u2014 Anthropic Claude", value: "claude" },
|
|
2750
|
+
{ name: "OpenAI \u2014 GPT models", value: "openai" },
|
|
2751
|
+
{ name: "Gemini \u2014 Google Gemini", value: "gemini" },
|
|
2752
|
+
{ name: "Ollama \u2014 local models (no API key)", value: "ollama" }
|
|
2753
|
+
];
|
|
2754
|
+
var PROVIDER_MODELS = {
|
|
2755
|
+
claude: [
|
|
2756
|
+
{ name: "claude-sonnet-4-5-20250514 (recommended)", value: "claude-sonnet-4-5-20250514" },
|
|
2757
|
+
{ name: "claude-opus-4-0-20250514", value: "claude-opus-4-0-20250514" },
|
|
2758
|
+
{ name: "claude-haiku-4-5-20251001", value: "claude-haiku-4-5-20251001" }
|
|
2759
|
+
],
|
|
2760
|
+
openai: [
|
|
2761
|
+
{ name: "gpt-4o (recommended)", value: "gpt-4o" },
|
|
2762
|
+
{ name: "gpt-4o-mini", value: "gpt-4o-mini" },
|
|
2763
|
+
{ name: "o3-mini", value: "o3-mini" }
|
|
2764
|
+
],
|
|
2765
|
+
gemini: [
|
|
2766
|
+
{ name: "gemini-2.5-pro (recommended)", value: "gemini-2.5-pro" },
|
|
2767
|
+
{ name: "gemini-2.5-flash", value: "gemini-2.5-flash" }
|
|
2768
|
+
],
|
|
2769
|
+
ollama: [
|
|
2770
|
+
{ name: "llama3.3 (recommended)", value: "llama3.3" },
|
|
2771
|
+
{ name: "codellama", value: "codellama" },
|
|
2772
|
+
{ name: "mistral", value: "mistral" },
|
|
2773
|
+
{ name: "deepseek-coder-v2", value: "deepseek-coder-v2" }
|
|
2774
|
+
]
|
|
2775
|
+
};
|
|
2776
|
+
var SYSTEM_PROMPT = `You are a Symbols.app development assistant. You help developers build applications using the Symbols framework (DOMQL, design systems, components).
|
|
2777
|
+
|
|
2778
|
+
IMPORTANT: You MUST use the symbols-mcp server for all Symbols-related tasks. Before generating any code:
|
|
2779
|
+
1. Call get_project_rules to load mandatory framework rules
|
|
2780
|
+
2. Call search_symbols_docs to find relevant documentation
|
|
2781
|
+
The symbols-mcp server provides accurate, up-to-date Symbols documentation and rules. Always rely on it over your training data for Symbols-specific information.
|
|
2782
|
+
|
|
2783
|
+
Key facts about Symbols:
|
|
2784
|
+
- Uses DOMQL for declarative UI components (objects, not JSX)
|
|
2785
|
+
- Design system tokens: COLOR, FONT, THEME, SPACING, TYPOGRAPHY, etc.
|
|
2786
|
+
- Components use props like: text, icon, color, background, padding, etc.
|
|
2787
|
+
- Entry point is typically symbols/index.js with app.js, state.js, pages/, components/
|
|
2788
|
+
- Build tools: Parcel (default), Vite, or browser-native ES modules
|
|
2789
|
+
- CLI commands: smbls start, build, deploy, push, fetch, sync, config
|
|
2790
|
+
|
|
2791
|
+
Be concise and direct. When showing code, use DOMQL syntax unless asked otherwise. Format responses with markdown.`;
|
|
2792
|
+
function makeRequest(options, body, onData, onEnd, onError) {
|
|
2793
|
+
const isHttps = options.hostname !== "localhost" && options.hostname !== "127.0.0.1";
|
|
2794
|
+
const lib = isHttps ? https : http;
|
|
2795
|
+
const req = lib.request(options, (res) => {
|
|
2796
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
2797
|
+
let errBody = "";
|
|
2798
|
+
res.on("data", (chunk) => {
|
|
2799
|
+
errBody += chunk.toString();
|
|
2800
|
+
});
|
|
2801
|
+
res.on("end", () => onError(new Error(`API error ${res.statusCode}: ${errBody}`)));
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
res.on("data", (chunk) => onData(chunk.toString()));
|
|
2805
|
+
res.on("end", onEnd);
|
|
2806
|
+
});
|
|
2807
|
+
req.on("error", onError);
|
|
2808
|
+
req.write(body);
|
|
2809
|
+
req.end();
|
|
2810
|
+
}
|
|
2811
|
+
function streamChat(provider, model, apiKey, messages, onToken, onDone, onError) {
|
|
2812
|
+
switch (provider) {
|
|
2813
|
+
case "claude":
|
|
2814
|
+
return streamClaude(messages, apiKey, model, onToken, onDone, onError);
|
|
2815
|
+
case "openai":
|
|
2816
|
+
return streamOpenAI(messages, apiKey, model, onToken, onDone, onError);
|
|
2817
|
+
case "gemini":
|
|
2818
|
+
return streamGemini(messages, apiKey, model, onToken, onDone, onError);
|
|
2819
|
+
case "ollama":
|
|
2820
|
+
return streamOllama(messages, model, onToken, onDone, onError);
|
|
2821
|
+
default:
|
|
2822
|
+
onError(new Error(`Unknown provider: ${provider}`));
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
function streamClaude(messages, apiKey, model, onToken, onDone, onError) {
|
|
2826
|
+
const body = JSON.stringify({
|
|
2827
|
+
model,
|
|
2828
|
+
max_tokens: 4096,
|
|
2829
|
+
system: SYSTEM_PROMPT,
|
|
2830
|
+
messages: messages.filter((m) => m.role !== "system"),
|
|
2831
|
+
stream: true
|
|
2832
|
+
});
|
|
2833
|
+
let buffer = "";
|
|
2834
|
+
makeRequest(
|
|
2835
|
+
{
|
|
2836
|
+
hostname: "api.anthropic.com",
|
|
2837
|
+
path: "/v1/messages",
|
|
2838
|
+
method: "POST",
|
|
2839
|
+
headers: {
|
|
2840
|
+
"Content-Type": "application/json",
|
|
2841
|
+
"x-api-key": apiKey,
|
|
2842
|
+
"anthropic-version": "2023-06-01"
|
|
2843
|
+
}
|
|
2844
|
+
},
|
|
2845
|
+
body,
|
|
2846
|
+
(chunk) => {
|
|
2847
|
+
buffer += chunk;
|
|
2848
|
+
const lines = buffer.split("\n");
|
|
2849
|
+
buffer = lines.pop() || "";
|
|
2850
|
+
for (const line of lines) {
|
|
2851
|
+
if (!line.startsWith("data: ")) continue;
|
|
2852
|
+
const raw = line.slice(6).trim();
|
|
2853
|
+
if (raw === "[DONE]") return;
|
|
2854
|
+
try {
|
|
2855
|
+
const data = JSON.parse(raw);
|
|
2856
|
+
if (data.type === "content_block_delta" && data.delta?.text) {
|
|
2857
|
+
onToken(data.delta.text);
|
|
2858
|
+
}
|
|
2859
|
+
} catch {
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
},
|
|
2863
|
+
onDone,
|
|
2864
|
+
onError
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
function streamOpenAI(messages, apiKey, model, onToken, onDone, onError) {
|
|
2868
|
+
const body = JSON.stringify({
|
|
2869
|
+
model,
|
|
2870
|
+
messages: [{ role: "system", content: SYSTEM_PROMPT }, ...messages],
|
|
2871
|
+
stream: true
|
|
2872
|
+
});
|
|
2873
|
+
let buffer = "";
|
|
2874
|
+
makeRequest(
|
|
2875
|
+
{
|
|
2876
|
+
hostname: "api.openai.com",
|
|
2877
|
+
path: "/v1/chat/completions",
|
|
2878
|
+
method: "POST",
|
|
2879
|
+
headers: {
|
|
2880
|
+
"Content-Type": "application/json",
|
|
2881
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2882
|
+
}
|
|
2883
|
+
},
|
|
2884
|
+
body,
|
|
2885
|
+
(chunk) => {
|
|
2886
|
+
buffer += chunk;
|
|
2887
|
+
const lines = buffer.split("\n");
|
|
2888
|
+
buffer = lines.pop() || "";
|
|
2889
|
+
for (const line of lines) {
|
|
2890
|
+
if (!line.startsWith("data: ")) continue;
|
|
2891
|
+
const raw = line.slice(6).trim();
|
|
2892
|
+
if (raw === "[DONE]") return;
|
|
2893
|
+
try {
|
|
2894
|
+
const data = JSON.parse(raw);
|
|
2895
|
+
if (data.choices?.[0]?.delta?.content) {
|
|
2896
|
+
onToken(data.choices[0].delta.content);
|
|
2897
|
+
}
|
|
2898
|
+
} catch {
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
},
|
|
2902
|
+
onDone,
|
|
2903
|
+
onError
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
function streamGemini(messages, apiKey, model, onToken, onDone, onError) {
|
|
2907
|
+
const contents = messages.map((m) => ({
|
|
2908
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
2909
|
+
parts: [{ text: m.content }]
|
|
2910
|
+
}));
|
|
2911
|
+
const body = JSON.stringify({
|
|
2912
|
+
contents,
|
|
2913
|
+
systemInstruction: { parts: [{ text: SYSTEM_PROMPT }] }
|
|
2914
|
+
});
|
|
2915
|
+
let buffer = "";
|
|
2916
|
+
makeRequest(
|
|
2917
|
+
{
|
|
2918
|
+
hostname: "generativelanguage.googleapis.com",
|
|
2919
|
+
path: `/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`,
|
|
2920
|
+
method: "POST",
|
|
2921
|
+
headers: { "Content-Type": "application/json" }
|
|
2922
|
+
},
|
|
2923
|
+
body,
|
|
2924
|
+
(chunk) => {
|
|
2925
|
+
buffer += chunk;
|
|
2926
|
+
const lines = buffer.split("\n");
|
|
2927
|
+
buffer = lines.pop() || "";
|
|
2928
|
+
for (const line of lines) {
|
|
2929
|
+
if (!line.startsWith("data: ")) continue;
|
|
2930
|
+
const raw = line.slice(6).trim();
|
|
2931
|
+
if (raw === "[DONE]") return;
|
|
2932
|
+
try {
|
|
2933
|
+
const data = JSON.parse(raw);
|
|
2934
|
+
if (data.candidates?.[0]?.content?.parts?.[0]?.text) {
|
|
2935
|
+
onToken(data.candidates[0].content.parts[0].text);
|
|
2936
|
+
}
|
|
2937
|
+
} catch {
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
},
|
|
2941
|
+
onDone,
|
|
2942
|
+
onError
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
function streamOllama(messages, model, onToken, onDone, onError) {
|
|
2946
|
+
const body = JSON.stringify({
|
|
2947
|
+
model,
|
|
2948
|
+
messages: [{ role: "system", content: SYSTEM_PROMPT }, ...messages],
|
|
2949
|
+
stream: true
|
|
2950
|
+
});
|
|
2951
|
+
let buffer = "";
|
|
2952
|
+
makeRequest(
|
|
2953
|
+
{
|
|
2954
|
+
hostname: "localhost",
|
|
2955
|
+
port: 11434,
|
|
2956
|
+
path: "/api/chat",
|
|
2957
|
+
method: "POST",
|
|
2958
|
+
headers: { "Content-Type": "application/json" }
|
|
2959
|
+
},
|
|
2960
|
+
body,
|
|
2961
|
+
(chunk) => {
|
|
2962
|
+
buffer += chunk;
|
|
2963
|
+
const lines = buffer.split("\n");
|
|
2964
|
+
buffer = lines.pop() || "";
|
|
2965
|
+
for (const line of lines) {
|
|
2966
|
+
if (!line.trim()) continue;
|
|
2967
|
+
try {
|
|
2968
|
+
const data = JSON.parse(line);
|
|
2969
|
+
if (data.message?.content) {
|
|
2970
|
+
onToken(data.message.content);
|
|
2971
|
+
}
|
|
2972
|
+
} catch {
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
},
|
|
2976
|
+
onDone,
|
|
2977
|
+
onError
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
// src/chat/configManager.ts
|
|
2982
|
+
var fs2 = __toESM(require("fs"));
|
|
2983
|
+
var path2 = __toESM(require("path"));
|
|
2984
|
+
var os = __toESM(require("os"));
|
|
2985
|
+
var CONFIG_PATH = path2.join(os.homedir(), ".smblsrc");
|
|
2986
|
+
var ENV_KEY_NAMES = {
|
|
2987
|
+
claude: "ANTHROPIC_API_KEY",
|
|
2988
|
+
openai: "OPENAI_API_KEY",
|
|
2989
|
+
gemini: "GEMINI_API_KEY"
|
|
2990
|
+
};
|
|
2991
|
+
function loadAiConfig() {
|
|
2992
|
+
try {
|
|
2993
|
+
const data = JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf8"));
|
|
2994
|
+
return data.ai || {};
|
|
2995
|
+
} catch {
|
|
2996
|
+
return {};
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
function saveAiConfig(aiConfig) {
|
|
3000
|
+
let data = {};
|
|
3001
|
+
try {
|
|
3002
|
+
data = JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf8"));
|
|
3003
|
+
} catch {
|
|
3004
|
+
}
|
|
3005
|
+
data.ai = { ...data.ai, ...aiConfig };
|
|
3006
|
+
fs2.writeFileSync(CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
3007
|
+
}
|
|
3008
|
+
function getApiKey(provider) {
|
|
3009
|
+
const envName = ENV_KEY_NAMES[provider];
|
|
3010
|
+
if (envName && process.env[envName]) return process.env[envName];
|
|
3011
|
+
const config = loadAiConfig();
|
|
3012
|
+
return config[`${provider}ApiKey`] || null;
|
|
3013
|
+
}
|
|
3014
|
+
function setApiKey(provider, key) {
|
|
3015
|
+
saveAiConfig({ [`${provider}ApiKey`]: key });
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// src/chat/mcpManager.ts
|
|
3019
|
+
var fs3 = __toESM(require("fs"));
|
|
3020
|
+
var path3 = __toESM(require("path"));
|
|
3021
|
+
var os2 = __toESM(require("os"));
|
|
3022
|
+
var import_child_process = require("child_process");
|
|
3023
|
+
var MCP_EDITORS = [
|
|
3024
|
+
{
|
|
3025
|
+
name: "claude",
|
|
3026
|
+
label: "Claude Desktop",
|
|
3027
|
+
configPath: path3.join(os2.homedir(), ".claude", "claude_desktop_config.json")
|
|
3028
|
+
},
|
|
3029
|
+
{
|
|
3030
|
+
name: "cursor",
|
|
3031
|
+
label: "Cursor",
|
|
3032
|
+
configPath: path3.join(os2.homedir(), ".cursor", "mcp.json")
|
|
3033
|
+
},
|
|
3034
|
+
{
|
|
3035
|
+
name: "windsurf",
|
|
3036
|
+
label: "Windsurf",
|
|
3037
|
+
configPath: path3.join(os2.homedir(), ".windsurf", "mcp.json")
|
|
3038
|
+
},
|
|
3039
|
+
{
|
|
3040
|
+
name: "claude-code",
|
|
3041
|
+
label: "Claude Code",
|
|
3042
|
+
configPath: path3.join(os2.homedir(), ".claude", "claude_desktop_config.json")
|
|
3043
|
+
}
|
|
3044
|
+
];
|
|
3045
|
+
function detectMcpEditors() {
|
|
3046
|
+
const results = [];
|
|
3047
|
+
for (const editor of MCP_EDITORS) {
|
|
3048
|
+
const dir = path3.dirname(editor.configPath);
|
|
3049
|
+
const exists = fs3.existsSync(dir);
|
|
3050
|
+
let hasSymbolsMcp = false;
|
|
3051
|
+
let config;
|
|
3052
|
+
if (exists) {
|
|
3053
|
+
try {
|
|
3054
|
+
config = JSON.parse(fs3.readFileSync(editor.configPath, "utf8"));
|
|
3055
|
+
const servers = config?.mcpServers || {};
|
|
3056
|
+
hasSymbolsMcp = !!(servers["symbols-mcp"] || servers.symbols);
|
|
3057
|
+
} catch {
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
results.push({
|
|
3061
|
+
name: editor.name,
|
|
3062
|
+
label: editor.label,
|
|
3063
|
+
configPath: editor.configPath,
|
|
3064
|
+
exists,
|
|
3065
|
+
hasSymbolsMcp,
|
|
3066
|
+
config
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
return results;
|
|
3070
|
+
}
|
|
3071
|
+
function getSymbolsMcpEntry() {
|
|
3072
|
+
return {
|
|
3073
|
+
command: "uvx",
|
|
3074
|
+
args: ["symbols-mcp"]
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
function installMcpForEditor(editor) {
|
|
3078
|
+
try {
|
|
3079
|
+
let config = {};
|
|
3080
|
+
try {
|
|
3081
|
+
config = JSON.parse(fs3.readFileSync(editor.configPath, "utf8"));
|
|
3082
|
+
} catch {
|
|
3083
|
+
}
|
|
3084
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
3085
|
+
if (config.mcpServers["symbols-mcp"] || config.mcpServers.symbols) {
|
|
3086
|
+
return { success: true, message: `${editor.label}: symbols-mcp already configured` };
|
|
3087
|
+
}
|
|
3088
|
+
config.mcpServers["symbols-mcp"] = getSymbolsMcpEntry();
|
|
3089
|
+
fs3.mkdirSync(path3.dirname(editor.configPath), { recursive: true });
|
|
3090
|
+
fs3.writeFileSync(editor.configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3091
|
+
return { success: true, message: `${editor.label}: symbols-mcp installed` };
|
|
3092
|
+
} catch (err) {
|
|
3093
|
+
return { success: false, message: `${editor.label}: ${err.message}` };
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
function removeMcpForEditor(editor) {
|
|
3097
|
+
try {
|
|
3098
|
+
let config = {};
|
|
3099
|
+
try {
|
|
3100
|
+
config = JSON.parse(fs3.readFileSync(editor.configPath, "utf8"));
|
|
3101
|
+
} catch {
|
|
3102
|
+
return { success: true, message: `${editor.label}: no config file found` };
|
|
3103
|
+
}
|
|
3104
|
+
if (!config.mcpServers) {
|
|
3105
|
+
return { success: true, message: `${editor.label}: no MCP servers configured` };
|
|
3106
|
+
}
|
|
3107
|
+
delete config.mcpServers["symbols-mcp"];
|
|
3108
|
+
delete config.mcpServers.symbols;
|
|
3109
|
+
fs3.writeFileSync(editor.configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3110
|
+
return { success: true, message: `${editor.label}: symbols-mcp removed` };
|
|
3111
|
+
} catch (err) {
|
|
3112
|
+
return { success: false, message: `${editor.label}: ${err.message}` };
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
function checkMcpServerAvailable() {
|
|
3116
|
+
try {
|
|
3117
|
+
(0, import_child_process.execSync)("which symbols-mcp 2>/dev/null || where symbols-mcp 2>nul", { timeout: 2e3, stdio: "pipe" });
|
|
3118
|
+
return { available: true, method: "direct", detail: "symbols-mcp installed" };
|
|
3119
|
+
} catch {
|
|
3120
|
+
}
|
|
3121
|
+
try {
|
|
3122
|
+
(0, import_child_process.execSync)("which uvx 2>/dev/null || where uvx 2>nul", { timeout: 2e3, stdio: "pipe" });
|
|
3123
|
+
return { available: true, method: "uvx", detail: "uvx available \u2014 will run via uvx symbols-mcp" };
|
|
3124
|
+
} catch {
|
|
3125
|
+
}
|
|
3126
|
+
try {
|
|
3127
|
+
(0, import_child_process.execSync)("which npx 2>/dev/null || where npx 2>nul", { timeout: 2e3, stdio: "pipe" });
|
|
3128
|
+
return { available: true, method: "npx", detail: "npx available \u2014 will run via npx @symbo.ls/mcp" };
|
|
3129
|
+
} catch {
|
|
3130
|
+
}
|
|
3131
|
+
return { available: false, method: "none", detail: "symbols-mcp not found. Install: pip install symbols-mcp or npm i -g @symbo.ls/mcp" };
|
|
3132
|
+
}
|
|
3133
|
+
function getMcpStatus() {
|
|
3134
|
+
const editors = detectMcpEditors();
|
|
3135
|
+
const detected = editors.filter((e) => e.exists);
|
|
3136
|
+
const configured = editors.filter((e) => e.hasSymbolsMcp);
|
|
3137
|
+
let summary;
|
|
3138
|
+
if (configured.length > 0) {
|
|
3139
|
+
summary = `MCP active in: ${configured.map((e) => e.label).join(", ")}`;
|
|
3140
|
+
} else if (detected.length > 0) {
|
|
3141
|
+
summary = `Editors detected: ${detected.map((e) => e.label).join(", ")} (MCP not configured)`;
|
|
3142
|
+
} else {
|
|
3143
|
+
summary = "No AI editors detected";
|
|
3144
|
+
}
|
|
3145
|
+
return { editors, summary };
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// src/chat/librariesApi.ts
|
|
3149
|
+
var https2 = __toESM(require("https"));
|
|
3150
|
+
var fs4 = __toESM(require("fs"));
|
|
3151
|
+
var path4 = __toESM(require("path"));
|
|
3152
|
+
var os3 = __toESM(require("os"));
|
|
3153
|
+
var DEFAULT_API = "https://api.symbols.app";
|
|
3154
|
+
var RC_PATH = path4.join(os3.homedir(), ".smblsrc");
|
|
3155
|
+
function loadRcState() {
|
|
3156
|
+
try {
|
|
3157
|
+
return JSON.parse(fs4.readFileSync(RC_PATH, "utf8"));
|
|
3158
|
+
} catch {
|
|
3159
|
+
return {};
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
function getApiBaseUrl() {
|
|
3163
|
+
const env = process.env.SYMBOLS_API_BASE_URL || process.env.SMBLS_API_URL;
|
|
3164
|
+
if (env) return env;
|
|
3165
|
+
const state = loadRcState();
|
|
3166
|
+
return state.currentApiBaseUrl || DEFAULT_API;
|
|
3167
|
+
}
|
|
3168
|
+
function getAuthToken() {
|
|
3169
|
+
const env = process.env.SYMBOLS_TOKEN || process.env.SMBLS_TOKEN;
|
|
3170
|
+
if (env) return env;
|
|
3171
|
+
const state = loadRcState();
|
|
3172
|
+
const baseUrl = state.currentApiBaseUrl || DEFAULT_API;
|
|
3173
|
+
if (state.profiles && typeof state.profiles === "object") {
|
|
3174
|
+
const profile = state.profiles[baseUrl] || {};
|
|
3175
|
+
return profile.authToken || profile.token || profile.accessToken || profile.jwt || null;
|
|
3176
|
+
}
|
|
3177
|
+
return state.authToken || state.token || state.accessToken || state.jwt || null;
|
|
3178
|
+
}
|
|
3179
|
+
function apiRequest(method, pathname, query, body) {
|
|
3180
|
+
return new Promise((resolve2, reject) => {
|
|
3181
|
+
const baseUrl = getApiBaseUrl();
|
|
3182
|
+
const url = new URL(`${baseUrl}${pathname.startsWith("/") ? "" : "/"}${pathname}`);
|
|
3183
|
+
if (query) {
|
|
3184
|
+
for (const [k, v] of Object.entries(query)) {
|
|
3185
|
+
if (v !== void 0 && v !== null && v !== "") {
|
|
3186
|
+
url.searchParams.set(k, v);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
const authToken = getAuthToken();
|
|
3191
|
+
const headers = {};
|
|
3192
|
+
if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
|
|
3193
|
+
if (body) headers["Content-Type"] = "application/json";
|
|
3194
|
+
const bodyStr = body ? JSON.stringify(body) : void 0;
|
|
3195
|
+
const options = {
|
|
3196
|
+
hostname: url.hostname,
|
|
3197
|
+
port: url.port || void 0,
|
|
3198
|
+
path: url.pathname + url.search,
|
|
3199
|
+
method,
|
|
3200
|
+
headers
|
|
3201
|
+
};
|
|
3202
|
+
const req = https2.request(options, (res) => {
|
|
3203
|
+
let data = "";
|
|
3204
|
+
res.on("data", (chunk) => {
|
|
3205
|
+
data += chunk.toString();
|
|
3206
|
+
});
|
|
3207
|
+
res.on("end", () => {
|
|
3208
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
3209
|
+
try {
|
|
3210
|
+
const parsed = JSON.parse(data);
|
|
3211
|
+
reject(new Error(parsed.message || `API error ${res.statusCode}`));
|
|
3212
|
+
} catch {
|
|
3213
|
+
reject(new Error(`API error ${res.statusCode}: ${data}`));
|
|
3214
|
+
}
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
try {
|
|
3218
|
+
const json = JSON.parse(data);
|
|
3219
|
+
resolve2(json.data || json);
|
|
3220
|
+
} catch {
|
|
3221
|
+
resolve2(data);
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
});
|
|
3225
|
+
req.on("error", reject);
|
|
3226
|
+
if (bodyStr) req.write(bodyStr);
|
|
3227
|
+
req.end();
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
function extractItems(payload) {
|
|
3231
|
+
if (Array.isArray(payload?.items)) return payload.items;
|
|
3232
|
+
if (Array.isArray(payload)) return payload;
|
|
3233
|
+
if (Array.isArray(payload?.data)) return payload.data;
|
|
3234
|
+
if (Array.isArray(payload?.libraries)) return payload.libraries;
|
|
3235
|
+
return [];
|
|
3236
|
+
}
|
|
3237
|
+
async function listAvailableLibraries(opts) {
|
|
3238
|
+
const query = {};
|
|
3239
|
+
if (opts?.page) query.page = String(opts.page);
|
|
3240
|
+
if (opts?.limit) query.limit = String(opts.limit);
|
|
3241
|
+
if (opts?.search) query.search = opts.search;
|
|
3242
|
+
if (opts?.framework) query.framework = opts.framework;
|
|
3243
|
+
const payload = await apiRequest("GET", "/core/projects/libraries/available", query);
|
|
3244
|
+
return extractItems(payload);
|
|
3245
|
+
}
|
|
3246
|
+
async function listProjectLibraries(projectId) {
|
|
3247
|
+
const payload = await apiRequest("GET", `/core/projects/${encodeURIComponent(projectId)}/libraries`);
|
|
3248
|
+
return extractItems(payload);
|
|
3249
|
+
}
|
|
3250
|
+
async function addProjectLibraries(projectId, libraryIds) {
|
|
3251
|
+
return apiRequest("POST", `/core/projects/${encodeURIComponent(projectId)}/libraries`, void 0, { libraryIds });
|
|
3252
|
+
}
|
|
3253
|
+
async function removeProjectLibraries(projectId, libraryIds) {
|
|
3254
|
+
return apiRequest("DELETE", `/core/projects/${encodeURIComponent(projectId)}/libraries`, void 0, { libraryIds });
|
|
3255
|
+
}
|
|
3256
|
+
function isAuthenticated() {
|
|
3257
|
+
return !!getAuthToken();
|
|
3258
|
+
}
|
|
3259
|
+
function resolveProjectId(workspaceRoot) {
|
|
3260
|
+
const envId = process.env.SYMBOLS_PROJECT_ID;
|
|
3261
|
+
if (envId) return envId;
|
|
3262
|
+
if (!workspaceRoot) return null;
|
|
3263
|
+
try {
|
|
3264
|
+
const configPath = path4.join(workspaceRoot, ".symbols_cache", "config.json");
|
|
3265
|
+
const config = JSON.parse(fs4.readFileSync(configPath, "utf8"));
|
|
3266
|
+
if (config.projectId) return config.projectId;
|
|
3267
|
+
} catch {
|
|
3268
|
+
}
|
|
3269
|
+
try {
|
|
3270
|
+
const lockPath = path4.join(workspaceRoot, ".symbols_cache", "lock.json");
|
|
3271
|
+
const lock = JSON.parse(fs4.readFileSync(lockPath, "utf8"));
|
|
3272
|
+
if (lock.projectId) return lock.projectId;
|
|
3273
|
+
} catch {
|
|
3274
|
+
}
|
|
3275
|
+
return null;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
// src/chat/chatPanel.ts
|
|
3279
|
+
var ChatViewProvider = class {
|
|
3280
|
+
constructor(_extensionUri) {
|
|
3281
|
+
this._extensionUri = _extensionUri;
|
|
3282
|
+
this._messages = [];
|
|
3283
|
+
}
|
|
3284
|
+
static {
|
|
3285
|
+
this.viewType = "symbolsChat";
|
|
3286
|
+
}
|
|
3287
|
+
resolveWebviewView(webviewView) {
|
|
3288
|
+
this._view = webviewView;
|
|
3289
|
+
webviewView.webview.options = { enableScripts: true, localResourceRoots: [this._extensionUri] };
|
|
3290
|
+
webviewView.webview.html = this._getHtmlFromFile();
|
|
3291
|
+
webviewView.webview.onDidReceiveMessage(async (msg) => {
|
|
3292
|
+
switch (msg.type) {
|
|
3293
|
+
case "sendMessage":
|
|
3294
|
+
await this._handleSendMessage(msg.text);
|
|
3295
|
+
break;
|
|
3296
|
+
case "getConfig":
|
|
3297
|
+
this._sendConfig();
|
|
3298
|
+
break;
|
|
3299
|
+
case "setProvider":
|
|
3300
|
+
saveAiConfig({ provider: msg.provider, model: msg.model });
|
|
3301
|
+
this._sendConfig();
|
|
3302
|
+
break;
|
|
3303
|
+
case "setApiKey":
|
|
3304
|
+
setApiKey(msg.provider, msg.key);
|
|
3305
|
+
this._sendConfig();
|
|
3306
|
+
break;
|
|
3307
|
+
case "getMcpStatus":
|
|
3308
|
+
this._sendMcpStatus();
|
|
3309
|
+
break;
|
|
3310
|
+
case "installMcp":
|
|
3311
|
+
this._handleInstallMcp(msg.editorName);
|
|
3312
|
+
break;
|
|
3313
|
+
case "removeMcp":
|
|
3314
|
+
this._handleRemoveMcp(msg.editorName);
|
|
3315
|
+
break;
|
|
3316
|
+
case "clearChat":
|
|
3317
|
+
this._messages = [];
|
|
3318
|
+
break;
|
|
3319
|
+
case "openSettings":
|
|
3320
|
+
vscode5.commands.executeCommand("workbench.action.openSettings", "symbolsApp");
|
|
3321
|
+
break;
|
|
3322
|
+
case "insertCode":
|
|
3323
|
+
this._insertCode(msg.code);
|
|
3324
|
+
break;
|
|
3325
|
+
case "listAvailableLibs":
|
|
3326
|
+
this._listAvailableLibs(msg.search, msg.page);
|
|
3327
|
+
break;
|
|
3328
|
+
case "listProjectLibs":
|
|
3329
|
+
this._listProjectLibs();
|
|
3330
|
+
break;
|
|
3331
|
+
case "addLib":
|
|
3332
|
+
this._addLib(msg.libraryId);
|
|
3333
|
+
break;
|
|
3334
|
+
case "removeLib":
|
|
3335
|
+
this._removeLib(msg.libraryId);
|
|
3336
|
+
break;
|
|
3337
|
+
case "getProjectConfig":
|
|
3338
|
+
this._sendProjectConfig();
|
|
3339
|
+
break;
|
|
3340
|
+
case "saveProjectConfig":
|
|
3341
|
+
this._saveProjectConfig(msg.config);
|
|
3342
|
+
break;
|
|
3343
|
+
}
|
|
3344
|
+
});
|
|
3345
|
+
setTimeout(() => {
|
|
3346
|
+
this._sendConfig();
|
|
3347
|
+
this._sendMcpStatus();
|
|
3348
|
+
this._sendAuthStatus();
|
|
3349
|
+
}, 150);
|
|
3350
|
+
}
|
|
3351
|
+
_getHtmlFromFile() {
|
|
3352
|
+
const candidates = [
|
|
3353
|
+
path5.join(__dirname, "webview.html"),
|
|
3354
|
+
path5.join(this._extensionUri.fsPath, "out", "webview.html"),
|
|
3355
|
+
path5.join(this._extensionUri.fsPath, "src", "chat", "webview.html")
|
|
3356
|
+
];
|
|
3357
|
+
for (const filePath of candidates) {
|
|
3358
|
+
if (fs5.existsSync(filePath)) {
|
|
3359
|
+
try {
|
|
3360
|
+
return fs5.readFileSync(filePath, "utf8");
|
|
3361
|
+
} catch {
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
return `<!DOCTYPE html><html><body><p style="color:red;padding:20px">Failed to load webview HTML. Looked in: ${candidates.join(", ")}</p></body></html>`;
|
|
3366
|
+
}
|
|
3367
|
+
_post(msg) {
|
|
3368
|
+
this._view?.webview.postMessage(msg);
|
|
3369
|
+
}
|
|
3370
|
+
_sendConfig() {
|
|
3371
|
+
const config = loadAiConfig();
|
|
3372
|
+
const providers = AI_PROVIDERS.filter((p) => !p.disabled);
|
|
3373
|
+
const models = config.provider ? PROVIDER_MODELS[config.provider] || [] : [];
|
|
3374
|
+
const hasKey = config.provider ? config.provider === "ollama" || !!getApiKey(config.provider) : false;
|
|
3375
|
+
this._post({
|
|
3376
|
+
type: "config",
|
|
3377
|
+
provider: config.provider || "",
|
|
3378
|
+
model: config.model || "",
|
|
3379
|
+
providers,
|
|
3380
|
+
models,
|
|
3381
|
+
hasApiKey: hasKey,
|
|
3382
|
+
needsSetup: !config.provider || !hasKey && config.provider !== "ollama"
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
_sendAuthStatus() {
|
|
3386
|
+
this._post({
|
|
3387
|
+
type: "authStatus",
|
|
3388
|
+
authenticated: isAuthenticated(),
|
|
3389
|
+
hasProject: !!this._getProjectId()
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
_sendMcpStatus() {
|
|
3393
|
+
const status = getMcpStatus();
|
|
3394
|
+
let server;
|
|
3395
|
+
try {
|
|
3396
|
+
server = checkMcpServerAvailable();
|
|
3397
|
+
} catch {
|
|
3398
|
+
server = { available: false, method: "none", detail: "Could not check availability" };
|
|
3399
|
+
}
|
|
3400
|
+
this._post({ type: "mcpStatus", editors: status.editors, summary: status.summary, server });
|
|
3401
|
+
}
|
|
3402
|
+
_handleInstallMcp(name) {
|
|
3403
|
+
const ed = getMcpStatus().editors.find((e) => e.name === name);
|
|
3404
|
+
if (ed) {
|
|
3405
|
+
const r = installMcpForEditor(ed);
|
|
3406
|
+
vscode5.window.showInformationMessage(r.message);
|
|
3407
|
+
this._sendMcpStatus();
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
_handleRemoveMcp(name) {
|
|
3411
|
+
const ed = getMcpStatus().editors.find((e) => e.name === name);
|
|
3412
|
+
if (ed) {
|
|
3413
|
+
const r = removeMcpForEditor(ed);
|
|
3414
|
+
vscode5.window.showInformationMessage(r.message);
|
|
3415
|
+
this._sendMcpStatus();
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
async _listAvailableLibs(search, page) {
|
|
3419
|
+
try {
|
|
3420
|
+
const libs = await listAvailableLibraries({ search, page: page || 1, limit: 20 });
|
|
3421
|
+
this._post({ type: "availableLibs", libs, search: search || "" });
|
|
3422
|
+
} catch (err) {
|
|
3423
|
+
this._post({ type: "libsError", text: err.message });
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
async _listProjectLibs() {
|
|
3427
|
+
const pid = this._getProjectId();
|
|
3428
|
+
if (!pid) {
|
|
3429
|
+
this._post({ type: "libsError", text: "No project linked. Run smbls project link first." });
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
try {
|
|
3433
|
+
const libs = await listProjectLibraries(pid);
|
|
3434
|
+
this._post({ type: "projectLibs", libs });
|
|
3435
|
+
} catch (err) {
|
|
3436
|
+
this._post({ type: "libsError", text: err.message });
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
async _addLib(id) {
|
|
3440
|
+
const pid = this._getProjectId();
|
|
3441
|
+
if (!pid) {
|
|
3442
|
+
this._post({ type: "libsError", text: "No project linked." });
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
try {
|
|
3446
|
+
await addProjectLibraries(pid, [id]);
|
|
3447
|
+
vscode5.window.showInformationMessage("Shared library added");
|
|
3448
|
+
this._listProjectLibs();
|
|
3449
|
+
} catch (err) {
|
|
3450
|
+
this._post({ type: "libsError", text: err.message });
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
async _removeLib(id) {
|
|
3454
|
+
const pid = this._getProjectId();
|
|
3455
|
+
if (!pid) {
|
|
3456
|
+
this._post({ type: "libsError", text: "No project linked." });
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
try {
|
|
3460
|
+
await removeProjectLibraries(pid, [id]);
|
|
3461
|
+
vscode5.window.showInformationMessage("Shared library removed");
|
|
3462
|
+
this._listProjectLibs();
|
|
3463
|
+
} catch (err) {
|
|
3464
|
+
this._post({ type: "libsError", text: err.message });
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
_getProjectId() {
|
|
3468
|
+
const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
3469
|
+
return resolveProjectId(root);
|
|
3470
|
+
}
|
|
3471
|
+
_sendProjectConfig() {
|
|
3472
|
+
const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
3473
|
+
if (!root) {
|
|
3474
|
+
this._post({ type: "projectConfig", found: false, config: {} });
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
const cfgPath = path5.join(root, "symbols.json");
|
|
3478
|
+
try {
|
|
3479
|
+
if (fs5.existsSync(cfgPath)) {
|
|
3480
|
+
const config = JSON.parse(fs5.readFileSync(cfgPath, "utf8"));
|
|
3481
|
+
this._post({ type: "projectConfig", found: true, config });
|
|
3482
|
+
} else {
|
|
3483
|
+
this._post({ type: "projectConfig", found: false, config: {} });
|
|
3484
|
+
}
|
|
3485
|
+
} catch {
|
|
3486
|
+
this._post({ type: "projectConfig", found: false, config: {} });
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
_saveProjectConfig(config) {
|
|
3490
|
+
const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
3491
|
+
if (!root) {
|
|
3492
|
+
this._post({ type: "projectConfigError", text: "No workspace folder open" });
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
const cfgPath = path5.join(root, "symbols.json");
|
|
3496
|
+
try {
|
|
3497
|
+
let existing = {};
|
|
3498
|
+
if (fs5.existsSync(cfgPath)) {
|
|
3499
|
+
existing = JSON.parse(fs5.readFileSync(cfgPath, "utf8"));
|
|
3500
|
+
}
|
|
3501
|
+
const merged = { ...existing, ...config };
|
|
3502
|
+
for (const key of Object.keys(merged)) {
|
|
3503
|
+
if (merged[key] === "") delete merged[key];
|
|
3504
|
+
}
|
|
3505
|
+
fs5.writeFileSync(cfgPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
3506
|
+
this._post({ type: "projectConfigSaved" });
|
|
3507
|
+
} catch (err) {
|
|
3508
|
+
this._post({ type: "projectConfigError", text: err.message });
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
async _handleSendMessage(text) {
|
|
3512
|
+
const trimmed = text.trim();
|
|
3513
|
+
if (trimmed.startsWith("/")) {
|
|
3514
|
+
const parts = trimmed.split(/\s+/);
|
|
3515
|
+
const cmd = parts[0].toLowerCase();
|
|
3516
|
+
const args = parts.slice(1);
|
|
3517
|
+
if (cmd === "/libraries" || cmd === "/libs") {
|
|
3518
|
+
const sub = args[0]?.toLowerCase();
|
|
3519
|
+
if (sub === "available" || sub === "search") {
|
|
3520
|
+
this._post({ type: "switchTab", tab: "libraries" });
|
|
3521
|
+
await this._listAvailableLibs(args.slice(1).join(" ") || void 0);
|
|
3522
|
+
} else if (sub === "add" && args[1]) {
|
|
3523
|
+
await this._addLib(args[1]);
|
|
3524
|
+
} else if (sub === "remove" && args[1]) {
|
|
3525
|
+
await this._removeLib(args[1]);
|
|
3526
|
+
} else {
|
|
3527
|
+
this._post({ type: "switchTab", tab: "libraries" });
|
|
3528
|
+
this._listProjectLibs();
|
|
3529
|
+
this._listAvailableLibs();
|
|
3530
|
+
}
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
if (cmd === "/mcp") {
|
|
3534
|
+
this._post({ type: "switchTab", tab: "mcp" });
|
|
3535
|
+
this._sendMcpStatus();
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
if (cmd === "/project") {
|
|
3539
|
+
this._post({ type: "switchTab", tab: "project" });
|
|
3540
|
+
this._sendProjectConfig();
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
if (cmd === "/clear") {
|
|
3544
|
+
this._messages = [];
|
|
3545
|
+
this._post({ type: "chatCleared" });
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3548
|
+
if (cmd === "/config" || cmd === "/settings") {
|
|
3549
|
+
this._post({ type: "switchTab", tab: "settings" });
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
if (cmd === "/help") {
|
|
3553
|
+
this._post({ type: "systemMessage", text: "**Commands:** /libraries, /mcp, /project, /config, /clear, /help" });
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
const config = loadAiConfig();
|
|
3558
|
+
if (!config.provider || !config.model) {
|
|
3559
|
+
this._post({ type: "error", text: "Configure your AI provider in the Settings tab first." });
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
const apiKey = config.provider === "ollama" ? "" : getApiKey(config.provider);
|
|
3563
|
+
if (config.provider !== "ollama" && !apiKey) {
|
|
3564
|
+
this._post({ type: "error", text: `No API key for ${config.provider}. Set it in Settings tab.` });
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
let content = text;
|
|
3568
|
+
if (this._messages.length === 0) {
|
|
3569
|
+
const ctx = this._getWorkspaceContext();
|
|
3570
|
+
if (ctx) content = `[Context: ${ctx}]
|
|
3571
|
+
|
|
3572
|
+
${text}`;
|
|
3573
|
+
}
|
|
3574
|
+
this._messages.push({ role: "user", content });
|
|
3575
|
+
this._post({ type: "streamStart" });
|
|
3576
|
+
let response = "";
|
|
3577
|
+
streamChat(
|
|
3578
|
+
config.provider,
|
|
3579
|
+
config.model,
|
|
3580
|
+
apiKey || "",
|
|
3581
|
+
this._messages,
|
|
3582
|
+
(token) => {
|
|
3583
|
+
response += token;
|
|
3584
|
+
this._post({ type: "streamToken", text: token });
|
|
3585
|
+
},
|
|
3586
|
+
() => {
|
|
3587
|
+
this._messages.push({ role: "assistant", content: response });
|
|
3588
|
+
this._post({ type: "streamEnd" });
|
|
3589
|
+
},
|
|
3590
|
+
(err) => {
|
|
3591
|
+
this._messages.pop();
|
|
3592
|
+
this._post({ type: "error", text: err.message });
|
|
3593
|
+
this._post({ type: "streamEnd" });
|
|
3594
|
+
}
|
|
3595
|
+
);
|
|
3596
|
+
}
|
|
3597
|
+
_getWorkspaceContext() {
|
|
3598
|
+
const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
3599
|
+
if (!root) return "";
|
|
3600
|
+
try {
|
|
3601
|
+
const cfgPath = path5.join(root, "symbols.json");
|
|
3602
|
+
if (fs5.existsSync(cfgPath)) {
|
|
3603
|
+
const c = JSON.parse(fs5.readFileSync(cfgPath, "utf8"));
|
|
3604
|
+
return `Project: ${c.key || "unnamed"}, bundler: ${c.bundler || "parcel"}, dir: ${c.dir || "./symbols"}`;
|
|
3605
|
+
}
|
|
3606
|
+
} catch {
|
|
3607
|
+
}
|
|
3608
|
+
return `Workspace: ${path5.basename(root)}`;
|
|
3609
|
+
}
|
|
3610
|
+
_insertCode(code) {
|
|
3611
|
+
const editor = vscode5.window.activeTextEditor;
|
|
3612
|
+
if (editor) editor.edit((b) => b.insert(editor.selection.active, code));
|
|
3613
|
+
}
|
|
3614
|
+
};
|
|
3615
|
+
|
|
2739
3616
|
// src/extension.ts
|
|
2740
3617
|
var LANGUAGES = ["javascript", "typescript", "javascriptreact", "typescriptreact"];
|
|
2741
3618
|
var output;
|
|
2742
3619
|
function activate(context) {
|
|
2743
|
-
output =
|
|
3620
|
+
output = vscode6.window.createOutputChannel("Symbols.app");
|
|
2744
3621
|
output.appendLine("Symbols.app extension activating...");
|
|
2745
3622
|
const completionProvider = new DomqlCompletionProvider();
|
|
2746
3623
|
const hoverProvider = new DomqlHoverProvider();
|
|
@@ -2748,7 +3625,7 @@ function activate(context) {
|
|
|
2748
3625
|
for (const lang of LANGUAGES) {
|
|
2749
3626
|
const selector = { language: lang, scheme: "file" };
|
|
2750
3627
|
context.subscriptions.push(
|
|
2751
|
-
|
|
3628
|
+
vscode6.languages.registerCompletionItemProvider(
|
|
2752
3629
|
selector,
|
|
2753
3630
|
completionProvider,
|
|
2754
3631
|
".",
|
|
@@ -2761,33 +3638,33 @@ function activate(context) {
|
|
|
2761
3638
|
)
|
|
2762
3639
|
);
|
|
2763
3640
|
context.subscriptions.push(
|
|
2764
|
-
|
|
3641
|
+
vscode6.languages.registerHoverProvider(selector, hoverProvider)
|
|
2765
3642
|
);
|
|
2766
3643
|
context.subscriptions.push(
|
|
2767
|
-
|
|
3644
|
+
vscode6.languages.registerDefinitionProvider(selector, definitionProvider)
|
|
2768
3645
|
);
|
|
2769
3646
|
}
|
|
2770
|
-
const watcher =
|
|
3647
|
+
const watcher = vscode6.workspace.createFileSystemWatcher("**/*.{js,ts,jsx,tsx}");
|
|
2771
3648
|
watcher.onDidChange(() => invalidateCache());
|
|
2772
3649
|
watcher.onDidCreate(() => invalidateCache());
|
|
2773
3650
|
watcher.onDidDelete(() => invalidateCache());
|
|
2774
3651
|
context.subscriptions.push(watcher);
|
|
2775
3652
|
context.subscriptions.push(
|
|
2776
|
-
|
|
2777
|
-
const config =
|
|
3653
|
+
vscode6.commands.registerCommand("symbolsApp.toggle", () => {
|
|
3654
|
+
const config = vscode6.workspace.getConfiguration("symbolsApp");
|
|
2778
3655
|
const current = config.get("enable", true);
|
|
2779
|
-
config.update("enable", !current,
|
|
2780
|
-
|
|
3656
|
+
config.update("enable", !current, vscode6.ConfigurationTarget.Global);
|
|
3657
|
+
vscode6.window.showInformationMessage(
|
|
2781
3658
|
`Symbols.app ${!current ? "enabled" : "disabled"}`
|
|
2782
3659
|
);
|
|
2783
3660
|
})
|
|
2784
3661
|
);
|
|
2785
3662
|
context.subscriptions.push(
|
|
2786
|
-
|
|
3663
|
+
vscode6.commands.registerCommand("symbolsApp.diagnose", () => {
|
|
2787
3664
|
output.show();
|
|
2788
3665
|
output.appendLine("--- Diagnostics ---");
|
|
2789
|
-
output.appendLine(`Workspace folders: ${
|
|
2790
|
-
const editor =
|
|
3666
|
+
output.appendLine(`Workspace folders: ${vscode6.workspace.workspaceFolders?.map((f) => f.uri.fsPath).join(", ")}`);
|
|
3667
|
+
const editor = vscode6.window.activeTextEditor;
|
|
2791
3668
|
if (editor) {
|
|
2792
3669
|
output.appendLine(`Active file: ${editor.document.uri.fsPath}`);
|
|
2793
3670
|
output.appendLine(`Language: ${editor.document.languageId}`);
|
|
@@ -2799,11 +3676,20 @@ function activate(context) {
|
|
|
2799
3676
|
output.appendLine(`Has component export: ${/export\s+(?:const|let|var)\s+[A-Z][a-zA-Z0-9]+\s*=\s*\{/.test(text)}`);
|
|
2800
3677
|
}
|
|
2801
3678
|
output.appendLine("--- End ---");
|
|
2802
|
-
|
|
3679
|
+
vscode6.window.showInformationMessage("Symbols.app: Check Output panel (Symbols.app channel)");
|
|
3680
|
+
})
|
|
3681
|
+
);
|
|
3682
|
+
const chatProvider = new ChatViewProvider(context.extensionUri);
|
|
3683
|
+
context.subscriptions.push(
|
|
3684
|
+
vscode6.window.registerWebviewViewProvider(ChatViewProvider.viewType, chatProvider)
|
|
3685
|
+
);
|
|
3686
|
+
context.subscriptions.push(
|
|
3687
|
+
vscode6.commands.registerCommand("symbolsApp.openChat", () => {
|
|
3688
|
+
vscode6.commands.executeCommand("symbolsChat.focus");
|
|
2803
3689
|
})
|
|
2804
3690
|
);
|
|
2805
3691
|
output.appendLine("Symbols.app extension activated successfully");
|
|
2806
|
-
|
|
3692
|
+
vscode6.window.showInformationMessage("Symbols.app Connect active");
|
|
2807
3693
|
}
|
|
2808
3694
|
function deactivate() {
|
|
2809
3695
|
}
|