diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 892d199c9b..2740ae1359 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@phcode/node-core", - "version": "5.5.9-0", + "version": "5.5.10-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@phcode/node-core", - "version": "5.5.9-0", + "version": "5.5.10-0", "hasInstallScript": true, "license": "GNU-AGPL3.0", "dependencies": { @@ -32,7 +32,7 @@ } }, "..": { - "version": "5.5.9-0", + "version": "5.5.10-0", "hasInstallScript": true, "dependencies": { "@bugsnag/js": "^7.18.0", diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 17eb0e243e..7d931c19c5 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2753,6 +2753,7 @@ define({ "AI_CHAT_SCROLL_TO_BOTTOM": "Scroll to bottom", "AI_CHAT_PLACEHOLDER": "Ask Claude...", "AI_CHAT_SETUP_PLACEHOLDER": "Set up Claude Code to start chatting", + "AI_CHAT_ACCESS_CHECK_FAILED": "Could not check AI access. Please try again.", "AI_CHAT_NOT_READY_YET": "AI is getting ready. Please wait a moment.", "AI_CHAT_PLACEHOLDER_MODEL": "Ask {0}...", "AI_CHAT_SEND_TITLE": "Send message", @@ -2893,10 +2894,17 @@ define({ "AI_CHAT_USAGE_API_TIME": "Time in API", "AI_CHAT_USAGE_COST": "Cost at list price", "AI_CHAT_USAGE_NOTE": "You won’t be charged this amount by Phoenix Pro.", - "AI_CHAT_USAGE_SPEND_INCLUDED": "With a Claude or ChatGPT subscription, these AI costs are covered by your plan. If you use an API key, billing is handled by your provider.", + "AI_CHAT_USAGE_SPEND_INCLUDED": "If you’re using a Claude or ChatGPT subscription, you won’t be charged this amount separately by your AI provider.", "AI_CHAT_USAGE_SPEND_NO_PRO_CHARGE": "You won’t be charged this amount by Phoenix Pro.", "AI_CHAT_USAGE_OPTION_TURNS": "Show usage after every turn", "AI_CHAT_USAGE_OPTION_DOLLARS": "Show $ cost", + "AI_CHAT_USAGE_SHOW_COST": "Show AI cost estimates", + "AI_CHAT_USAGE_HIDE_COST": "Hide AI cost estimates", + "AI_CHAT_USAGE_SHOW_COST_TITLE": "Show AI cost estimates?", + "AI_CHAT_USAGE_SHOW_COST_COVERED": "If you use a Claude or ChatGPT subscription, these costs are already covered by your subscription.", + "AI_CHAT_USAGE_SHOW_COST_VALUE": "Turning this on won’t add anything to your bill. It only shows the value of your usage at API prices.", + "AI_CHAT_USAGE_SHOW_COST_NOTE": "API keys and paid extra usage can still incur charges.", + "AI_CHAT_USAGE_SHOW_COST_CONFIRM": "Show $ value", "AI_CHAT_USAGE_CARD_TITLE": "Usage", "AI_CHAT_USAGE_RANGE_30": "{0} \u00b7 last 30 days", "AI_CHAT_USAGE_DETAILS": "Details", @@ -2904,7 +2912,7 @@ define({ "AI_CHAT_USAGE_MONTHS_SHOWN": "{0} of {1} months", "AI_CHAT_USAGE_LESS": "Less", "AI_CHAT_USAGE_MORE": "More", - "AI_CHAT_USAGE_SPEND": "spend", + "AI_CHAT_USAGE_SPEND": "value", "AI_CHAT_USAGE_TOKENS_LABEL": "tokens", "AI_CHAT_USAGE_TURNS_LABEL": "turns", "AI_CHAT_USAGE_ACTIVE_DAYS": "active days", @@ -2912,7 +2920,7 @@ define({ "AI_CHAT_USAGE_TOP_MODELS": "Top models", "AI_CHAT_USAGE_SEE_MORE": "See more", "AI_CHAT_USAGE_SEE_LESS": "See less", - "AI_CHAT_USAGE_BY_SPEND": "by spend", + "AI_CHAT_USAGE_BY_SPEND": "by value", "AI_CHAT_USAGE_BY_TOKENS": "by tokens", "AI_CHAT_USAGE_ACTIVITY": "Activity", "AI_CHAT_USAGE_LAST_30_DAYS": "Last 30 days", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 9cd4b37f41..ced1d358b7 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -6655,18 +6655,20 @@ .ai-usage-card-head { display: flex; align-items: center; - gap: 8px; + gap: 0; margin-bottom: 10px; min-width: 0; } .ai-usage-card-title { + margin-right: 8px; font-size: @ai-text-body; font-weight: 600; } /* The one header item that may give way in a narrow panel. */ .ai-usage-card-range { + margin-right: 8px; font-size: @ai-text-meta; color: @project-panel-text-2; padding: 2px 6px; @@ -6685,15 +6687,14 @@ } } - /* Other Phoenix windows write the same ledger; this reads it again. */ + .ai-usage-card-cost, .ai-usage-card-reload { - margin-left: auto; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; - width: 20px; - height: 20px; + width: 22px; + height: 22px; border-radius: 4px; font-size: 11px; color: fade(@project-panel-text-2, 80%); @@ -6704,11 +6705,32 @@ background: rgba(255, 255, 255, 0.06); } - &.ai-usage-reloading i { - animation: ai-usage-spin 0.7s linear infinite; + &:focus-visible { + outline: 2px solid @project-panel-text-2; + outline-offset: 2px; } } + .ai-usage-card-cost { + margin-left: auto; + padding: 0; + border: 0; + background: transparent; + box-shadow: none; + font-family: inherit; + font-size: 15px; + font-weight: 600; + + &:hover { + background: transparent; + } + } + + /* Other Phoenix windows write the same ledger; this reads it again. */ + .ai-usage-card-reload.ai-usage-reloading i { + animation: ai-usage-spin 0.7s linear infinite; + } + .ai-usage-card-toggle { margin-left: 6px; flex-shrink: 0; @@ -7047,6 +7069,19 @@ } } +.modal.ai-usage-cost-dialog { + width: 480px; + max-width: calc(100vw - 48px); + + .modal-body { + font-size: 14px; + line-height: 1.6; + + p { margin: 0 0 12px; } + .ai-usage-cost-note { margin-bottom: 0; font-size: 12px; opacity: 0.8; } + } +} + .ai-usage-delete-dialog { width: 460px; max-width: calc(100vw - 32px); diff --git a/src/utils/Metrics.js b/src/utils/Metrics.js index 4826391cb5..e05eb04287 100644 --- a/src/utils/Metrics.js +++ b/src/utils/Metrics.js @@ -95,7 +95,7 @@ define(function (require, exports, module) { * `PLATFORM`, `PROJECT`, `THEMES`, `EXTENSIONS`, `EXTENSIONS`, `UI`, `APP_MENU`, `UI_MENU`, `UI_DIALOG`, * `UI_BOTTOM_PANEL`, `UI_SIDE_PANEL`, `LIVE_PREVIEW`, `CODE_HINTS`, `EDITOR`, `SEARCH`, `SHARING`, * `PERFORMANCE`, `NEW_PROJECT` - * `ERROR`, `USER`, `NODEJS`, `LINT`, `GIT`, `AUTH`, `PRO`, `GUIDE` + * `ERROR`, `USER`, `NODEJS`, `LINT`, `GIT`, `AUTH`, `PRO`, `GUIDE`, `EXPERIMENT` * * @typedef EVENT_TYPE * @type {Object} @@ -132,6 +132,7 @@ define(function (require, exports, module) { AUTH: "auth", PRO: "pro", AI: "ai", + EXPERIMENT: "exp", TERMINAL: "term", MD: "md", GUIDE: "guide", @@ -362,6 +363,20 @@ define(function (require, exports, module) { // some events generate too many ga events that ga can't handle. ignore them. const ignoredGAEvents = ['instantSearch']; + + /** + * Keep experiment names readable internally while meeting GA's event-name character rules. + * @param {string} category Event type, possibly carrying a subscriber/power-user prefix. + * @param {string} action Event category. + * @param {string} label Event label. + * @return {string} GA event name; existing non-experiment names are unchanged. + */ + function _googleEventName(category, action, label) { + const name = `${category}.${action}.${label}`; + return category === EVENT_TYPE.EXPERIMENT || /^[SLPR]-exp$/.test(category) ? + name.replace(/[^a-zA-Z0-9_]/g, "_") : name; + } + function _sendToGoogleAnalytics(category, action, label, count) { // https://developers.google.com/analytics/devguides/collection/analyticsjs/events // TODO, see if we are sending too many events to ga, unlike core analytics, GA has a limit of @@ -377,7 +392,7 @@ define(function (require, exports, module) { if(!count){ count = 1; } - let eventAct = `${category}.${action}.${label}`; + const eventAct = _googleEventName(category, action, label); if(ignoredGAEvents.includes(action)){ return; } diff --git a/tracking-repos.json b/tracking-repos.json index c4b00baa98..2e491514c0 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "4dc933fb3306dd5c8bd098969dce17544c58c0a6" + "commitID": "69e7fa766936e0f35dda949f7a603a1f9afadd7a" } }