feat: initial commit - Jhonny Editor

- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
This commit is contained in:
João Henrique
2026-09-08 09:59:31 -04:00
commit b541f502ba
1507 changed files with 387650 additions and 0 deletions
+250
View File
@@ -0,0 +1,250 @@
/* AI Provider Abstraction Layer
* Supports Claude (Anthropic) and Gemini (Google) APIs.
* Runs inside CEP (Chromium with Node.js access). */
var https = require("https");
// Track the current in-flight request so we can abort it
var _currentRequest = null;
// ---- Provider Configurations ----
var PROVIDERS = {
claude: {
name: "Claude",
icon: "◆",
keyHint: "Get a key at console.anthropic.com",
keyUrl: "https://console.anthropic.com/settings/keys",
models: [
{ id: "claude-sonnet-4-20250514", label: "Claude Sonnet 4 (Best)" },
{ id: "claude-3-5-sonnet-20241022", label: "Claude 3.5 Sonnet" },
{ id: "claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku (Fast)" },
{ id: "claude-3-opus-20240229", label: "Claude 3 Opus" },
],
defaultModel: "claude-sonnet-4-20250514",
},
gemini: {
name: "Gemini",
icon: "✦",
keyHint: "Get a key at aistudio.google.com",
keyUrl: "https://aistudio.google.com/apikey",
models: [
{ id: "gemini-2.5-flash-preview-05-20", label: "Gemini 2.5 Flash (Best)" },
{ id: "gemini-2.0-flash", label: "Gemini 2.0 Flash" },
{ id: "gemini-1.5-pro", label: "Gemini 1.5 Pro" },
{ id: "gemini-1.5-flash", label: "Gemini 1.5 Flash (Fast)" },
],
defaultModel: "gemini-2.5-flash-preview-05-20",
},
};
// ---- System Prompt ----
var BASE_SYSTEM_PROMPT =
"You are an AI assistant embedded inside Adobe Premiere Pro. " +
"You can control Premiere Pro by generating ExtendScript code that runs directly in the application.\n\n" +
"IMPORTANT RULES:\n" +
"1. ExtendScript uses ES3 syntax only: use 'var' (never let/const), no arrow functions, no template literals, no destructuring.\n" +
"2. Always wrap your scripts in a try/catch and return results via the __result() and __error() helper functions that are available globally.\n" +
"3. Available helper functions: __ticksToSeconds(ticks), __secondsToTicks(seconds), __jsonStringify(obj), __result(data), __error(msg).\n" +
"4. The app object is the global Premiere Pro application object.\n" +
"5. To access the active sequence: var seq = app.project.activeSequence;\n" +
"6. To access project items: app.project.rootItem.children\n" +
"7. For QE DOM (advanced): call app.enableQE() first, then use qe.project, qe.source, etc.\n\n" +
"When the user asks you to do something in Premiere Pro:\n" +
"1. Explain what you will do briefly.\n" +
"2. Generate the ExtendScript code in a ```extendscript code block.\n" +
"3. The code will be automatically executed. You'll see the result and can follow up.\n\n" +
"When the user asks a question about their project, generate ExtendScript to query the information.\n" +
"Always be concise and helpful. If an operation fails, explain why and suggest alternatives.";
// ---- Claude (Anthropic) API ----
function callClaude(apiKey, model, messages, systemPrompt, options, callback) {
var body = JSON.stringify({
model: model,
max_tokens: options.maxTokens || 4096,
temperature: typeof options.temperature === "number" ? options.temperature : 0.3,
system: systemPrompt,
messages: messages.map(function (m) {
return { role: m.role, content: m.content };
}),
});
var reqOptions = {
hostname: "api.anthropic.com",
path: "/v1/messages",
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true",
},
};
makeRequest(reqOptions, body, function (err, data) {
if (err) return callback(err, null);
try {
var parsed = JSON.parse(data);
if (parsed.error) {
return callback(parsed.error.message || "API error", null);
}
var text = "";
if (parsed.content && parsed.content.length > 0) {
for (var i = 0; i < parsed.content.length; i++) {
if (parsed.content[i].type === "text") {
text += parsed.content[i].text;
}
}
}
callback(null, {
text: text,
usage: parsed.usage || {},
model: parsed.model,
stopReason: parsed.stop_reason,
});
} catch (e) {
callback("Failed to parse response: " + e.message, null);
}
});
}
// ---- Gemini (Google) API ----
function callGemini(apiKey, model, messages, systemPrompt, options, callback) {
var contents = messages.map(function (m) {
return {
role: m.role === "assistant" ? "model" : "user",
parts: [{ text: m.content }],
};
});
var body = JSON.stringify({
contents: contents,
systemInstruction: {
parts: [{ text: systemPrompt }],
},
generationConfig: {
temperature: typeof options.temperature === "number" ? options.temperature : 0.3,
maxOutputTokens: options.maxTokens || 4096,
},
});
var reqOptions = {
hostname: "generativelanguage.googleapis.com",
path: "/v1beta/models/" + model + ":generateContent",
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": apiKey,
},
};
makeRequest(reqOptions, body, function (err, data) {
if (err) return callback(err, null);
try {
var parsed = JSON.parse(data);
if (parsed.error) {
return callback(parsed.error.message || "API error", null);
}
var text = "";
if (
parsed.candidates &&
parsed.candidates[0] &&
parsed.candidates[0].content
) {
var parts = parsed.candidates[0].content.parts;
for (var i = 0; i < parts.length; i++) {
if (parts[i].text) text += parts[i].text;
}
}
callback(null, {
text: text,
usage: parsed.usageMetadata || {},
model: model,
stopReason:
parsed.candidates &&
parsed.candidates[0] &&
parsed.candidates[0].finishReason,
});
} catch (e) {
callback("Failed to parse response: " + e.message, null);
}
});
}
// ---- Unified Call ----
function callAI(provider, apiKey, model, messages, systemPrompt, options, callback) {
var fullSystemPrompt = BASE_SYSTEM_PROMPT;
if (systemPrompt) {
fullSystemPrompt += "\n\n" + systemPrompt;
}
if (provider === "claude") {
callClaude(apiKey, model, messages, fullSystemPrompt, options, callback);
} else if (provider === "gemini") {
callGemini(apiKey, model, messages, fullSystemPrompt, options, callback);
} else {
callback("Unknown provider: " + provider, null);
}
}
// ---- Validate API Key (quick test call) ----
function validateApiKey(provider, apiKey, model, callback) {
var testMessages = [{ role: "user", content: "Reply with just the word: connected" }];
callAI(provider, apiKey, model, testMessages, "", { maxTokens: 32 }, function (err, result) {
if (err) return callback(false, err);
if (result && result.text) return callback(true, null);
callback(false, "No response received");
});
}
// ---- Abort any in-flight request ----
function abortCurrentRequest() {
if (_currentRequest) {
try { _currentRequest.destroy(); } catch (e) {}
_currentRequest = null;
}
}
// ---- HTTPS Request Helper (Node.js) ----
function makeRequest(options, body, callback) {
abortCurrentRequest();
// Set Content-Length for compatibility with proxies/firewalls
var bodyBuffer = Buffer.from(body, "utf-8");
options.headers = options.headers || {};
options.headers["Content-Length"] = bodyBuffer.length;
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function () {
var data = Buffer.concat(chunks).toString("utf-8");
if (res.statusCode >= 400) {
try {
var errData = JSON.parse(data);
var errMsg =
(errData.error && errData.error.message) || "HTTP " + res.statusCode;
callback(errMsg, null);
} catch (e) {
callback("HTTP " + res.statusCode + ": " + data.substring(0, 200), null);
}
return;
}
callback(null, data);
});
});
req.on("error", function (e) {
callback("Network error: " + e.message, null);
});
req.setTimeout(60000, function () {
req.destroy();
callback("Request timed out (60s)", null);
});
_currentRequest = req;
req.write(bodyBuffer);
req.end();
}