/* Premiere Pro AI Chat — Main Panel Logic * Handles UI state, chat flow, ExtendScript execution, and settings. */ var cs = new CSInterface(); // ---- Constants ---- var MAX_HISTORY = 50; // Cap conversation history to prevent token overflow // ---- State ---- var state = { provider: "claude", apiKey: "", model: "", messages: [], // { role: "user"|"assistant", content: string } isStreaming: false, autoExec: true, temperature: 0.3, maxTokens: 4096, customSystemPrompt: "", projectContext: null, scriptQueue: [], // Sequential script execution queue scriptRunning: false, }; // ---- Provider Selection (Login Screen) ---- function selectProvider(provider) { state.provider = provider; var tabs = document.querySelectorAll(".tab"); for (var i = 0; i < tabs.length; i++) { tabs[i].classList.toggle("active", tabs[i].dataset.provider === provider); } updateProviderUI(); } function updateProviderUI() { var config = PROVIDERS[state.provider]; var hint = document.getElementById("providerHint"); var link = document.getElementById("providerLink"); hint.innerHTML = "Get a key at " + config.keyUrl.replace("https://", "") + ""; var select = document.getElementById("modelSelect"); select.innerHTML = ""; for (var i = 0; i < config.models.length; i++) { var opt = document.createElement("option"); opt.value = config.models[i].id; opt.textContent = config.models[i].label; select.appendChild(opt); } select.value = config.defaultModel; } function openLink(url) { // Validate URL to prevent shell injection if (!url || !/^https?:\/\//i.test(url)) { console.warn("[openLink] Blocked non-HTTP URL: " + url); return; } try { var cp = require("child_process"); var os = require("os"); var safeUrl = url.replace(/["\\`$!]/g, ""); // strip dangerous chars if (os.platform() === "win32") { cp.exec('start "" "' + safeUrl + '"'); } else { cp.exec('open "' + safeUrl + '"'); } } catch (e) { console.log("Could not open URL: " + url); } } // ---- Login ---- function login() { var apiKey = document.getElementById("apiKeyInput").value.trim(); if (!apiKey) { showLoginError("Please enter an API key."); return; } var model = document.getElementById("modelSelect").value; var btn = document.getElementById("loginBtn"); btn.disabled = true; btn.textContent = "Connecting..."; hideLoginError(); validateApiKey(state.provider, apiKey, model, function (valid, error) { btn.disabled = false; btn.textContent = "Connect & Start Chatting"; if (!valid) { showLoginError("Connection failed: " + (error || "Unknown error")); return; } state.apiKey = apiKey; state.model = model; // Persist non-sensitive preferences only. Keep the API key in memory for // this panel session so it is not exposed through browser storage. try { localStorage.setItem("ai_chat_provider", state.provider); localStorage.setItem("ai_chat_model", model); } catch (e) {} showChatScreen(); }); } function logout() { state.apiKey = ""; state.messages = []; state.projectContext = null; try { localStorage.removeItem("ai_chat_api_key"); } catch (e) {} showLoginScreen(); } function showLoginError(msg) { var el = document.getElementById("loginError"); el.textContent = msg; el.style.display = "block"; } function hideLoginError() { document.getElementById("loginError").style.display = "none"; } function toggleKeyVisibility() { var input = document.getElementById("apiKeyInput"); var icon = document.getElementById("eyeIcon"); if (input.type === "password") { input.type = "text"; icon.textContent = "🙈"; } else { input.type = "password"; icon.textContent = "👁"; } } // ---- Screen Navigation ---- function showLoginScreen() { document.getElementById("loginScreen").style.display = "flex"; document.getElementById("chatScreen").style.display = "none"; } function showChatScreen() { document.getElementById("loginScreen").style.display = "none"; document.getElementById("chatScreen").style.display = "flex"; var config = PROVIDERS[state.provider]; document.getElementById("headerTitle").textContent = config.name; document.getElementById("headerModel").textContent = state.model; // Refresh project context refreshContext(); } // ---- Chat ---- function sendMessage() { var input = document.getElementById("chatInput"); var text = input.value.trim(); if (!text || state.isStreaming) return; input.value = ""; autoResizeInput(); // Remove welcome message var welcome = document.querySelector(".welcome-msg"); if (welcome) welcome.remove(); addMessage("user", text); state.messages.push({ role: "user", content: text }); // Trim history to prevent token overflow trimHistory(); sendToAI(); } function sendSuggestion(text) { document.getElementById("chatInput").value = text; sendMessage(); } function trimHistory() { // Keep only the last MAX_HISTORY messages to avoid token overflow if (state.messages.length > MAX_HISTORY) { state.messages = state.messages.slice(state.messages.length - MAX_HISTORY); } } function clearChat() { state.messages = []; state.scriptQueue = []; state.scriptRunning = false; var container = document.getElementById("messages"); container.innerHTML = ""; // Re-add welcome message var welcome = document.createElement("div"); welcome.className = "welcome-msg"; welcome.innerHTML = '
Welcome! I can help you edit in Premiere Pro. Try:
' + '" + escapeHtml(inlineCodes[i]) + "");
}
// Restore code blocks (escaped content)
for (var j = 0; j < codeBlocks.length; j++) {
var cls = codeBlocks[j].lang ? ' class="lang-' + escapeHtml(codeBlocks[j].lang) + '"' : "";
html = html.replace(placeholder + j + "\x00",
'' + escapeHtml(codeBlocks[j].code) + '');
}
return html;
}
function escapeHtml(text) {
var div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
function showTypingIndicator() {
var container = document.getElementById("messages");
var typing = document.createElement("div");
typing.className = "msg assistant";
typing.id = "typingIndicator";
typing.innerHTML = '" + escapeHtml(script) + "";
scriptBlock.appendChild(header);
scriptBlock.appendChild(code);
block.appendChild(scriptBlock);
msgDiv.appendChild(block);
container.appendChild(msgDiv);
container.scrollTop = container.scrollHeight;
}
// ---- Project Context ----
function refreshContext() {
cs.evalScript("getProjectContext()", function (result) {
try {
var parsed = JSON.parse(result);
if (parsed.success && parsed.data) {
state.projectContext = parsed.data;
var banner = document.getElementById("contextBanner");
var text = document.getElementById("contextText");
banner.style.display = "flex";
if (parsed.data.hasProject) {
var info = parsed.data.name;
if (parsed.data.activeSequence) {
info += " → " + parsed.data.activeSequence.name;
}
text.textContent = info;
} else {
text.textContent = "No project open";
}
}
} catch (e) {
// Not in CEP environment
var banner = document.getElementById("contextBanner");
banner.style.display = "flex";
document.getElementById("contextText").textContent = "Not connected to Premiere Pro";
}
});
}
// ---- Settings ----
function openSettings() {
var modal = document.getElementById("settingsModal");
modal.style.display = "flex";
// Populate settings
var config = PROVIDERS[state.provider];
var select = document.getElementById("settingsModel");
select.innerHTML = "";
for (var i = 0; i < config.models.length; i++) {
var opt = document.createElement("option");
opt.value = config.models[i].id;
opt.textContent = config.models[i].label;
select.appendChild(opt);
}
select.value = state.model;
document.getElementById("settingsTemp").value = state.temperature;
document.getElementById("settingsTempVal").textContent = state.temperature;
document.getElementById("settingsMaxTokens").value = state.maxTokens;
document.getElementById("settingsSystemPrompt").value = state.customSystemPrompt;
document.getElementById("settingsAutoExec").checked = state.autoExec;
// Bind temp slider
document.getElementById("settingsTemp").oninput = function () {
document.getElementById("settingsTempVal").textContent = this.value;
};
}
function closeSettings() {
document.getElementById("settingsModal").style.display = "none";
}
function saveSettings() {
state.model = document.getElementById("settingsModel").value;
state.temperature = parseFloat(document.getElementById("settingsTemp").value);
state.maxTokens = parseInt(document.getElementById("settingsMaxTokens").value, 10);
state.customSystemPrompt = document.getElementById("settingsSystemPrompt").value;
state.autoExec = document.getElementById("settingsAutoExec").checked;
document.getElementById("headerModel").textContent = state.model;
// Persist
try {
localStorage.setItem("ai_chat_model", state.model);
localStorage.setItem("ai_chat_temperature", String(state.temperature));
localStorage.setItem("ai_chat_max_tokens", String(state.maxTokens));
localStorage.setItem("ai_chat_system_prompt", state.customSystemPrompt);
localStorage.setItem("ai_chat_auto_exec", String(state.autoExec));
} catch (e) {}
closeSettings();
}
function changeApiKey() {
closeSettings();
logout();
}
// ---- Init ----
(function init() {
updateProviderUI();
// Restore saved settings
try {
// Remove keys persisted by older releases.
localStorage.removeItem("ai_chat_api_key");
var savedProvider = localStorage.getItem("ai_chat_provider");
var savedModel = localStorage.getItem("ai_chat_model");
var savedTemp = localStorage.getItem("ai_chat_temperature");
var savedMaxTokens = localStorage.getItem("ai_chat_max_tokens");
var savedSystemPrompt = localStorage.getItem("ai_chat_system_prompt");
var savedAutoExec = localStorage.getItem("ai_chat_auto_exec");
if (savedProvider) {
state.provider = savedProvider;
selectProvider(savedProvider);
}
if (savedTemp) state.temperature = parseFloat(savedTemp);
if (savedMaxTokens) state.maxTokens = parseInt(savedMaxTokens, 10);
if (savedSystemPrompt) state.customSystemPrompt = savedSystemPrompt;
if (savedAutoExec !== null) state.autoExec = savedAutoExec === "true";
if (savedModel) document.getElementById("modelSelect").value = savedModel;
} catch (e) {}
showLoginScreen();
})();