Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions phoenix-builder-mcp/mcp-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,12 @@ export function registerTools(server, processManager, wsControlServer, phoenixDe
"Execute JavaScript in the Phoenix Code browser runtime and return the result. " +
"Code runs async in the page context with access to: " +
"$ (jQuery) for DOM queries/clicks, " +
"brackets.test.CommandManager, brackets.test.EditorManager, brackets.test.ProjectManager, " +
"brackets.test.DocumentManager, brackets.test.FileSystem, brackets.test.FileUtils, " +
"and 50+ other modules on brackets.test.* — " +
"supports await.",
"brackets.test.CommandManager, brackets.test.EditorManager, " +
"brackets.test.ProjectManager, brackets.test.DocumentManager, " +
"brackets.test.FileSystem, brackets.test.FileUtils, " +
"and 50+ other modules on brackets.test.* — supports await. " +
"__kernalModeTrust is available as a parameter " +
"(deleted from window after boot, preserved here for dev/test).",
{
code: z.string().describe("JavaScript code to execute in Phoenix"),
instance: z.string().optional().describe("Target a specific Phoenix instance by name (e.g. 'Phoenix-a3f2'). Required when multiple instances are connected.")
Expand Down
16 changes: 12 additions & 4 deletions src-node/mcp-editor-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)

const takeScreenshotTool = sdkModule.tool(
"takeScreenshot",
"Take a screenshot of the Phoenix Code editor window. Returns a PNG image. " +
"Take a screenshot of the Phoenix Code editor window. " +
"By default returns the screenshot as a PNG image inline. " +
"If filePath is specified, saves the screenshot to that file and returns the file path instead. " +
"Prefer capturing specific regions instead of the full page: " +
"use selector '#panel-live-preview-frame' for the live preview content, " +
"or '.editor-holder' for the code editor area. " +
Expand All @@ -95,16 +97,22 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
"and other editor UI elements. Use purePreview=true to temporarily hide these overlays.",
{
selector: z.string().optional().describe("CSS selector to capture a specific element. Use '#panel-live-preview-frame' for the live preview, '.editor-holder' for the code editor."),
purePreview: z.boolean().optional().describe("When true, temporarily switches to preview mode to hide element highlight overlays and toolboxes before capturing, then restores the previous mode.")
purePreview: z.boolean().optional().describe("When true, temporarily switches to preview mode to hide element highlight overlays and toolboxes before capturing, then restores the previous mode."),
filePath: z.string().optional().describe("Absolute path to save the screenshot as a PNG file. If specified, returns the file path instead of inline image data.")
},
async function (args) {
let toolResult;
try {
const result = await nodeConnector.execPeer("takeScreenshot", {
selector: args.selector || undefined,
purePreview: args.purePreview || false
purePreview: args.purePreview || false,
filePath: args.filePath || undefined
});
if (result.base64) {
if (result.filePath) {
toolResult = {
content: [{ type: "text", text: "Screenshot saved to: " + result.filePath }]
};
} else if (result.base64) {
toolResult = {
content: [{ type: "image", data: result.base64, mimeType: "image/png" }]
};
Expand Down
2 changes: 2 additions & 0 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1914,6 +1914,8 @@ define({
"AI_CHAT_QUESTION_OTHER": "Type a custom answer\u2026",
"AI_CHAT_IMAGE_LIMIT": "Maximum {0} images allowed",
"AI_CHAT_IMAGE_REMOVE": "Remove image",
"AI_CHAT_ATTACH_FILE": "Attach files",
"AI_CHAT_FILE_REMOVE": "Remove file",
"AI_CHAT_QUEUED": "Queued",
"AI_CHAT_QUEUED_EDIT": "Edit",
"AI_CHAT_TOOL_CLARIFICATION": "Reading your follow-up",
Expand Down
5 changes: 3 additions & 2 deletions src/phoenix-builder/phoenix-builder-boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,11 @@

// --- Register built-in handler for exec_js_request ---
// Evaluates arbitrary JS in the page context and returns the result.
// `__kernalModeTrust` is available inside exec_js code for dev/test access.
registerHandler("exec_js_request", function (msg) {
const AsyncFunction = (async function () {}).constructor;
const fn = new AsyncFunction(msg.code);
fn().then(function (result) {
const fn = new AsyncFunction("__kernalModeTrust", msg.code);
fn(_kernalModeTrust).then(function (result) {
_sendMessage({
type: "exec_js_response",
id: msg.id,
Expand Down
95 changes: 95 additions & 0 deletions src/styles/Extn-AIChatPanel.less
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,34 @@
}
}

/* ── User message file chips ────────────────────────────────────────── */
.ai-user-files {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 6px;
justify-content: flex-end;

.ai-user-file-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
font-size: 11px;
color: @project-panel-text-2;
max-width: 180px;

.ai-file-chip-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}

/* ── User message image thumbnails ──────────────────────────────────── */
.ai-user-images {
display: flex;
Expand Down Expand Up @@ -1352,6 +1380,73 @@
display: flex;
}
}

.ai-file-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 6px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 4px;
font-size: 11px;
color: @project-panel-text-2;
max-width: 150px;
position: relative;

.ai-file-chip-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.ai-file-remove {
position: absolute;
top: -6px;
right: -6px;
width: 16px;
height: 16px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.2);
color: @project-panel-text-2;
font-size: 11px;
padding: 0;
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
}

&:hover .ai-file-remove {
display: flex;
}
}
}

.ai-attach-btn {
background: none;
border: none;
color: @project-panel-text-2;
width: 28px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
align-self: stretch;
border-radius: 7px 0 0 7px;
opacity: 0.5;
transition: opacity 0.15s ease, color 0.15s ease;

&:hover {
opacity: 1;
color: @project-panel-text-1;
}

i {
font-size: @sidebar-content-font-size;
}
}

.ai-chat-textarea {
Expand Down
Loading