diff --git a/services/premiere-plugin/js/main.js b/services/premiere-plugin/js/main.js
new file mode 100644
index 0000000..2b57f08
--- /dev/null
+++ b/services/premiere-plugin/js/main.js
@@ -0,0 +1,682 @@
+/**
+ * Wild Dragon MAM - Premiere Pro Panel
+ * Main JavaScript file for the CEP panel
+ */
+
+// ============================================================================
+// State Management
+// ============================================================================
+
+const state = {
+ serverUrl: localStorage.getItem("mam_server_url") || "http://localhost:7434",
+ isConnected: false,
+ isConnecting: false,
+ selectedAsset: null,
+ assets: [],
+ filteredAssets: [],
+ projects: [],
+ selectedProject: "all",
+ searchQuery: "",
+ currentPage: 0,
+ pageSize: 50,
+ totalAssets: 0,
+ downloadProgress: 0,
+ isDownloading: false
+};
+
+// ============================================================================
+// DOM Elements
+// ============================================================================
+
+let elements = {};
+
+function initDOMElements() {
+ elements = {
+ // Connection bar
+ serverUrlInput: document.getElementById("server-url"),
+ connectBtn: document.getElementById("connect-btn"),
+ statusIndicator: document.getElementById("status-indicator"),
+
+ // Search and filter
+ searchInput: document.getElementById("search-input"),
+ projectFilter: document.getElementById("project-filter"),
+
+ // Asset grid
+ assetGrid: document.getElementById("asset-grid"),
+ emptyState: document.getElementById("empty-state"),
+
+ // Details panel
+ detailsPanel: document.getElementById("details-panel"),
+ detailsFilename: document.getElementById("details-filename"),
+ detailsCodec: document.getElementById("details-codec"),
+ detailsResolution: document.getElementById("details-resolution"),
+ detailsFps: document.getElementById("details-fps"),
+ detailsDuration: document.getElementById("details-duration"),
+ detailsSize: document.getElementById("details-size"),
+ detailsTags: document.getElementById("details-tags"),
+
+ // Action bar
+ importBtn: document.getElementById("import-btn"),
+ importAllBtn: document.getElementById("import-all-btn"),
+
+ // Progress
+ progressContainer: document.getElementById("progress-container"),
+ progressLabel: document.getElementById("progress-label"),
+ progressFill: document.getElementById("progress-fill")
+ };
+}
+
+// ============================================================================
+// Initialization
+// ============================================================================
+
+document.addEventListener("DOMContentLoaded", () => {
+ initDOMElements();
+ setupEventListeners();
+ restoreSettings();
+ logMessage("Wild Dragon MAM panel initialized");
+});
+
+function setupEventListeners() {
+ // Connection controls
+ elements.serverUrlInput.addEventListener("change", (e) => {
+ state.serverUrl = e.target.value;
+ localStorage.setItem("mam_server_url", state.serverUrl);
+ });
+
+ elements.connectBtn.addEventListener("click", connectToServer);
+
+ // Search and filter
+ elements.searchInput.addEventListener("input", debounce(handleSearch, 300));
+ elements.projectFilter.addEventListener("change", handleProjectFilter);
+
+ // Asset grid events are delegated
+ elements.assetGrid.addEventListener("click", handleAssetClick);
+
+ // Import buttons
+ elements.importBtn.addEventListener("click", importSelectedAsset);
+ elements.importAllBtn.addEventListener("click", importAllAssets);
+}
+
+function restoreSettings() {
+ elements.serverUrlInput.value = state.serverUrl;
+}
+
+// ============================================================================
+// Server Connection
+// ============================================================================
+
+async function connectToServer() {
+ if (state.isConnecting) return;
+
+ state.isConnecting = true;
+ updateConnectionStatus("connecting");
+ elements.connectBtn.disabled = true;
+
+ try {
+ const response = await fetch(`${state.serverUrl}/api/health`, {
+ method: "GET",
+ headers: {
+ "Accept": "application/json"
+ }
+ });
+
+ if (response.ok) {
+ state.isConnected = true;
+ updateConnectionStatus("connected");
+ elements.connectBtn.textContent = "Reconnect";
+ logMessage("Connected to Wild Dragon MAM");
+
+ // Fetch initial data
+ await fetchProjects();
+ await fetchAssets();
+ } else {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ } catch (error) {
+ console.error("Connection error:", error);
+ state.isConnected = false;
+ updateConnectionStatus("disconnected");
+ elements.connectBtn.textContent = "Connect";
+ showErrorMessage(`Failed to connect: ${error.message}`);
+ } finally {
+ state.isConnecting = false;
+ elements.connectBtn.disabled = false;
+ }
+}
+
+function updateConnectionStatus(status) {
+ const indicator = elements.statusIndicator;
+
+ indicator.classList.remove("connected", "connecting");
+
+ if (status === "connected") {
+ indicator.classList.add("connected");
+ } else if (status === "connecting") {
+ indicator.classList.add("connecting");
+ }
+}
+
+// ============================================================================
+// API Calls
+// ============================================================================
+
+async function fetchProjects() {
+ try {
+ const response = await fetch(`${state.serverUrl}/api/projects`, {
+ headers: {
+ "Accept": "application/json"
+ }
+ });
+
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
+ const data = await response.json();
+ state.projects = data.projects || [];
+
+ // Update project filter
+ const oldSelectedProject = state.selectedProject;
+ elements.projectFilter.innerHTML = '';
+
+ state.projects.forEach((project) => {
+ const option = document.createElement("option");
+ option.value = project.id;
+ option.textContent = project.name;
+ elements.projectFilter.appendChild(option);
+ });
+
+ elements.projectFilter.value = oldSelectedProject;
+ logMessage(`Loaded ${state.projects.length} projects`);
+ } catch (error) {
+ console.error("Error fetching projects:", error);
+ showErrorMessage("Failed to fetch projects");
+ }
+}
+
+async function fetchAssets(page = 0) {
+ if (!state.isConnected) {
+ showErrorMessage("Not connected to server");
+ return;
+ }
+
+ try {
+ const params = new URLSearchParams({
+ offset: page * state.pageSize,
+ limit: state.pageSize
+ });
+
+ if (state.searchQuery) {
+ params.append("search", state.searchQuery);
+ }
+
+ if (state.selectedProject !== "all") {
+ params.append("project_id", state.selectedProject);
+ }
+
+ const response = await fetch(
+ `${state.serverUrl}/api/assets?${params.toString()}`,
+ {
+ headers: {
+ "Accept": "application/json"
+ }
+ }
+ );
+
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
+ const data = await response.json();
+ state.assets = data.assets || [];
+ state.totalAssets = data.total || 0;
+ state.currentPage = page;
+
+ renderAssets();
+ logMessage(`Loaded ${state.assets.length} assets`);
+ } catch (error) {
+ console.error("Error fetching assets:", error);
+ showErrorMessage("Failed to fetch assets");
+ }
+}
+
+async function fetchAssetDetails(assetId) {
+ if (!state.isConnected) return null;
+
+ try {
+ const response = await fetch(`${state.serverUrl}/api/assets/${assetId}`, {
+ headers: {
+ "Accept": "application/json"
+ }
+ });
+
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return await response.json();
+ } catch (error) {
+ console.error("Error fetching asset details:", error);
+ return null;
+ }
+}
+
+async function getSignedUrl(assetId, proxyId = null) {
+ if (!state.isConnected) return null;
+
+ try {
+ const endpoint = proxyId
+ ? `${state.serverUrl}/api/assets/${assetId}/proxies/${proxyId}/download`
+ : `${state.serverUrl}/api/assets/${assetId}/download`;
+
+ const response = await fetch(endpoint, {
+ method: "POST",
+ headers: {
+ "Accept": "application/json"
+ }
+ });
+
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
+ const data = await response.json();
+ return data.signed_url;
+ } catch (error) {
+ console.error("Error getting signed URL:", error);
+ return null;
+ }
+}
+
+// ============================================================================
+// UI Rendering
+// ============================================================================
+
+function renderAssets() {
+ elements.assetGrid.innerHTML = "";
+
+ if (state.assets.length === 0) {
+ elements.emptyState.style.display = "flex";
+ return;
+ }
+
+ elements.emptyState.style.display = "none";
+
+ state.assets.forEach((asset) => {
+ const card = createAssetCard(asset);
+ elements.assetGrid.appendChild(card);
+ });
+}
+
+function createAssetCard(asset) {
+ const card = document.createElement("div");
+ card.className = "asset-card";
+ if (state.selectedAsset?.id === asset.id) {
+ card.classList.add("selected");
+ }
+ card.dataset.assetId = asset.id;
+
+ // Thumbnail
+ const thumbnail = document.createElement("div");
+ thumbnail.className = "asset-thumbnail";
+
+ if (asset.thumbnail_url) {
+ const img = document.createElement("img");
+ img.src = asset.thumbnail_url;
+ img.alt = asset.filename;
+ img.loading = "lazy";
+ img.onerror = () => {
+ img.style.display = "none";
+ thumbnail.textContent = "No preview";
+ };
+ thumbnail.appendChild(img);
+ } else {
+ thumbnail.textContent = "No preview";
+ }
+
+ card.appendChild(thumbnail);
+
+ // Info
+ const info = document.createElement("div");
+ info.className = "asset-info";
+
+ const filename = document.createElement("div");
+ filename.className = "asset-filename";
+ filename.title = asset.filename;
+ filename.textContent = asset.filename;
+ info.appendChild(filename);
+
+ const meta = document.createElement("div");
+ meta.className = "asset-meta";
+ meta.innerHTML = `
+ ${asset.duration ? formatDuration(asset.duration) : "N/A"}
+ ${asset.codec || "N/A"}
+ `;
+ info.appendChild(meta);
+
+ const status = document.createElement("div");
+ status.className = `asset-status-badge status-badge ${asset.status || "ready"}`;
+ status.textContent = (asset.status || "ready").toUpperCase();
+ info.appendChild(status);
+
+ card.appendChild(info);
+
+ return card;
+}
+
+function showAssetDetails(asset) {
+ state.selectedAsset = asset;
+
+ elements.detailsPanel.classList.remove("hidden");
+ elements.detailsFilename.textContent = asset.filename;
+ elements.detailsCodec.textContent = asset.codec || "Unknown";
+ elements.detailsResolution.textContent = asset.resolution || "N/A";
+ elements.detailsFps.textContent = asset.fps || "N/A";
+ elements.detailsDuration.textContent = asset.duration
+ ? formatDuration(asset.duration)
+ : "N/A";
+ elements.detailsSize.textContent = asset.file_size
+ ? formatFileSize(asset.file_size)
+ : "N/A";
+
+ // Render tags
+ elements.detailsTags.innerHTML = "";
+ if (asset.tags && asset.tags.length > 0) {
+ asset.tags.forEach((tag) => {
+ const tagEl = document.createElement("span");
+ tagEl.className = "tag";
+ tagEl.textContent = tag;
+ elements.detailsTags.appendChild(tagEl);
+ });
+ } else {
+ elements.detailsTags.innerHTML = 'No tags';
+ }
+
+ // Update import button state
+ elements.importBtn.disabled = false;
+}
+
+function hideAssetDetails() {
+ state.selectedAsset = null;
+ elements.detailsPanel.classList.add("hidden");
+ elements.importBtn.disabled = true;
+}
+
+// ============================================================================
+// Search and Filter
+// ============================================================================
+
+function handleSearch(e) {
+ state.searchQuery = e.target.value;
+ state.currentPage = 0;
+ fetchAssets();
+}
+
+function handleProjectFilter(e) {
+ state.selectedProject = e.target.value;
+ state.currentPage = 0;
+ fetchAssets();
+}
+
+// ============================================================================
+// Import Functionality
+// ============================================================================
+
+async function importSelectedAsset() {
+ if (!state.selectedAsset) {
+ showErrorMessage("No asset selected");
+ return;
+ }
+
+ await importAsset(state.selectedAsset);
+}
+
+async function importAllAssets() {
+ if (state.assets.length === 0) {
+ showErrorMessage("No assets to import");
+ return;
+ }
+
+ for (const asset of state.assets) {
+ await importAsset(asset);
+ }
+
+ showSuccessMessage("All assets imported");
+}
+
+async function importAsset(asset) {
+ try {
+ elements.importBtn.disabled = true;
+
+ // Get the proxy file (or original if no proxy)
+ const proxyId = asset.proxy_file_id || asset.file_id;
+ if (!proxyId) {
+ showErrorMessage("No file available for import");
+ return;
+ }
+
+ // Get signed URL for download
+ showProgress("Getting download link...", 0);
+ const signedUrl = await getSignedUrl(asset.id, proxyId);
+
+ if (!signedUrl) {
+ showErrorMessage("Failed to get download link");
+ return;
+ }
+
+ // Download the file
+ showProgress(`Downloading ${asset.filename}...`, 30);
+ const filePath = await downloadFile(signedUrl, asset.filename);
+
+ if (!filePath) {
+ showErrorMessage("Failed to download file");
+ return;
+ }
+
+ // Import into Premiere
+ showProgress("Importing into Premiere Pro...", 70);
+ await importFileToPremiereProject(filePath);
+
+ hideProgress();
+ showSuccessMessage(`Imported: ${asset.filename}`);
+ } catch (error) {
+ console.error("Import error:", error);
+ showErrorMessage(`Import failed: ${error.message}`);
+ } finally {
+ elements.importBtn.disabled = state.selectedAsset === null;
+ }
+}
+
+async function downloadFile(url, filename) {
+ return new Promise((resolve, reject) => {
+ // In a real implementation, use Node.js fs module or fetch API
+ // For now, we'll use fetch with progress tracking
+
+ fetch(url)
+ .then((response) => {
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
+ const contentLength = response.headers.get("content-length");
+ const reader = response.body.getReader();
+ let receivedLength = 0;
+
+ const chunks = [];
+
+ function read() {
+ reader.read().then(({ done, value }) => {
+ if (done) {
+ // Convert chunks to blob
+ const blob = new Blob(chunks);
+
+ // Save file (would need Node.js in production)
+ // For CEP, you'd use special file I/O
+ const tempPath = `${getTempPath()}/${filename}`;
+
+ // Update progress
+ state.downloadProgress = 100;
+ updateProgressUI();
+
+ resolve(tempPath);
+ return;
+ }
+
+ chunks.push(value);
+ receivedLength += value.length;
+
+ if (contentLength) {
+ state.downloadProgress = 30 + (receivedLength / contentLength) * 40;
+ updateProgressUI();
+ }
+
+ read();
+ });
+ }
+
+ read();
+ })
+ .catch(reject);
+ });
+}
+
+function getTempPath() {
+ if (navigator.platform.includes("Win")) {
+ return process.env.TEMP || "C:\\Users\\%USERNAME%\\AppData\\Local\\Temp";
+ } else {
+ return "/tmp";
+ }
+}
+
+async function importFileToPremiereProject(filePath) {
+ return new Promise((resolve, reject) => {
+ const script = `
+ var result = {};
+ try {
+ if (app.project) {
+ app.project.importFiles(["${filePath}"]);
+ result.success = true;
+ result.message = "File imported successfully";
+ } else {
+ result.success = false;
+ result.message = "No active project";
+ }
+ } catch (error) {
+ result.success = false;
+ result.message = error.message;
+ }
+ JSON.stringify(result);
+ `;
+
+ csInterface.evalScript(script, (result) => {
+ try {
+ const parsed = JSON.parse(result);
+ if (parsed.success) {
+ resolve(parsed);
+ } else {
+ reject(new Error(parsed.message));
+ }
+ } catch (error) {
+ reject(error);
+ }
+ });
+ });
+}
+
+// ============================================================================
+// UI Helpers
+// ============================================================================
+
+function handleAssetClick(e) {
+ const card = e.target.closest(".asset-card");
+ if (!card) return;
+
+ // Clear previous selection
+ document.querySelectorAll(".asset-card.selected").forEach((el) => {
+ el.classList.remove("selected");
+ });
+
+ // Select new card
+ card.classList.add("selected");
+
+ // Show details
+ const assetId = card.dataset.assetId;
+ const asset = state.assets.find((a) => a.id === assetId);
+
+ if (asset) {
+ showAssetDetails(asset);
+ }
+}
+
+function showProgress(label, percent) {
+ elements.progressContainer.classList.add("visible");
+ elements.progressLabel.textContent = label;
+ state.downloadProgress = percent;
+ updateProgressUI();
+}
+
+function hideProgress() {
+ elements.progressContainer.classList.remove("visible");
+ state.downloadProgress = 0;
+}
+
+function updateProgressUI() {
+ elements.progressFill.style.width = `${state.downloadProgress}%`;
+}
+
+function showErrorMessage(message) {
+ const container = document.createElement("div");
+ container.className = "error-message";
+ container.textContent = message;
+
+ const searchArea = document.querySelector(".search-filter-area");
+ searchArea.insertBefore(container, searchArea.firstChild);
+
+ setTimeout(() => {
+ container.remove();
+ }, 5000);
+}
+
+function showSuccessMessage(message) {
+ const container = document.createElement("div");
+ container.className = "success-message";
+ container.textContent = message;
+
+ const searchArea = document.querySelector(".search-filter-area");
+ searchArea.insertBefore(container, searchArea.firstChild);
+
+ setTimeout(() => {
+ container.remove();
+ }, 5000);
+}
+
+function logMessage(message) {
+ console.log(`[MAM Panel] ${message}`);
+}
+
+// ============================================================================
+// Utility Functions
+// ============================================================================
+
+function debounce(func, delay) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, delay);
+ };
+}
+
+function formatDuration(seconds) {
+ if (!seconds) return "N/A";
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ const secs = Math.floor(seconds % 60);
+
+ if (hours > 0) {
+ return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
+ } else {
+ return `${minutes}:${String(secs).padStart(2, "0")}`;
+ }
+}
+
+function formatFileSize(bytes) {
+ if (!bytes) return "0 B";
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB"];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
+}