Skip to content

Commit dad4d2e

Browse files
committed
ai control extension
1 parent a6dab21 commit dad4d2e

File tree

7 files changed

+246
-89
lines changed

7 files changed

+246
-89
lines changed

html/browser-message.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">
2+
<h3>Phoenix AI Control Status</h3>
3+
<div style="background-color: #f9f9f9; border-left: 4px solid #007acc; padding: 16px; margin: 16px 0; border-radius: 4px;">
4+
<p><strong>Browser Version</strong></p>
5+
<p>Managed AI control is not yet supported in the browser version of Phoenix Code.</p>
6+
</div>
7+
<p>For more information on controlling AI in educational institutions, visit:
8+
<a href="https://docs.phcode.dev/docs/control-ai" target="_blank">https://docs.phcode.dev/docs/control-ai</a></p>
9+
</div>

html/error-template.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">
2+
<h3>Error Checking AI Control Status</h3>
3+
<div style="background-color: #ffecec; border-left: 4px solid #f44336; padding: 16px; margin: 16px 0; border-radius: 4px;">
4+
<p>An error occurred while checking AI control configuration:</p>
5+
<p><code>{{errorMessage}}</code></p>
6+
</div>
7+
<p>For more information on controlling AI in educational institutions, visit:
8+
<a href="https://docs.phcode.dev/docs/control-ai" target="_blank">https://docs.phcode.dev/docs/control-ai</a></p>
9+
</div>

html/status-template.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">
2+
<h3>Phoenix AI Control Status</h3>
3+
4+
<div style="background-color: #f9f9f9; border-left: 4px solid {{statusColor}}; padding: 16px; margin: 16px 0; border-radius: 4px;">
5+
<p style="font-size: 16px;"><strong>AI Status:</strong> <span style="color: {{statusColor}};">{{statusIcon}} {{statusText}}</span></p>
6+
<p>{{status.message}}</p>
7+
</div>
8+
9+
{{#showConfigDetails}}
10+
<div style="margin: 16px 0;">
11+
<h4>Configuration Details</h4>
12+
<table style="width: 100%; border-collapse: collapse;">
13+
<tr>
14+
<td style="padding: 8px; border-bottom: 1px solid #eee;"><strong>Platform:</strong></td>
15+
<td style="padding: 8px; border-bottom: 1px solid #eee;">{{status.platform}}</td>
16+
</tr>
17+
<tr>
18+
<td style="padding: 8px; border-bottom: 1px solid #eee;"><strong>Configuration File:</strong></td>
19+
<td style="padding: 8px; border-bottom: 1px solid #eee; word-break: break-all;">{{status.configPath}}</td>
20+
</tr>
21+
<tr>
22+
<td style="padding: 8px; border-bottom: 1px solid #eee;"><strong>Current User:</strong></td>
23+
<td style="padding: 8px; border-bottom: 1px solid #eee;">{{status.currentUser}}</td>
24+
</tr>
25+
<tr>
26+
<td style="padding: 8px; border-bottom: 1px solid #eee;"><strong>Managed By:</strong></td>
27+
<td style="padding: 8px; border-bottom: 1px solid #eee;">{{status.managedBy}}</td>
28+
</tr>
29+
{{#hasAllowedUsers}}
30+
<tr>
31+
<td style="padding: 8px; border-bottom: 1px solid #eee;"><strong>Allowed Users:</strong></td>
32+
<td style="padding: 8px; border-bottom: 1px solid #eee;">{{allowedUsersList}}</td>
33+
</tr>
34+
{{/hasAllowedUsers}}
35+
</table>
36+
</div>
37+
{{/showConfigDetails}}
38+
39+
<div style="margin-top: 24px;">
40+
<h4>Need to Control AI in Your Educational Institution?</h4>
41+
<p>Visit <a href="https://docs.phcode.dev/docs/control-ai" target="_blank">https://docs.phcode.dev/docs/control-ai</a> for documentation on how to:</p>
42+
<ul>
43+
<li>Disable AI features by default</li>
44+
<li>Allow specific users to access AI</li>
45+
<li>Set up managed AI deployments</li>
46+
</ul>
47+
</div>
48+
</div>

main.js

Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
/**
2-
* A simple extension that downloads an image and then converts it to grey scale using `sharp` node.js lib via
3-
* Phoenix Code node.js Extension. Extension can be activated using menu: `file->Download Image & Greyscale`.
4-
* */
2+
* Phoenix AI Control Extension
3+
* Verifies and displays the AI control configuration status for educational institutions.
4+
*/
55

66
/*global define, brackets, $ */
77

88
// See detailed docs in https://docs.phcode.dev/api/creating-extensions
99
// A good place to look for code examples for extensions: https://github.com/phcode-dev/phoenix/tree/main/src/extensions/default
1010

11-
1211
define(function (require, exports, module) {
1312
"use strict";
1413

@@ -18,82 +17,71 @@ define(function (require, exports, module) {
1817
Dialogs = brackets.getModule("widgets/Dialogs"),
1918
CommandManager = brackets.getModule("command/CommandManager"),
2019
Menus = brackets.getModule("command/Menus"),
21-
NodeConnector = brackets.getModule("NodeConnector");
22-
23-
let nodeConnector;
24-
25-
async function fetchImage() {
26-
const imageUrl = "https://picsum.photos/536/354";
27-
const response = await fetch(imageUrl);
20+
NodeConnector = brackets.getModule("NodeConnector"),
21+
Mustache = brackets.getModule("thirdparty/mustache/mustache");
2822

29-
if (!response.ok) {
30-
throw new Error(
31-
`Failed to fetch image (status ${response.status})`
32-
);
33-
}
23+
// HTML Templates
24+
const browserMessageTemplate = require("text!./html/browser-message.html"),
25+
statusTemplate = require("text!./html/status-template.html"),
26+
errorTemplate = require("text!./html/error-template.html");
3427

35-
return response.arrayBuffer();
36-
}
28+
let nodeConnector;
3729

3830
// Function to run when the menu item is clicked
39-
async function handleHelloWorld() {
40-
if (!Phoenix.isNativeApp) {
41-
alert("Node Features only works in desktop apps.");
42-
return;
43-
}
44-
let html = "<b>Image conversion failed</b>";
45-
try {
46-
alert("downloading image...");
47-
// Fetch the image and get its array buffer
48-
const imageArrayBuffer = await fetchImage();
49-
50-
// Call the nodeConnector to convert the image to grayscale
51-
const { buffer, success } = await nodeConnector.execPeer(
52-
"convertToGreyScale",
53-
{ imageName: "imageName" },
54-
imageArrayBuffer
55-
);
31+
async function checkAIControlStatus() {
32+
let html;
5633

57-
if (!success) {
58-
alert("Image conversion failed in Node.");
59-
return;
34+
if (!Phoenix.isNativeApp) {
35+
html = browserMessageTemplate;
36+
} else {
37+
try {
38+
// Call the nodeConnector to get AI control status
39+
const status = await nodeConnector.execPeer("getAIControlStatus");
40+
41+
// Prepare view model for Mustache
42+
const viewModel = {
43+
statusColor: status.isEnabled ? "#4caf50" : "#f44336", // Green if enabled, red if disabled
44+
statusIcon: status.isEnabled ? "✓" : "✗",
45+
statusText: status.isEnabled ? "Enabled" : "Disabled",
46+
status: status,
47+
showConfigDetails: status.exists && status.isConfigured,
48+
hasAllowedUsers: status.allowedUsers && status.allowedUsers.length > 0,
49+
allowedUsersList: status.allowedUsers ? status.allowedUsers.join(", ") : ""
50+
};
51+
52+
// Render the template with Mustache
53+
html = Mustache.render(statusTemplate, viewModel);
54+
55+
} catch (error) {
56+
console.error("Error checking AI control status:", error);
57+
58+
// Render error template with Mustache
59+
html = Mustache.render(errorTemplate, {
60+
errorMessage: error.message || "Unknown error"
61+
});
6062
}
61-
62-
// Construct HTML with the grayscale image array buffer
63-
// For example, you can use the buffer as a base64 data URL
64-
html = `<img src="data:image/jpeg;base64,${Buffer.from(buffer).toString("base64")}">`;
65-
} catch (error) {
66-
console.error("Error:", error);
6763
}
68-
Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, "Image to greyscale with node.js", html);
64+
65+
Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, "Phoenix AI Control Status", html);
6966
}
7067

71-
// First, register a command - a UI-less object associating an id to a handler
72-
var MY_COMMAND_ID = "helloworld.imageConvert"; // package-style naming to avoid collisions
73-
CommandManager.register("Download Image & Greyscale", MY_COMMAND_ID, handleHelloWorld);
68+
// Register command for AI Control Status check
69+
var AI_CONTROL_STATUS_ID = "phoenix.aiControlStatus";
70+
CommandManager.register("Check AI Control Status", AI_CONTROL_STATUS_ID, checkAIControlStatus);
7471

75-
// Then create a menu item bound to the command
76-
// The label of the menu item is the name we gave the command (see above)
72+
// Add menu item to File menu
7773
var menu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);
78-
menu.addMenuItem(MY_COMMAND_ID);
79-
80-
// We could also add a key binding at the same time:
81-
//menu.addMenuItem(MY_COMMAND_ID, "Ctrl-Alt-W");
82-
// (Note: "Ctrl" is automatically mapped to "Cmd" on Mac)
74+
menu.addMenuItem(AI_CONTROL_STATUS_ID);
8375

84-
// Initialize extension once shell is finished initializing.
76+
// Initialize extension once shell is finished initializing
8577
AppInit.appReady(function () {
86-
// nb: Please enable `Debug menu> Phoenix code diagnostic tools> enable detailed logs` to view all console logs.`
87-
console.log("hello world");
78+
console.log("Phoenix AI Control extension initialized");
8879

8980
if (Phoenix.isNativeApp) {
9081
nodeConnector = NodeConnector.createNodeConnector(
9182
"github-phcode-dev-phoenix-code-ai-control",
9283
exports
9384
);
94-
// you can also execute nodejs code in dekstop builds
95-
// below code will execute the function `echoTest` defined in `node/index.js`
96-
nodeConnector.execPeer("echoTest", "yo!").then(console.log);
9785
}
9886
});
9987
});

node/index.js

Lines changed: 120 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,140 @@
11
/**
2-
* This is an optional node extension that is available only in desktop builds. This code will be run in node. You can
3-
* bring in any third party node modules using package.json in folder `node/package.json`
4-
*
5-
* nb: Please note that you should not package `node_modules` folder when you care creating the extension zip file.
6-
* Zip `node/package-lock.json`, but not node modules.
2+
* Phoenix AI Control - Node.js component
3+
* Verifies AI control configuration for educational institutions
74
*
85
* To communicate between this node file and the phoenix extension use: NodeConnector-API -
96
* See. https://docs.phcode.dev/api/API-Reference/NodeConnector for detailed docs.
107
**/
11-
console.log("hello world node extension");
8+
console.log("Phoenix AI Control extension initialized");
9+
10+
const fs = require('fs');
11+
const os = require('os');
12+
const path = require('path');
1213

1314
const extnNodeConnector = global.createNodeConnector(
1415
"github-phcode-dev-phoenix-code-ai-control",
1516
exports
1617
);
1718

18-
async function echoTest(name) {
19-
return "hello from node " + name;
19+
/**
20+
* Get the platform-specific config file path
21+
* @returns {string} The path to the config file
22+
*/
23+
function getConfigFilePath() {
24+
const platform = os.platform();
25+
26+
if (platform === 'win32') {
27+
return 'C:\\Program Files\\Phoenix AI Control\\config.json';
28+
} else if (platform === 'darwin') {
29+
return '/Library/Application Support/Phoenix AI Control/config.json';
30+
} else if (platform === 'linux') {
31+
return '/etc/phoenix-ai-control/config.json';
32+
}
33+
34+
throw new Error(`Unsupported platform: ${platform}`);
35+
}
36+
37+
/**
38+
* Check if the current user is in the allowed users list
39+
* @param {Array<string>} allowedUsers - List of allowed usernames
40+
* @returns {boolean} True if current user is allowed
41+
*/
42+
function isCurrentUserAllowed(allowedUsers) {
43+
if (!allowedUsers || !Array.isArray(allowedUsers) || allowedUsers.length === 0) {
44+
return false;
45+
}
46+
47+
const currentUser = os.userInfo().username;
48+
return allowedUsers.includes(currentUser);
2049
}
2150

22-
async function convertToGreyScale(imageName, imageArrayBuffer) {
51+
/**
52+
* Get AI control configuration
53+
* @returns {Object} The configuration status and details
54+
*/
55+
async function getAIControlStatus() {
2356
try {
24-
// Convert the image buffer to grayscale using sharp
25-
const outputBuffer = await sharp(imageArrayBuffer)
26-
.grayscale() // Convert to grayscale
27-
.toBuffer(); // Convert to buffer
57+
const configFilePath = getConfigFilePath();
58+
59+
// Check if config file exists
60+
if (!fs.existsSync(configFilePath)) {
61+
return {
62+
exists: false,
63+
isConfigured: false,
64+
isEnabled: true,
65+
message: "AI is currently enabled for all users. No control configuration found.",
66+
configPath: configFilePath
67+
};
68+
}
69+
70+
// Read and parse config file
71+
const configContent = fs.readFileSync(configFilePath, 'utf8');
72+
const config = JSON.parse(configContent);
2873

29-
// Return the output buffer
74+
const currentUser = os.userInfo().username;
75+
const platform = os.platform();
76+
let platformName = '';
77+
78+
if (platform === 'win32') {
79+
platformName = 'Windows';
80+
} else if (platform === 'darwin') {
81+
platformName = 'macOS';
82+
} else if (platform === 'linux') {
83+
platformName = 'Linux';
84+
}
85+
86+
// Check if AI is disabled globally
87+
if (config.disableAI === true) {
88+
// Check if current user is in allowed users list
89+
if (config.allowedUsers && isCurrentUserAllowed(config.allowedUsers)) {
90+
return {
91+
exists: true,
92+
isConfigured: true,
93+
isEnabled: true,
94+
message: `AI is enabled for your user (${currentUser}) but disabled for others.`,
95+
managedBy: config.managedByEmail || 'Not specified',
96+
allowedUsers: config.allowedUsers || [],
97+
currentUser: currentUser,
98+
platform: platformName,
99+
configPath: configFilePath
100+
};
101+
} else {
102+
return {
103+
exists: true,
104+
isConfigured: true,
105+
isEnabled: false,
106+
message: `AI is disabled by system configuration.`,
107+
managedBy: config.managedByEmail || 'Not specified',
108+
allowedUsers: config.allowedUsers || [],
109+
currentUser: currentUser,
110+
platform: platformName,
111+
configPath: configFilePath
112+
};
113+
}
114+
} else {
115+
// AI is enabled globally
116+
return {
117+
exists: true,
118+
isConfigured: true,
119+
isEnabled: true,
120+
message: `AI is enabled for all users.`,
121+
managedBy: config.managedByEmail || 'Not specified',
122+
allowedUsers: config.allowedUsers || [],
123+
currentUser: currentUser,
124+
platform: platformName,
125+
configPath: configFilePath
126+
};
127+
}
128+
} catch (error) {
129+
console.error('Error checking AI control:', error);
30130
return {
31-
success: true,
32-
buffer: outputBuffer // a single binary array buffer can be transmitted, it should use key buffer
131+
exists: false,
132+
isConfigured: false,
133+
isEnabled: true,
134+
error: error.message,
135+
message: `Error checking AI configuration: ${error.message}`
33136
};
34-
} catch (error) {
35-
throw new Error("Error converting image to black and white:", error);
36137
}
37138
}
38139

39-
exports.echoTest = echoTest;
40-
exports.convertToGreyScale = convertToGreyScale;
140+
exports.getAIControlStatus = getAIControlStatus;

node/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"name": "node-extension-component",
2+
"name": "phoenix-ai-control-node",
33
"version": "1.0.0",
4-
"description": "",
4+
"description": "Node component for Phoenix AI Control extension",
55
"main": "index.js",
66
"scripts": {
77
"test": "echo \"Error: no test specified\" && exit 1"
88
},
9-
"author": "",
10-
"license": "ISC",
9+
"author": "Phoenix Code",
10+
"license": "MIT",
1111
"dependencies": {
1212
}
1313
}

0 commit comments

Comments
 (0)