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
96 changes: 96 additions & 0 deletions src/extensionsIntegrated/Phoenix/phoenix-tour.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
StringUtils = require("utils/StringUtils"),
Metrics = require("utils/Metrics"),
SidebarView = require("project/SidebarView"),
SidebarTabs = require("view/SidebarTabs"),
ProjectManager = require("project/ProjectManager"),
EditorManager = require("editor/EditorManager"),
CommandManager = require("command/CommandManager"),
Expand Down Expand Up @@ -76,6 +77,21 @@
let _rafId = null;
let _timers = [];

// Per-step: tracks a click on the highlighted target while the
// overlay is showing, fires a metric, then detaches. Cleared by
// _detachStepClickMetric on step transitions and teardown.
let _activeStepClickHandler = null;
let _activeStepClickTarget = null;

// Step 2: when the step starts we briefly switch the sidebar to the
// AI tab as an automatic peek (2s) and revert to whatever the user
// was on. _peekPrevTab is non-null only while a peek is in flight so
// teardown can revert cleanly if the tour ends mid-peek.
let _step2PeekTimer = null;
let _step2PeekPrevTab = null;
const STEP2_PEEK_HOLD_MS = 2000;
const SIDEBAR_AI_TAB_ID = "ai";

function _markComplete() {
_state.version = CURRENT_TOUR_VERSION;
_saveState(_state);
Expand All @@ -92,8 +108,74 @@
}
}

/**
* Attach a one-shot click listener to `$target` that fires a "stepN_clicked"
* metric. Captures real user clicks during the overlay session — not the
* synthetic class toggles the demos do. Replaces any previously attached
* step handler so we never double-count across step transitions.
*/
function _attachStepClickMetric(stepNum, $target) {
_detachStepClickMetric();
if (!$target || !$target.length || !$target[0]) {

Check warning on line 119 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3jVfPEIblWdKuOD992&open=AZ3jVfPEIblWdKuOD992&pullRequest=2878
return;
}
const targetEl = $target[0];
const handler = function () {
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step" + stepNum + "_clicked");
_detachStepClickMetric();
};
targetEl.addEventListener("click", handler, true);
_activeStepClickTarget = targetEl;
_activeStepClickHandler = handler;
}

function _detachStepClickMetric() {
if (_activeStepClickTarget && _activeStepClickHandler) {
_activeStepClickTarget.removeEventListener("click", _activeStepClickHandler, true);
}
_activeStepClickTarget = null;
_activeStepClickHandler = null;
}

/**
* Step 2 only: automatically switch the sidebar to the AI tab for a
* couple of seconds so the user sees what's behind the tab, then
* revert. No-op if the user is already on the AI tab.
*/
function _runStep2AIPeek() {
_cancelStep2AIPeek();
const current = SidebarTabs.getActiveTab && SidebarTabs.getActiveTab();

Check warning on line 147 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3jVfPEIblWdKuOD993&open=AZ3jVfPEIblWdKuOD993&pullRequest=2878
if (current === SIDEBAR_AI_TAB_ID) {
return;
}
_step2PeekPrevTab = current;
SidebarTabs.setActiveTab(SIDEBAR_AI_TAB_ID);
_step2PeekTimer = setTimeout(function () {
if (_step2PeekPrevTab) {
SidebarTabs.setActiveTab(_step2PeekPrevTab);
}
_step2PeekPrevTab = null;
_step2PeekTimer = null;
}, STEP2_PEEK_HOLD_MS);
}

function _cancelStep2AIPeek() {
if (_step2PeekTimer) {
clearTimeout(_step2PeekTimer);
_step2PeekTimer = null;
}
// If we tore down or transitioned mid-peek, restore the previous
// tab so the sidebar doesn't get stranded on AI.
if (_step2PeekPrevTab) {
SidebarTabs.setActiveTab(_step2PeekPrevTab);
_step2PeekPrevTab = null;
}
}

function _teardown() {
_clearTimers();
_detachStepClickMetric();
_cancelStep2AIPeek();
if ($overlay) {
$overlay.remove();
$overlay = null;
Expand Down Expand Up @@ -213,6 +295,7 @@
_trackTarget($btn, "right");
_setStep(1);
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step1");
_attachStepClickMetric(1, $btn);
// Single, stable message for the entire step. The visible toggle of
// design mode does the explaining; rotating text under a 2-second
// demo is too quick to read.
Expand Down Expand Up @@ -248,6 +331,9 @@
}

function _runStep2() {
// Each step transition cancels the previous step's instrumentation.
_detachStepClickMetric();
_cancelStep2AIPeek();
_ensureSidebarVisible();
const $tab = $('.sidebar-tab[data-tab-id="ai"]');
if (!$tab.length) {
Expand All @@ -269,11 +355,17 @@
}
}
]);
_attachStepClickMetric(2, $tab);
// Auto-peek the AI panel for a couple of seconds so the user gets
// a glance at its contents, then revert.
_runStep2AIPeek();
// Intentionally do NOT advance on a real click of the target — the
// user needs time to read the prompt; only the Next button advances.
}

function _runStep3() {
_detachStepClickMetric();
_cancelStep2AIPeek();
_ensureSidebarVisible();
const $newBtn = $("#newProject");
if (!$newBtn.length) {
Expand All @@ -286,6 +378,7 @@
_trackTarget($newBtn, "right");
_setStep(3);
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step3");
_attachStepClickMetric(3, $newBtn);
_setText(Strings.PHOENIX_TOUR_NEW_PROJECT);
_setActions([
{
Expand Down Expand Up @@ -354,6 +447,8 @@
}

async function _runStep4() {
_detachStepClickMetric();
_cancelStep2AIPeek();
_ensureSidebarVisible();
try {
await _ensureLivePreviewReady();
Expand All @@ -375,6 +470,7 @@
_trackTarget($btn, "left");
_setStep(4);
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step4");
_attachStepClickMetric(4, $btn);
_setText(Strings.PHOENIX_TOUR_EDIT_MODE);
_setActions([
{
Expand Down
1 change: 1 addition & 0 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,7 @@ define({
"AI_CHAT_TOOL_RESIZE_PREVIEW": "Resize preview",
"AI_LIVE_PREVIEW_BANNER_TEXT": "AI is inspecting the live preview",
"AI_LIVE_PREVIEW_BANNER_RESIZE": "AI resized preview to {0}",
"AI_LIVE_PREVIEW_BANNER_DISMISS_TOOLTIP": "Click to dismiss",
"AI_CHAT_TOOL_CONTROL_EDITOR": "Editor",
"AI_CHAT_TOOL_TASKS": "Tasks",
"AI_CHAT_TOOL_TASKS_SUMMARY": "{0} of {1} tasks done",
Expand Down
46 changes: 46 additions & 0 deletions src/styles/Extn-AIChatPanel.less
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,48 @@
}
}

.ai-onboarding-prompt-images {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 8px 12px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
background: @bc-ai-input-bg;

.ai-image-thumb {
position: relative;

img {
display: block;
max-width: 64px;
max-height: 48px;
object-fit: cover;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.12);
cursor: pointer;
}

.ai-image-remove {
position: absolute;
top: -6px;
right: -6px;
width: 18px;
height: 18px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
font-size: 11px;
line-height: 1;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
}

.ai-onboarding-prompt-actions {
display: flex;
justify-content: flex-end;
Expand Down Expand Up @@ -602,6 +644,10 @@
}
}

/* Fullscreen video overlay opened from the AI panel intro thumbnail.
Position fixed so it covers the entire app viewport (not just the
AI panel column). The inner player is sized to use most of the
screen while preserving 16:9 letterboxing inside the dark backdrop. */
/* ── Assistant message — markdown content ───────────────────────────── */
.ai-msg-assistant {
.ai-msg-content {
Expand Down
87 changes: 87 additions & 0 deletions src/styles/VideoPlayer.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* GNU AGPL-3.0 License
*
* Copyright (c) 2021 - present core.ai . All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
*
*/

/* Styles for view/VideoPlayer.js — both the inline createPlayer wrapper
* and the renderFullScreenPlayer overlay (genie-style expand-from-source
* animation). The fullscreen overlay's player-expand animation is
* JS-driven (FLIP transform) so it can target the actual source rect at
* click time; only the backdrop fade is keyframe-driven. */

@keyframes phx-video-fs-backdrop-in {
from { background-color: rgba(0, 0, 0, 0); }
to { background-color: rgba(0, 0, 0, 0.88); }
}

.phx-video-fullscreen-overlay {
position: fixed;
inset: 0;
z-index: 1000;
background: rgba(0, 0, 0, 0.88);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 24px;
box-sizing: border-box;
animation: phx-video-fs-backdrop-in 220ms ease-out;

.phx-video-fullscreen-player {
cursor: default;
width: min(90vw, calc((90vh - 48px) * 16 / 9));
max-width: 90vw;
max-height: 90vh;
aspect-ratio: 16 / 9;
background: #000;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 6px 32px rgba(0, 0, 0, 0.6);
transform-origin: center;
will-change: transform, opacity;

video {
display: block;
width: 100%;
height: 100%;
background: #000;
}
}

.phx-video-fullscreen-close {
position: absolute;
top: 14px;
right: 14px;
width: 36px;
height: 36px;
border: 0;
border-radius: 50%;
background: rgba(0, 0, 0, 0.55);
color: #fff;
font-size: 16px;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.15s ease, transform 0.15s ease;

&:hover {
background: rgba(0, 0, 0, 0.8);
transform: scale(1.05);
}
}
}
1 change: 1 addition & 0 deletions src/styles/brackets.less
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
@import "Extn-Terminal.less";
@import "UserProfile.less";
@import "phoenix-pro.less";
@import "VideoPlayer.less";

/* Overall layout */

Expand Down
Loading
Loading