Skip to content

Commit e94c7b5

Browse files
committed
chore: wire in entitlements
1 parent 0a564bd commit e94c7b5

3 files changed

Lines changed: 168 additions & 7 deletions

File tree

src/core-ai/AIChatPanel.js

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ define(function (require, exports, module) {
4141
StringUtils = require("utils/StringUtils"),
4242
marked = require("thirdparty/marked.min");
4343

44+
// Capture at module load time — window.KernalModeTrust is deleted before extensions load
45+
const _KernalModeTrust = window.KernalModeTrust;
46+
4447
let _nodeConnector = null;
4548
let _isStreaming = false;
4649
let _queuedMessage = null; // text queued by user while AI is streaming
@@ -86,6 +89,7 @@ define(function (require, exports, module) {
8689

8790
// DOM references
8891
let $panel, $messages, $status, $statusText, $textarea, $sendBtn, $stopBtn, $imagePreview;
92+
let $aiTabContainer = null;
8993

9094
// Live DOM query for $messages — the cached $messages reference can become stale
9195
// after SidebarTabs reparents the panel. Use this for any deferred operations
@@ -148,6 +152,7 @@ define(function (require, exports, module) {
148152
'<div class="ai-unavailable-message">' +
149153
Strings.AI_CHAT_DESKTOP_ONLY +
150154
'</div>' +
155+
'<button class="ai-upsell-btn ai-download-btn">' + Strings.AI_CHAT_DOWNLOAD_BTN + '</button>' +
151156
'</div>' +
152157
'</div>';
153158

@@ -185,16 +190,138 @@ define(function (require, exports, module) {
185190
}
186191
});
187192

188-
// Check availability and render appropriate UI
189-
_checkAvailability();
193+
// Create container once, add to AI tab
194+
$aiTabContainer = $('<div class="ai-tab-container"></div>');
195+
SidebarTabs.addToTab("ai", $aiTabContainer);
196+
197+
// Listen for entitlement changes to refresh UI on login/logout
198+
const EntitlementsManager = _KernalModeTrust && _KernalModeTrust.EntitlementsManager;
199+
if (EntitlementsManager) {
200+
EntitlementsManager.on(EntitlementsManager.EVENT_ENTITLEMENTS_CHANGED, _checkEntitlementAndInit);
201+
}
202+
203+
// Check entitlements and render appropriate UI
204+
_checkEntitlementAndInit();
190205
}
191206

192207
/**
193208
* Show placeholder UI for non-native (browser) builds.
194209
*/
195210
function initPlaceholder() {
211+
$aiTabContainer = $('<div class="ai-tab-container"></div>');
212+
SidebarTabs.addToTab("ai", $aiTabContainer);
196213
const $placeholder = $(PLACEHOLDER_HTML);
197-
SidebarTabs.addToTab("ai", $placeholder);
214+
$placeholder.find(".ai-download-btn").on("click", function () {
215+
window.open("https://phcode.io", "_blank");
216+
});
217+
$aiTabContainer.empty().append($placeholder);
218+
}
219+
220+
/**
221+
* Remove any existing panel content from the AI tab container.
222+
*/
223+
function _removeCurrentPanel() {
224+
if ($aiTabContainer) {
225+
$aiTabContainer.empty();
226+
}
227+
// Clear cached DOM references so stale jQuery objects aren't reused
228+
$panel = null;
229+
$messages = null;
230+
$status = null;
231+
$statusText = null;
232+
$textarea = null;
233+
$sendBtn = null;
234+
$stopBtn = null;
235+
$imagePreview = null;
236+
}
237+
238+
/**
239+
* Gate AI UI behind entitlement checks. Shows login screen if not logged in,
240+
* upsell screen if no AI plan, or proceeds to CLI availability check if entitled.
241+
*/
242+
function _checkEntitlementAndInit() {
243+
_removeCurrentPanel();
244+
const EntitlementsManager = _KernalModeTrust && _KernalModeTrust.EntitlementsManager;
245+
if (!EntitlementsManager) {
246+
// No entitlement system (test env or dev) — skip straight to CLI check
247+
_checkAvailability();
248+
return;
249+
}
250+
EntitlementsManager.getAIEntitlement().then(function (entitlement) {
251+
if (entitlement.aiDisabledByAdmin) {
252+
_renderAdminDisabledUI();
253+
} else if (entitlement.activated) {
254+
_checkAvailability();
255+
} else if (entitlement.needsLogin) {
256+
_renderLoginUI();
257+
} else {
258+
_renderUpsellUI(entitlement);
259+
}
260+
}).catch(function () {
261+
_checkAvailability(); // fallback on error
262+
});
263+
}
264+
265+
/**
266+
* Render the login prompt UI (user not signed in).
267+
*/
268+
function _renderLoginUI() {
269+
const html =
270+
'<div class="ai-chat-panel">' +
271+
'<div class="ai-unavailable">' +
272+
'<div class="ai-unavailable-icon"><i class="fa-solid fa-wand-magic-sparkles"></i></div>' +
273+
'<div class="ai-unavailable-title">' + Strings.AI_CHAT_LOGIN_TITLE + '</div>' +
274+
'<div class="ai-unavailable-message">' +
275+
Strings.AI_CHAT_LOGIN_MESSAGE +
276+
'</div>' +
277+
'<button class="ai-retry-btn ai-login-btn">' + Strings.AI_CHAT_LOGIN_BTN + '</button>' +
278+
'</div>' +
279+
'</div>';
280+
const $login = $(html);
281+
$login.find(".ai-login-btn").on("click", function () {
282+
_KernalModeTrust.EntitlementsManager.loginToAccount();
283+
});
284+
$aiTabContainer.empty().append($login);
285+
}
286+
287+
/**
288+
* Render the upsell UI (user logged in but no AI plan).
289+
*/
290+
function _renderUpsellUI(entitlement) {
291+
const html =
292+
'<div class="ai-chat-panel">' +
293+
'<div class="ai-unavailable">' +
294+
'<div class="ai-unavailable-icon"><i class="fa-solid fa-wand-magic-sparkles"></i></div>' +
295+
'<div class="ai-unavailable-title">' + Strings.AI_CHAT_UPSELL_TITLE + '</div>' +
296+
'<div class="ai-unavailable-message">' +
297+
Strings.AI_CHAT_UPSELL_MESSAGE +
298+
'</div>' +
299+
'<button class="ai-upsell-btn">' + Strings.AI_CHAT_UPSELL_BTN + '</button>' +
300+
'</div>' +
301+
'</div>';
302+
const $upsell = $(html);
303+
$upsell.find(".ai-upsell-btn").on("click", function () {
304+
const url = (entitlement && entitlement.buyURL) || brackets.config.purchase_url;
305+
Phoenix.app.openURLInDefaultBrowser(url);
306+
});
307+
$aiTabContainer.empty().append($upsell);
308+
}
309+
310+
/**
311+
* Render the admin-disabled UI (AI turned off by system administrator).
312+
*/
313+
function _renderAdminDisabledUI() {
314+
const html =
315+
'<div class="ai-chat-panel">' +
316+
'<div class="ai-unavailable">' +
317+
'<div class="ai-unavailable-icon"><i class="fa-solid fa-wand-magic-sparkles"></i></div>' +
318+
'<div class="ai-unavailable-title">' + Strings.AI_CHAT_ADMIN_DISABLED_TITLE + '</div>' +
319+
'<div class="ai-unavailable-message">' +
320+
Strings.AI_CHAT_ADMIN_DISABLED_MESSAGE +
321+
'</div>' +
322+
'</div>' +
323+
'</div>';
324+
$aiTabContainer.empty().append($(html));
198325
}
199326

200327
/**
@@ -419,7 +546,7 @@ define(function (require, exports, module) {
419546
_newSession();
420547
});
421548

422-
SidebarTabs.addToTab("ai", $panel);
549+
$aiTabContainer.empty().append($panel);
423550
}
424551

425552
/**
@@ -428,10 +555,9 @@ define(function (require, exports, module) {
428555
function _renderUnavailableUI(error) {
429556
const $unavailable = $(UNAVAILABLE_HTML);
430557
$unavailable.find(".ai-retry-btn").on("click", function () {
431-
$unavailable.remove();
432558
_checkAvailability();
433559
});
434-
SidebarTabs.addToTab("ai", $unavailable);
560+
$aiTabContainer.empty().append($unavailable);
435561
}
436562

437563
// --- Context bar chip management ---

src/nls/root/strings.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1823,7 +1823,16 @@ define({
18231823
"AI_CHAT_CLI_NOT_FOUND": "Claude CLI Not Found",
18241824
"AI_CHAT_CLI_INSTALL_MSG": "Install the Claude CLI to use AI features:<br><code>npm install -g @anthropic-ai/claude-code</code><br><br>Then run <code>claude login</code> to authenticate.",
18251825
"AI_CHAT_RETRY": "Retry",
1826-
"AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app.",
1826+
"AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.",
1827+
"AI_CHAT_DOWNLOAD_BTN": "Download Desktop App",
1828+
"AI_CHAT_LOGIN_TITLE": "Sign In to Use AI",
1829+
"AI_CHAT_LOGIN_MESSAGE": "Sign in to your {APP_NAME} account to access AI features.",
1830+
"AI_CHAT_LOGIN_BTN": "Sign In",
1831+
"AI_CHAT_UPSELL_TITLE": "Phoenix Pro + AI",
1832+
"AI_CHAT_UPSELL_MESSAGE": "AI features are available with Phoenix Pro.",
1833+
"AI_CHAT_UPSELL_BTN": "Get Phoenix Pro",
1834+
"AI_CHAT_ADMIN_DISABLED_TITLE": "AI Disabled",
1835+
"AI_CHAT_ADMIN_DISABLED_MESSAGE": "AI features have been disabled by your system administrator.",
18271836
"AI_CHAT_TOOL_SEARCH_FILES": "Search files",
18281837
"AI_CHAT_TOOL_SEARCH_CODE": "Search code",
18291838
"AI_CHAT_TOOL_READ": "Read",

src/styles/Extn-AIChatPanel.less

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,20 @@
2020

2121
/* AI Chat Panel — sidebar chat UI for Claude Code integration */
2222

23+
.ai-tab-container {
24+
display: flex;
25+
flex-direction: column;
26+
-webkit-box-flex: 1;
27+
flex: 1;
28+
min-height: 0;
29+
overflow: hidden;
30+
}
31+
2332
.ai-chat-panel {
2433
display: flex;
2534
flex-direction: column;
2635
-webkit-box-flex: 1;
36+
flex: 1;
2737
min-height: 0;
2838
overflow: hidden;
2939
background-color: @bc-sidebar-bg;
@@ -1409,4 +1419,20 @@
14091419
color: @project-panel-text-1;
14101420
}
14111421
}
1422+
1423+
.ai-upsell-btn {
1424+
background: #FF9900;
1425+
border: none;
1426+
color: #000;
1427+
font-weight: 600;
1428+
font-size: @sidebar-small-font-size;
1429+
padding: 3px 12px;
1430+
border-radius: 3px;
1431+
cursor: pointer;
1432+
transition: background-color 0.15s ease;
1433+
1434+
&:hover {
1435+
background: #FFa820;
1436+
}
1437+
}
14121438
}

0 commit comments

Comments
 (0)