@xenide-io/the-old-ui-theme 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/suite.d.mts +23 -2
- package/dist/suite.d.ts +23 -2
- package/dist/suite.js +157 -18
- package/dist/suite.mjs +158 -24
- package/package.json +1 -1
- package/src/suite/components/ai-panel.test.ts +13 -0
- package/src/suite/components/ai-panel.tsx +166 -12
- package/src/suite/components/suite-sidebar.tsx +21 -10
- package/src/suite/index.ts +7 -0
package/dist/suite.d.mts
CHANGED
|
@@ -477,9 +477,24 @@ declare function SuiteThemeProvider({ config, children, }: {
|
|
|
477
477
|
declare function useSuiteTheme(): SuiteThemeContextValue;
|
|
478
478
|
|
|
479
479
|
declare const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
480
|
+
declare const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
|
|
481
|
+
declare const SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
|
|
482
|
+
declare const TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
|
|
480
483
|
type SuiteAskAiOpenDetail = {
|
|
481
484
|
prompt?: string;
|
|
482
485
|
};
|
|
486
|
+
declare function persistSuiteAskAiOpen(open: boolean): void;
|
|
487
|
+
declare function readSuiteAskAiOpen(): boolean;
|
|
488
|
+
type SuiteAiActionStatus = "proposed" | "applied" | "cancelled" | "failed";
|
|
489
|
+
interface SuiteAiAction {
|
|
490
|
+
id: string;
|
|
491
|
+
kind: string;
|
|
492
|
+
status: SuiteAiActionStatus;
|
|
493
|
+
summary: string;
|
|
494
|
+
href?: string;
|
|
495
|
+
error?: string;
|
|
496
|
+
payload?: Record<string, unknown>;
|
|
497
|
+
}
|
|
483
498
|
/** Open the suite Ask AI panel from anywhere (Today card, command palette, …). */
|
|
484
499
|
declare function openSuiteAskAi(detail?: SuiteAskAiOpenDetail): void;
|
|
485
500
|
/** Consume a prompt queued before the panel mounted. */
|
|
@@ -492,12 +507,13 @@ interface SuiteAiChatMessage {
|
|
|
492
507
|
role: "user" | "assistant";
|
|
493
508
|
content: string;
|
|
494
509
|
created_at?: string;
|
|
510
|
+
actions?: SuiteAiAction[];
|
|
495
511
|
}
|
|
496
512
|
/**
|
|
497
513
|
* Suite-wide Ask AI chat — Notion-style continuous thread.
|
|
498
514
|
* Lives in the sidebar (or any parent that mounts it). Not Kraken Deep Research.
|
|
499
515
|
*/
|
|
500
|
-
declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, }: {
|
|
516
|
+
declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, }: {
|
|
501
517
|
presets?: SuiteAiPreset[];
|
|
502
518
|
emptyState?: ReactNode;
|
|
503
519
|
title?: string;
|
|
@@ -525,6 +541,11 @@ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessa
|
|
|
525
541
|
suiteAppBases?: Partial<Record<SuiteAppSlug, string>>;
|
|
526
542
|
/** Cross-app SSO. Same-app links just navigate to `path`. */
|
|
527
543
|
onSwitchApp?: (app: SuiteAppSlug, path: string) => void;
|
|
544
|
+
/** Client-side same-app route change — keeps Shelly mounted. */
|
|
545
|
+
onSameAppNavigate?: (path: string) => void;
|
|
546
|
+
resolveAction?: (id: string, decision: "confirm" | "cancel") => Promise<{
|
|
547
|
+
messages: SuiteAiChatMessage[];
|
|
548
|
+
}>;
|
|
528
549
|
}): react.JSX.Element | null;
|
|
529
550
|
|
|
530
551
|
interface SuiteWorkspaceEntry {
|
|
@@ -951,4 +972,4 @@ interface SuiteAppLayoutProps {
|
|
|
951
972
|
*/
|
|
952
973
|
declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, lockMainScroll, onStartResize, onResizeBy, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
|
|
953
974
|
|
|
954
|
-
export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
|
|
975
|
+
export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, persistSuiteAskAiOpen, readSuiteAskAiOpen, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
|
package/dist/suite.d.ts
CHANGED
|
@@ -477,9 +477,24 @@ declare function SuiteThemeProvider({ config, children, }: {
|
|
|
477
477
|
declare function useSuiteTheme(): SuiteThemeContextValue;
|
|
478
478
|
|
|
479
479
|
declare const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
480
|
+
declare const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
|
|
481
|
+
declare const SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
|
|
482
|
+
declare const TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
|
|
480
483
|
type SuiteAskAiOpenDetail = {
|
|
481
484
|
prompt?: string;
|
|
482
485
|
};
|
|
486
|
+
declare function persistSuiteAskAiOpen(open: boolean): void;
|
|
487
|
+
declare function readSuiteAskAiOpen(): boolean;
|
|
488
|
+
type SuiteAiActionStatus = "proposed" | "applied" | "cancelled" | "failed";
|
|
489
|
+
interface SuiteAiAction {
|
|
490
|
+
id: string;
|
|
491
|
+
kind: string;
|
|
492
|
+
status: SuiteAiActionStatus;
|
|
493
|
+
summary: string;
|
|
494
|
+
href?: string;
|
|
495
|
+
error?: string;
|
|
496
|
+
payload?: Record<string, unknown>;
|
|
497
|
+
}
|
|
483
498
|
/** Open the suite Ask AI panel from anywhere (Today card, command palette, …). */
|
|
484
499
|
declare function openSuiteAskAi(detail?: SuiteAskAiOpenDetail): void;
|
|
485
500
|
/** Consume a prompt queued before the panel mounted. */
|
|
@@ -492,12 +507,13 @@ interface SuiteAiChatMessage {
|
|
|
492
507
|
role: "user" | "assistant";
|
|
493
508
|
content: string;
|
|
494
509
|
created_at?: string;
|
|
510
|
+
actions?: SuiteAiAction[];
|
|
495
511
|
}
|
|
496
512
|
/**
|
|
497
513
|
* Suite-wide Ask AI chat — Notion-style continuous thread.
|
|
498
514
|
* Lives in the sidebar (or any parent that mounts it). Not Kraken Deep Research.
|
|
499
515
|
*/
|
|
500
|
-
declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, }: {
|
|
516
|
+
declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, }: {
|
|
501
517
|
presets?: SuiteAiPreset[];
|
|
502
518
|
emptyState?: ReactNode;
|
|
503
519
|
title?: string;
|
|
@@ -525,6 +541,11 @@ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessa
|
|
|
525
541
|
suiteAppBases?: Partial<Record<SuiteAppSlug, string>>;
|
|
526
542
|
/** Cross-app SSO. Same-app links just navigate to `path`. */
|
|
527
543
|
onSwitchApp?: (app: SuiteAppSlug, path: string) => void;
|
|
544
|
+
/** Client-side same-app route change — keeps Shelly mounted. */
|
|
545
|
+
onSameAppNavigate?: (path: string) => void;
|
|
546
|
+
resolveAction?: (id: string, decision: "confirm" | "cancel") => Promise<{
|
|
547
|
+
messages: SuiteAiChatMessage[];
|
|
548
|
+
}>;
|
|
528
549
|
}): react.JSX.Element | null;
|
|
529
550
|
|
|
530
551
|
interface SuiteWorkspaceEntry {
|
|
@@ -951,4 +972,4 @@ interface SuiteAppLayoutProps {
|
|
|
951
972
|
*/
|
|
952
973
|
declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, lockMainScroll, onStartResize, onResizeBy, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
|
|
953
974
|
|
|
954
|
-
export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
|
|
975
|
+
export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, getTodayGreeting, openSuiteAskAi, persistSuiteAskAiOpen, readSuiteAskAiOpen, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
|
package/dist/suite.js
CHANGED
|
@@ -243,8 +243,10 @@ __export(suite_exports, {
|
|
|
243
243
|
SIDEBAR_RAIL_WIDTH: () => SIDEBAR_RAIL_WIDTH,
|
|
244
244
|
SUITE_APPS: () => SUITE_APPS2,
|
|
245
245
|
SUITE_APP_MAP: () => SUITE_APP_MAP,
|
|
246
|
+
SUITE_ASK_AI_OPEN_KEY: () => SUITE_ASK_AI_OPEN_KEY,
|
|
246
247
|
SUITE_GLYPHS: () => SUITE_GLYPHS,
|
|
247
248
|
SUITE_ICON_NAMES: () => SUITE_ICON_NAMES,
|
|
249
|
+
SUITE_MUTATED_EVENT: () => SUITE_MUTATED_EVENT,
|
|
248
250
|
SUITE_OPEN_ASK_AI_EVENT: () => SUITE_OPEN_ASK_AI_EVENT,
|
|
249
251
|
SUITE_SPRINGS: () => SUITE_SPRINGS,
|
|
250
252
|
SourceAppGlyph: () => SourceAppGlyph,
|
|
@@ -274,6 +276,7 @@ __export(suite_exports, {
|
|
|
274
276
|
SuiteToolbar: () => SuiteToolbar,
|
|
275
277
|
SuiteUserMenu: () => SuiteUserMenu,
|
|
276
278
|
SuiteWorkspaceSwitcher: () => SuiteWorkspaceSwitcher,
|
|
279
|
+
TURTLETIME_TIMER_MUTATED_EVENT: () => TURTLETIME_TIMER_MUTATED_EVENT,
|
|
277
280
|
TodayAskAiCard: () => TodayAskAiCard,
|
|
278
281
|
TodayCalibrating: () => TodayCalibrating,
|
|
279
282
|
TodayEmptyAction: () => TodayEmptyAction,
|
|
@@ -295,6 +298,8 @@ __export(suite_exports, {
|
|
|
295
298
|
getTodayGreeting: () => getTodayGreeting,
|
|
296
299
|
m: () => import_react14.m,
|
|
297
300
|
openSuiteAskAi: () => openSuiteAskAi,
|
|
301
|
+
persistSuiteAskAiOpen: () => persistSuiteAskAiOpen,
|
|
302
|
+
readSuiteAskAiOpen: () => readSuiteAskAiOpen,
|
|
298
303
|
resolveSuiteNotificationHref: () => resolveSuiteNotificationHref,
|
|
299
304
|
sidebarColumnWidth: () => sidebarColumnWidth,
|
|
300
305
|
sourceToSuiteApp: () => sourceToSuiteApp,
|
|
@@ -2736,6 +2741,25 @@ function Tooltip({
|
|
|
2736
2741
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2737
2742
|
var SuiteAiBlockNoteMessage2 = (0, import_react11.lazy)(() => Promise.resolve().then(() => (init_ai_message_blocknote(), ai_message_blocknote_exports)));
|
|
2738
2743
|
var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
2744
|
+
var SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
|
|
2745
|
+
var SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
|
|
2746
|
+
var TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
|
|
2747
|
+
function persistSuiteAskAiOpen(open) {
|
|
2748
|
+
if (typeof window === "undefined") return;
|
|
2749
|
+
try {
|
|
2750
|
+
if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
|
|
2751
|
+
else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
|
|
2752
|
+
} catch (e) {
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
function readSuiteAskAiOpen() {
|
|
2756
|
+
if (typeof window === "undefined") return false;
|
|
2757
|
+
try {
|
|
2758
|
+
return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
|
|
2759
|
+
} catch (e) {
|
|
2760
|
+
return false;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2739
2763
|
var pendingAskAiPrompt = null;
|
|
2740
2764
|
function openSuiteAskAi(detail) {
|
|
2741
2765
|
var _a;
|
|
@@ -2752,6 +2776,18 @@ function consumePendingAskAiPrompt() {
|
|
|
2752
2776
|
pendingAskAiPrompt = null;
|
|
2753
2777
|
return next;
|
|
2754
2778
|
}
|
|
2779
|
+
function notifySuiteMutated(actions) {
|
|
2780
|
+
if (typeof window === "undefined" || !(actions == null ? void 0 : actions.length)) return;
|
|
2781
|
+
const kinds = actions.map((action) => action.kind);
|
|
2782
|
+
if (kinds.some(
|
|
2783
|
+
(kind) => kind.includes("timer") || kind.includes("time") || kind.includes("hours")
|
|
2784
|
+
)) {
|
|
2785
|
+
window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
|
|
2786
|
+
}
|
|
2787
|
+
window.dispatchEvent(
|
|
2788
|
+
new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } })
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2755
2791
|
function SuiteAiPanel({
|
|
2756
2792
|
presets = [],
|
|
2757
2793
|
emptyState = "Ask when you are ready \u2014 overdue work, a running timer, or anything else.",
|
|
@@ -2764,13 +2800,17 @@ function SuiteAiPanel({
|
|
|
2764
2800
|
open: openProp,
|
|
2765
2801
|
onOpenChange,
|
|
2766
2802
|
suiteAppBases,
|
|
2767
|
-
onSwitchApp
|
|
2803
|
+
onSwitchApp,
|
|
2804
|
+
onSameAppNavigate,
|
|
2805
|
+
resolveAction
|
|
2768
2806
|
}) {
|
|
2769
2807
|
const inputId = (0, import_react11.useId)();
|
|
2770
2808
|
const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react11.useState)(false);
|
|
2809
|
+
const [resolvingId, setResolvingId] = (0, import_react11.useState)("");
|
|
2771
2810
|
const open = openProp != null ? openProp : uncontrolledOpen;
|
|
2772
2811
|
const setOpen = (0, import_react11.useCallback)(
|
|
2773
2812
|
(next) => {
|
|
2813
|
+
persistSuiteAskAiOpen(next);
|
|
2774
2814
|
if (openProp === void 0) setUncontrolledOpen(next);
|
|
2775
2815
|
onOpenChange == null ? void 0 : onOpenChange(next);
|
|
2776
2816
|
},
|
|
@@ -2871,7 +2911,14 @@ function SuiteAiPanel({
|
|
|
2871
2911
|
signal: controller.signal
|
|
2872
2912
|
});
|
|
2873
2913
|
if (controller.signal.aborted) return;
|
|
2874
|
-
|
|
2914
|
+
const nextMessages = (_a = result.messages) != null ? _a : [];
|
|
2915
|
+
setMessages(nextMessages);
|
|
2916
|
+
notifySuiteMutated(
|
|
2917
|
+
nextMessages.flatMap((message) => {
|
|
2918
|
+
var _a2;
|
|
2919
|
+
return (_a2 = message.actions) != null ? _a2 : [];
|
|
2920
|
+
})
|
|
2921
|
+
);
|
|
2875
2922
|
} catch (err) {
|
|
2876
2923
|
if (controller.signal.aborted) return;
|
|
2877
2924
|
setMessages(
|
|
@@ -2914,6 +2961,29 @@ function SuiteAiPanel({
|
|
|
2914
2961
|
);
|
|
2915
2962
|
}
|
|
2916
2963
|
}
|
|
2964
|
+
async function handleResolve(actionId, decision) {
|
|
2965
|
+
var _a;
|
|
2966
|
+
if (!resolveAction || resolvingId) return;
|
|
2967
|
+
setResolvingId(actionId);
|
|
2968
|
+
setError("");
|
|
2969
|
+
try {
|
|
2970
|
+
const result = await resolveAction(actionId, decision);
|
|
2971
|
+
const nextMessages = (_a = result.messages) != null ? _a : [];
|
|
2972
|
+
setMessages(nextMessages);
|
|
2973
|
+
notifySuiteMutated(
|
|
2974
|
+
nextMessages.flatMap((message) => {
|
|
2975
|
+
var _a2;
|
|
2976
|
+
return (_a2 = message.actions) != null ? _a2 : [];
|
|
2977
|
+
})
|
|
2978
|
+
);
|
|
2979
|
+
} catch (err) {
|
|
2980
|
+
setError(
|
|
2981
|
+
err instanceof Error ? err.message : "Could not apply that action."
|
|
2982
|
+
);
|
|
2983
|
+
} finally {
|
|
2984
|
+
setResolvingId("");
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2917
2987
|
function onConversationClick(event) {
|
|
2918
2988
|
var _a;
|
|
2919
2989
|
if (event.defaultPrevented) return;
|
|
@@ -2931,8 +3001,10 @@ function SuiteAiPanel({
|
|
|
2931
3001
|
);
|
|
2932
3002
|
if (!match) return;
|
|
2933
3003
|
event.preventDefault();
|
|
3004
|
+
persistSuiteAskAiOpen(true);
|
|
2934
3005
|
if (match.sameApp) {
|
|
2935
|
-
|
|
3006
|
+
if (onSameAppNavigate) onSameAppNavigate(match.path);
|
|
3007
|
+
else window.location.assign(match.path);
|
|
2936
3008
|
return;
|
|
2937
3009
|
}
|
|
2938
3010
|
if (onSwitchApp) onSwitchApp(match.slug, match.path);
|
|
@@ -3001,6 +3073,7 @@ function SuiteAiPanel({
|
|
|
3001
3073
|
)) }) : null
|
|
3002
3074
|
] }) : null,
|
|
3003
3075
|
messages.map((message, index) => {
|
|
3076
|
+
var _a;
|
|
3004
3077
|
const isUser = message.role === "user";
|
|
3005
3078
|
if (isUser) {
|
|
3006
3079
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
@@ -3027,13 +3100,67 @@ function SuiteAiPanel({
|
|
|
3027
3100
|
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Icon, { className: "h-3.5 w-3.5" })
|
|
3028
3101
|
}
|
|
3029
3102
|
),
|
|
3030
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3103
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
|
|
3104
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3105
|
+
import_react11.Suspense,
|
|
3106
|
+
{
|
|
3107
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
|
|
3108
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SuiteAiBlockNoteMessage2, { markdown: message.content })
|
|
3109
|
+
}
|
|
3110
|
+
) }),
|
|
3111
|
+
((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
3112
|
+
"div",
|
|
3113
|
+
{
|
|
3114
|
+
"data-test": "ask-ai-action",
|
|
3115
|
+
"data-status": action.status,
|
|
3116
|
+
className: "rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink",
|
|
3117
|
+
children: [
|
|
3118
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "font-medium", children: action.summary }),
|
|
3119
|
+
action.status === "proposed" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
3120
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3121
|
+
"button",
|
|
3122
|
+
{
|
|
3123
|
+
type: "button",
|
|
3124
|
+
"data-test": "ask-ai-action-confirm",
|
|
3125
|
+
disabled: Boolean(resolvingId) || !resolveAction,
|
|
3126
|
+
onClick: () => void handleResolve(action.id, "confirm"),
|
|
3127
|
+
className: "inline-flex min-h-8 items-center rounded-lg bg-ph-brand px-2.5 text-xs font-medium text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40",
|
|
3128
|
+
children: resolvingId === action.id ? "Working\u2026" : "Confirm"
|
|
3129
|
+
}
|
|
3130
|
+
),
|
|
3131
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3132
|
+
"button",
|
|
3133
|
+
{
|
|
3134
|
+
type: "button",
|
|
3135
|
+
"data-test": "ask-ai-action-cancel",
|
|
3136
|
+
disabled: Boolean(resolvingId) || !resolveAction,
|
|
3137
|
+
onClick: () => void handleResolve(action.id, "cancel"),
|
|
3138
|
+
className: "inline-flex min-h-8 items-center rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40",
|
|
3139
|
+
children: "Cancel"
|
|
3140
|
+
}
|
|
3141
|
+
)
|
|
3142
|
+
] }) : null,
|
|
3143
|
+
action.status === "applied" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "mt-1 text-xs text-ph-subtle", children: [
|
|
3144
|
+
"Done",
|
|
3145
|
+
action.href ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
3146
|
+
" \xB7 ",
|
|
3147
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3148
|
+
"a",
|
|
3149
|
+
{
|
|
3150
|
+
href: action.href,
|
|
3151
|
+
className: "text-ph-brand underline-offset-2 hover:underline",
|
|
3152
|
+
children: "Open"
|
|
3153
|
+
}
|
|
3154
|
+
)
|
|
3155
|
+
] }) : null
|
|
3156
|
+
] }) : null,
|
|
3157
|
+
action.status === "cancelled" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-1 text-xs text-ph-subtle", children: "Cancelled" }) : null,
|
|
3158
|
+
action.status === "failed" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "mt-1 text-xs text-ph-danger", children: action.error || "Could not apply that." }) : null
|
|
3159
|
+
]
|
|
3160
|
+
},
|
|
3161
|
+
action.id
|
|
3162
|
+
))
|
|
3163
|
+
] })
|
|
3037
3164
|
]
|
|
3038
3165
|
},
|
|
3039
3166
|
`assistant-${index}`
|
|
@@ -3587,30 +3714,37 @@ function SuiteSidebar({
|
|
|
3587
3714
|
}) {
|
|
3588
3715
|
const showAskAi = Boolean(onAskAiOpenChange && askAi);
|
|
3589
3716
|
const chatOpen = showAskAi && askAiOpen && !collapsed;
|
|
3717
|
+
const setAskAi = (0, import_react16.useCallback)(
|
|
3718
|
+
(next) => {
|
|
3719
|
+
persistSuiteAskAiOpen(next);
|
|
3720
|
+
onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(next);
|
|
3721
|
+
},
|
|
3722
|
+
[onAskAiOpenChange]
|
|
3723
|
+
);
|
|
3590
3724
|
(0, import_react16.useEffect)(() => {
|
|
3591
3725
|
if (!onAskAiOpenChange) return;
|
|
3592
3726
|
function onOpen() {
|
|
3593
3727
|
onRequestExpand == null ? void 0 : onRequestExpand();
|
|
3594
|
-
|
|
3728
|
+
setAskAi(true);
|
|
3595
3729
|
}
|
|
3596
3730
|
window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
3597
3731
|
return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
3598
|
-
}, [onAskAiOpenChange, onRequestExpand]);
|
|
3732
|
+
}, [onAskAiOpenChange, onRequestExpand, setAskAi]);
|
|
3599
3733
|
(0, import_react16.useEffect)(() => {
|
|
3600
3734
|
if (!chatOpen) return;
|
|
3601
3735
|
function onKey(event) {
|
|
3602
|
-
if (event.key === "Escape")
|
|
3736
|
+
if (event.key === "Escape") setAskAi(false);
|
|
3603
3737
|
}
|
|
3604
3738
|
window.addEventListener("keydown", onKey);
|
|
3605
3739
|
return () => window.removeEventListener("keydown", onKey);
|
|
3606
|
-
}, [chatOpen,
|
|
3740
|
+
}, [chatOpen, setAskAi]);
|
|
3607
3741
|
function toggleAskAi() {
|
|
3608
3742
|
if (askAiOpen) {
|
|
3609
|
-
|
|
3743
|
+
setAskAi(false);
|
|
3610
3744
|
return;
|
|
3611
3745
|
}
|
|
3612
3746
|
if (collapsed) onRequestExpand == null ? void 0 : onRequestExpand();
|
|
3613
|
-
|
|
3747
|
+
setAskAi(true);
|
|
3614
3748
|
}
|
|
3615
3749
|
const askAiButton = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
3616
3750
|
"button",
|
|
@@ -3652,7 +3786,7 @@ function SuiteSidebar({
|
|
|
3652
3786
|
type: "button",
|
|
3653
3787
|
"data-test": "suite-ask-ai-back",
|
|
3654
3788
|
"aria-label": "Back to navigation",
|
|
3655
|
-
onClick: () =>
|
|
3789
|
+
onClick: () => setAskAi(false),
|
|
3656
3790
|
className: "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
|
|
3657
3791
|
children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_iconoir_react10.NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true })
|
|
3658
3792
|
}
|
|
@@ -3707,7 +3841,7 @@ function SuiteSidebar({
|
|
|
3707
3841
|
{
|
|
3708
3842
|
type: "button",
|
|
3709
3843
|
"data-test": "suite-ask-ai-back",
|
|
3710
|
-
onClick: () =>
|
|
3844
|
+
onClick: () => setAskAi(false),
|
|
3711
3845
|
className: "inline-flex h-9 items-center gap-1 rounded-lg px-2 text-sm font-medium text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
|
|
3712
3846
|
children: [
|
|
3713
3847
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_iconoir_react10.NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
|
|
@@ -3919,8 +4053,10 @@ function SuiteAppLayout({
|
|
|
3919
4053
|
SIDEBAR_RAIL_WIDTH,
|
|
3920
4054
|
SUITE_APPS,
|
|
3921
4055
|
SUITE_APP_MAP,
|
|
4056
|
+
SUITE_ASK_AI_OPEN_KEY,
|
|
3922
4057
|
SUITE_GLYPHS,
|
|
3923
4058
|
SUITE_ICON_NAMES,
|
|
4059
|
+
SUITE_MUTATED_EVENT,
|
|
3924
4060
|
SUITE_OPEN_ASK_AI_EVENT,
|
|
3925
4061
|
SUITE_SPRINGS,
|
|
3926
4062
|
SourceAppGlyph,
|
|
@@ -3950,6 +4086,7 @@ function SuiteAppLayout({
|
|
|
3950
4086
|
SuiteToolbar,
|
|
3951
4087
|
SuiteUserMenu,
|
|
3952
4088
|
SuiteWorkspaceSwitcher,
|
|
4089
|
+
TURTLETIME_TIMER_MUTATED_EVENT,
|
|
3953
4090
|
TodayAskAiCard,
|
|
3954
4091
|
TodayCalibrating,
|
|
3955
4092
|
TodayEmptyAction,
|
|
@@ -3971,6 +4108,8 @@ function SuiteAppLayout({
|
|
|
3971
4108
|
getTodayGreeting,
|
|
3972
4109
|
m,
|
|
3973
4110
|
openSuiteAskAi,
|
|
4111
|
+
persistSuiteAskAiOpen,
|
|
4112
|
+
readSuiteAskAiOpen,
|
|
3974
4113
|
resolveSuiteNotificationHref,
|
|
3975
4114
|
sidebarColumnWidth,
|
|
3976
4115
|
sourceToSuiteApp,
|
package/dist/suite.mjs
CHANGED
|
@@ -2174,9 +2174,28 @@ import {
|
|
|
2174
2174
|
Square,
|
|
2175
2175
|
WarningTriangle
|
|
2176
2176
|
} from "iconoir-react";
|
|
2177
|
-
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2177
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2178
2178
|
var SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote-DIUICX3G.mjs"));
|
|
2179
2179
|
var SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
2180
|
+
var SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
|
|
2181
|
+
var SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
|
|
2182
|
+
var TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
|
|
2183
|
+
function persistSuiteAskAiOpen(open) {
|
|
2184
|
+
if (typeof window === "undefined") return;
|
|
2185
|
+
try {
|
|
2186
|
+
if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
|
|
2187
|
+
else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
|
|
2188
|
+
} catch (e) {
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
function readSuiteAskAiOpen() {
|
|
2192
|
+
if (typeof window === "undefined") return false;
|
|
2193
|
+
try {
|
|
2194
|
+
return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
|
|
2195
|
+
} catch (e) {
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2180
2199
|
var pendingAskAiPrompt = null;
|
|
2181
2200
|
function openSuiteAskAi(detail) {
|
|
2182
2201
|
var _a;
|
|
@@ -2193,6 +2212,18 @@ function consumePendingAskAiPrompt() {
|
|
|
2193
2212
|
pendingAskAiPrompt = null;
|
|
2194
2213
|
return next;
|
|
2195
2214
|
}
|
|
2215
|
+
function notifySuiteMutated(actions) {
|
|
2216
|
+
if (typeof window === "undefined" || !(actions == null ? void 0 : actions.length)) return;
|
|
2217
|
+
const kinds = actions.map((action) => action.kind);
|
|
2218
|
+
if (kinds.some(
|
|
2219
|
+
(kind) => kind.includes("timer") || kind.includes("time") || kind.includes("hours")
|
|
2220
|
+
)) {
|
|
2221
|
+
window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
|
|
2222
|
+
}
|
|
2223
|
+
window.dispatchEvent(
|
|
2224
|
+
new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } })
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2196
2227
|
function SuiteAiPanel({
|
|
2197
2228
|
presets = [],
|
|
2198
2229
|
emptyState = "Ask when you are ready \u2014 overdue work, a running timer, or anything else.",
|
|
@@ -2205,13 +2236,17 @@ function SuiteAiPanel({
|
|
|
2205
2236
|
open: openProp,
|
|
2206
2237
|
onOpenChange,
|
|
2207
2238
|
suiteAppBases,
|
|
2208
|
-
onSwitchApp
|
|
2239
|
+
onSwitchApp,
|
|
2240
|
+
onSameAppNavigate,
|
|
2241
|
+
resolveAction
|
|
2209
2242
|
}) {
|
|
2210
2243
|
const inputId = useId();
|
|
2211
2244
|
const [uncontrolledOpen, setUncontrolledOpen] = useState7(false);
|
|
2245
|
+
const [resolvingId, setResolvingId] = useState7("");
|
|
2212
2246
|
const open = openProp != null ? openProp : uncontrolledOpen;
|
|
2213
2247
|
const setOpen = useCallback2(
|
|
2214
2248
|
(next) => {
|
|
2249
|
+
persistSuiteAskAiOpen(next);
|
|
2215
2250
|
if (openProp === void 0) setUncontrolledOpen(next);
|
|
2216
2251
|
onOpenChange == null ? void 0 : onOpenChange(next);
|
|
2217
2252
|
},
|
|
@@ -2312,7 +2347,14 @@ function SuiteAiPanel({
|
|
|
2312
2347
|
signal: controller.signal
|
|
2313
2348
|
});
|
|
2314
2349
|
if (controller.signal.aborted) return;
|
|
2315
|
-
|
|
2350
|
+
const nextMessages = (_a = result.messages) != null ? _a : [];
|
|
2351
|
+
setMessages(nextMessages);
|
|
2352
|
+
notifySuiteMutated(
|
|
2353
|
+
nextMessages.flatMap((message) => {
|
|
2354
|
+
var _a2;
|
|
2355
|
+
return (_a2 = message.actions) != null ? _a2 : [];
|
|
2356
|
+
})
|
|
2357
|
+
);
|
|
2316
2358
|
} catch (err) {
|
|
2317
2359
|
if (controller.signal.aborted) return;
|
|
2318
2360
|
setMessages(
|
|
@@ -2355,6 +2397,29 @@ function SuiteAiPanel({
|
|
|
2355
2397
|
);
|
|
2356
2398
|
}
|
|
2357
2399
|
}
|
|
2400
|
+
async function handleResolve(actionId, decision) {
|
|
2401
|
+
var _a;
|
|
2402
|
+
if (!resolveAction || resolvingId) return;
|
|
2403
|
+
setResolvingId(actionId);
|
|
2404
|
+
setError("");
|
|
2405
|
+
try {
|
|
2406
|
+
const result = await resolveAction(actionId, decision);
|
|
2407
|
+
const nextMessages = (_a = result.messages) != null ? _a : [];
|
|
2408
|
+
setMessages(nextMessages);
|
|
2409
|
+
notifySuiteMutated(
|
|
2410
|
+
nextMessages.flatMap((message) => {
|
|
2411
|
+
var _a2;
|
|
2412
|
+
return (_a2 = message.actions) != null ? _a2 : [];
|
|
2413
|
+
})
|
|
2414
|
+
);
|
|
2415
|
+
} catch (err) {
|
|
2416
|
+
setError(
|
|
2417
|
+
err instanceof Error ? err.message : "Could not apply that action."
|
|
2418
|
+
);
|
|
2419
|
+
} finally {
|
|
2420
|
+
setResolvingId("");
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2358
2423
|
function onConversationClick(event) {
|
|
2359
2424
|
var _a;
|
|
2360
2425
|
if (event.defaultPrevented) return;
|
|
@@ -2372,8 +2437,10 @@ function SuiteAiPanel({
|
|
|
2372
2437
|
);
|
|
2373
2438
|
if (!match) return;
|
|
2374
2439
|
event.preventDefault();
|
|
2440
|
+
persistSuiteAskAiOpen(true);
|
|
2375
2441
|
if (match.sameApp) {
|
|
2376
|
-
|
|
2442
|
+
if (onSameAppNavigate) onSameAppNavigate(match.path);
|
|
2443
|
+
else window.location.assign(match.path);
|
|
2377
2444
|
return;
|
|
2378
2445
|
}
|
|
2379
2446
|
if (onSwitchApp) onSwitchApp(match.slug, match.path);
|
|
@@ -2442,6 +2509,7 @@ function SuiteAiPanel({
|
|
|
2442
2509
|
)) }) : null
|
|
2443
2510
|
] }) : null,
|
|
2444
2511
|
messages.map((message, index) => {
|
|
2512
|
+
var _a;
|
|
2445
2513
|
const isUser = message.role === "user";
|
|
2446
2514
|
if (isUser) {
|
|
2447
2515
|
return /* @__PURE__ */ jsx17(
|
|
@@ -2468,13 +2536,67 @@ function SuiteAiPanel({
|
|
|
2468
2536
|
children: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5" })
|
|
2469
2537
|
}
|
|
2470
2538
|
),
|
|
2471
|
-
/* @__PURE__ */
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2539
|
+
/* @__PURE__ */ jsxs15("div", { className: "min-w-0 max-w-[85%] space-y-2", children: [
|
|
2540
|
+
/* @__PURE__ */ jsx17("div", { className: "rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink", children: /* @__PURE__ */ jsx17(
|
|
2541
|
+
Suspense,
|
|
2542
|
+
{
|
|
2543
|
+
fallback: /* @__PURE__ */ jsx17("p", { className: "whitespace-pre-wrap break-words", children: message.content }),
|
|
2544
|
+
children: /* @__PURE__ */ jsx17(SuiteAiBlockNoteMessage, { markdown: message.content })
|
|
2545
|
+
}
|
|
2546
|
+
) }),
|
|
2547
|
+
((_a = message.actions) != null ? _a : []).map((action) => /* @__PURE__ */ jsxs15(
|
|
2548
|
+
"div",
|
|
2549
|
+
{
|
|
2550
|
+
"data-test": "ask-ai-action",
|
|
2551
|
+
"data-status": action.status,
|
|
2552
|
+
className: "rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink",
|
|
2553
|
+
children: [
|
|
2554
|
+
/* @__PURE__ */ jsx17("p", { className: "font-medium", children: action.summary }),
|
|
2555
|
+
action.status === "proposed" ? /* @__PURE__ */ jsxs15("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
2556
|
+
/* @__PURE__ */ jsx17(
|
|
2557
|
+
"button",
|
|
2558
|
+
{
|
|
2559
|
+
type: "button",
|
|
2560
|
+
"data-test": "ask-ai-action-confirm",
|
|
2561
|
+
disabled: Boolean(resolvingId) || !resolveAction,
|
|
2562
|
+
onClick: () => void handleResolve(action.id, "confirm"),
|
|
2563
|
+
className: "inline-flex min-h-8 items-center rounded-lg bg-ph-brand px-2.5 text-xs font-medium text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40",
|
|
2564
|
+
children: resolvingId === action.id ? "Working\u2026" : "Confirm"
|
|
2565
|
+
}
|
|
2566
|
+
),
|
|
2567
|
+
/* @__PURE__ */ jsx17(
|
|
2568
|
+
"button",
|
|
2569
|
+
{
|
|
2570
|
+
type: "button",
|
|
2571
|
+
"data-test": "ask-ai-action-cancel",
|
|
2572
|
+
disabled: Boolean(resolvingId) || !resolveAction,
|
|
2573
|
+
onClick: () => void handleResolve(action.id, "cancel"),
|
|
2574
|
+
className: "inline-flex min-h-8 items-center rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40",
|
|
2575
|
+
children: "Cancel"
|
|
2576
|
+
}
|
|
2577
|
+
)
|
|
2578
|
+
] }) : null,
|
|
2579
|
+
action.status === "applied" ? /* @__PURE__ */ jsxs15("p", { className: "mt-1 text-xs text-ph-subtle", children: [
|
|
2580
|
+
"Done",
|
|
2581
|
+
action.href ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
|
|
2582
|
+
" \xB7 ",
|
|
2583
|
+
/* @__PURE__ */ jsx17(
|
|
2584
|
+
"a",
|
|
2585
|
+
{
|
|
2586
|
+
href: action.href,
|
|
2587
|
+
className: "text-ph-brand underline-offset-2 hover:underline",
|
|
2588
|
+
children: "Open"
|
|
2589
|
+
}
|
|
2590
|
+
)
|
|
2591
|
+
] }) : null
|
|
2592
|
+
] }) : null,
|
|
2593
|
+
action.status === "cancelled" ? /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-ph-subtle", children: "Cancelled" }) : null,
|
|
2594
|
+
action.status === "failed" ? /* @__PURE__ */ jsx17("p", { className: "mt-1 text-xs text-ph-danger", children: action.error || "Could not apply that." }) : null
|
|
2595
|
+
]
|
|
2596
|
+
},
|
|
2597
|
+
action.id
|
|
2598
|
+
))
|
|
2599
|
+
] })
|
|
2478
2600
|
]
|
|
2479
2601
|
},
|
|
2480
2602
|
`assistant-${index}`
|
|
@@ -2579,7 +2701,7 @@ function SuiteAiPanel({
|
|
|
2579
2701
|
// src/suite/components/workspace-switcher.tsx
|
|
2580
2702
|
import { useState as useState8 } from "react";
|
|
2581
2703
|
import { Building, Check as Check2, NavArrowDown } from "iconoir-react";
|
|
2582
|
-
import { Fragment as
|
|
2704
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2583
2705
|
function SuiteWorkspaceSwitcher({
|
|
2584
2706
|
workspaces = [],
|
|
2585
2707
|
groups,
|
|
@@ -2620,7 +2742,7 @@ function SuiteWorkspaceSwitcher({
|
|
|
2620
2742
|
"aria-expanded": open,
|
|
2621
2743
|
children: [
|
|
2622
2744
|
/* @__PURE__ */ jsx18(Building, { className: "h-4 w-4 shrink-0 text-ph-ink", "aria-hidden": true }),
|
|
2623
|
-
!collapsed ? /* @__PURE__ */ jsxs16(
|
|
2745
|
+
!collapsed ? /* @__PURE__ */ jsxs16(Fragment6, { children: [
|
|
2624
2746
|
/* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-ph-ink", children: name }),
|
|
2625
2747
|
/* @__PURE__ */ jsx18(
|
|
2626
2748
|
NavArrowDown,
|
|
@@ -3007,9 +3129,9 @@ function SuiteSettingsMobileNav({
|
|
|
3007
3129
|
|
|
3008
3130
|
// src/suite/components/suite-sidebar.tsx
|
|
3009
3131
|
import Link2 from "next/link";
|
|
3010
|
-
import { useEffect as useEffect8 } from "react";
|
|
3132
|
+
import { useCallback as useCallback4, useEffect as useEffect8 } from "react";
|
|
3011
3133
|
import { NavArrowLeft, Sparks as Sparks2 } from "iconoir-react";
|
|
3012
|
-
import { Fragment as
|
|
3134
|
+
import { Fragment as Fragment7, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3013
3135
|
function renderNode(node, collapsed) {
|
|
3014
3136
|
if (typeof node === "function") return node(collapsed);
|
|
3015
3137
|
return node;
|
|
@@ -3032,30 +3154,37 @@ function SuiteSidebar({
|
|
|
3032
3154
|
}) {
|
|
3033
3155
|
const showAskAi = Boolean(onAskAiOpenChange && askAi);
|
|
3034
3156
|
const chatOpen = showAskAi && askAiOpen && !collapsed;
|
|
3157
|
+
const setAskAi = useCallback4(
|
|
3158
|
+
(next) => {
|
|
3159
|
+
persistSuiteAskAiOpen(next);
|
|
3160
|
+
onAskAiOpenChange == null ? void 0 : onAskAiOpenChange(next);
|
|
3161
|
+
},
|
|
3162
|
+
[onAskAiOpenChange]
|
|
3163
|
+
);
|
|
3035
3164
|
useEffect8(() => {
|
|
3036
3165
|
if (!onAskAiOpenChange) return;
|
|
3037
3166
|
function onOpen() {
|
|
3038
3167
|
onRequestExpand == null ? void 0 : onRequestExpand();
|
|
3039
|
-
|
|
3168
|
+
setAskAi(true);
|
|
3040
3169
|
}
|
|
3041
3170
|
window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
3042
3171
|
return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
3043
|
-
}, [onAskAiOpenChange, onRequestExpand]);
|
|
3172
|
+
}, [onAskAiOpenChange, onRequestExpand, setAskAi]);
|
|
3044
3173
|
useEffect8(() => {
|
|
3045
3174
|
if (!chatOpen) return;
|
|
3046
3175
|
function onKey(event) {
|
|
3047
|
-
if (event.key === "Escape")
|
|
3176
|
+
if (event.key === "Escape") setAskAi(false);
|
|
3048
3177
|
}
|
|
3049
3178
|
window.addEventListener("keydown", onKey);
|
|
3050
3179
|
return () => window.removeEventListener("keydown", onKey);
|
|
3051
|
-
}, [chatOpen,
|
|
3180
|
+
}, [chatOpen, setAskAi]);
|
|
3052
3181
|
function toggleAskAi() {
|
|
3053
3182
|
if (askAiOpen) {
|
|
3054
|
-
|
|
3183
|
+
setAskAi(false);
|
|
3055
3184
|
return;
|
|
3056
3185
|
}
|
|
3057
3186
|
if (collapsed) onRequestExpand == null ? void 0 : onRequestExpand();
|
|
3058
|
-
|
|
3187
|
+
setAskAi(true);
|
|
3059
3188
|
}
|
|
3060
3189
|
const askAiButton = /* @__PURE__ */ jsx22(
|
|
3061
3190
|
"button",
|
|
@@ -3097,7 +3226,7 @@ function SuiteSidebar({
|
|
|
3097
3226
|
type: "button",
|
|
3098
3227
|
"data-test": "suite-ask-ai-back",
|
|
3099
3228
|
"aria-label": "Back to navigation",
|
|
3100
|
-
onClick: () =>
|
|
3229
|
+
onClick: () => setAskAi(false),
|
|
3101
3230
|
className: "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
|
|
3102
3231
|
children: /* @__PURE__ */ jsx22(NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true })
|
|
3103
3232
|
}
|
|
@@ -3152,7 +3281,7 @@ function SuiteSidebar({
|
|
|
3152
3281
|
{
|
|
3153
3282
|
type: "button",
|
|
3154
3283
|
"data-test": "suite-ask-ai-back",
|
|
3155
|
-
onClick: () =>
|
|
3284
|
+
onClick: () => setAskAi(false),
|
|
3156
3285
|
className: "inline-flex h-9 items-center gap-1 rounded-lg px-2 text-sm font-medium text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
|
|
3157
3286
|
children: [
|
|
3158
3287
|
/* @__PURE__ */ jsx22(NavArrowLeft, { className: "h-4 w-4", "aria-hidden": true }),
|
|
@@ -3163,7 +3292,7 @@ function SuiteSidebar({
|
|
|
3163
3292
|
/* @__PURE__ */ jsx22("span", { className: "truncate text-sm font-semibold text-ph-ink", children: "Shelly AI" })
|
|
3164
3293
|
] }),
|
|
3165
3294
|
/* @__PURE__ */ jsx22("div", { className: "min-h-0 flex-1", children: askAi })
|
|
3166
|
-
] }) : /* @__PURE__ */ jsxs19(
|
|
3295
|
+
] }) : /* @__PURE__ */ jsxs19(Fragment7, { children: [
|
|
3167
3296
|
/* @__PURE__ */ jsx22("nav", { "aria-label": "Pages", className: "shrink-0 space-y-0.5", children: /* @__PURE__ */ jsx22("div", { className: "space-y-0.5", children: navItems.map((item) => {
|
|
3168
3297
|
var _a;
|
|
3169
3298
|
const Icon = item.icon;
|
|
@@ -3363,8 +3492,10 @@ export {
|
|
|
3363
3492
|
SIDEBAR_RAIL_WIDTH,
|
|
3364
3493
|
SUITE_APPS2 as SUITE_APPS,
|
|
3365
3494
|
SUITE_APP_MAP,
|
|
3495
|
+
SUITE_ASK_AI_OPEN_KEY,
|
|
3366
3496
|
SUITE_GLYPHS,
|
|
3367
3497
|
SUITE_ICON_NAMES,
|
|
3498
|
+
SUITE_MUTATED_EVENT,
|
|
3368
3499
|
SUITE_OPEN_ASK_AI_EVENT,
|
|
3369
3500
|
SUITE_SPRINGS,
|
|
3370
3501
|
SourceAppGlyph,
|
|
@@ -3394,6 +3525,7 @@ export {
|
|
|
3394
3525
|
SuiteToolbar,
|
|
3395
3526
|
SuiteUserMenu,
|
|
3396
3527
|
SuiteWorkspaceSwitcher,
|
|
3528
|
+
TURTLETIME_TIMER_MUTATED_EVENT,
|
|
3397
3529
|
TodayAskAiCard,
|
|
3398
3530
|
TodayCalibrating,
|
|
3399
3531
|
TodayEmptyAction,
|
|
@@ -3415,6 +3547,8 @@ export {
|
|
|
3415
3547
|
getTodayGreeting,
|
|
3416
3548
|
m,
|
|
3417
3549
|
openSuiteAskAi,
|
|
3550
|
+
persistSuiteAskAiOpen,
|
|
3551
|
+
readSuiteAskAiOpen,
|
|
3418
3552
|
resolveSuiteNotificationHref,
|
|
3419
3553
|
sidebarColumnWidth,
|
|
3420
3554
|
sourceToSuiteApp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenide-io/the-old-ui-theme",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"description": "Props-driven React components and theme tokens inspired by classic interface design \u2014 optimized for Next.js apps and internal tools.",
|
|
5
5
|
"author": "Xenide",
|
|
6
6
|
"license": "MIT",
|
|
@@ -3,6 +3,9 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
import {
|
|
4
4
|
consumePendingAskAiPrompt,
|
|
5
5
|
openSuiteAskAi,
|
|
6
|
+
persistSuiteAskAiOpen,
|
|
7
|
+
readSuiteAskAiOpen,
|
|
8
|
+
SUITE_ASK_AI_OPEN_KEY,
|
|
6
9
|
SUITE_OPEN_ASK_AI_EVENT,
|
|
7
10
|
} from "./ai-panel";
|
|
8
11
|
|
|
@@ -18,3 +21,13 @@ describe("openSuiteAskAi", () => {
|
|
|
18
21
|
expect(consumePendingAskAiPrompt()).toBeNull();
|
|
19
22
|
});
|
|
20
23
|
});
|
|
24
|
+
|
|
25
|
+
describe("persistSuiteAskAiOpen", () => {
|
|
26
|
+
it("remembers that Shelly was open across a same-tab navigation", () => {
|
|
27
|
+
sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
|
|
28
|
+
persistSuiteAskAiOpen(true);
|
|
29
|
+
expect(readSuiteAskAiOpen()).toBe(true);
|
|
30
|
+
persistSuiteAskAiOpen(false);
|
|
31
|
+
expect(readSuiteAskAiOpen()).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -31,11 +31,49 @@ import {
|
|
|
31
31
|
const SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote"));
|
|
32
32
|
|
|
33
33
|
export const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
|
|
34
|
+
export const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
|
|
35
|
+
export const SUITE_MUTATED_EVENT = "shellstack:suite-mutated";
|
|
36
|
+
export const TURTLETIME_TIMER_MUTATED_EVENT = "tt-timer-mutated";
|
|
34
37
|
|
|
35
38
|
export type SuiteAskAiOpenDetail = {
|
|
36
39
|
prompt?: string;
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
export function persistSuiteAskAiOpen(open: boolean): void {
|
|
43
|
+
if (typeof window === "undefined") return;
|
|
44
|
+
try {
|
|
45
|
+
if (open) sessionStorage.setItem(SUITE_ASK_AI_OPEN_KEY, "1");
|
|
46
|
+
else sessionStorage.removeItem(SUITE_ASK_AI_OPEN_KEY);
|
|
47
|
+
} catch {
|
|
48
|
+
// private mode / blocked storage
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function readSuiteAskAiOpen(): boolean {
|
|
53
|
+
if (typeof window === "undefined") return false;
|
|
54
|
+
try {
|
|
55
|
+
return sessionStorage.getItem(SUITE_ASK_AI_OPEN_KEY) === "1";
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type SuiteAiActionStatus =
|
|
62
|
+
| "proposed"
|
|
63
|
+
| "applied"
|
|
64
|
+
| "cancelled"
|
|
65
|
+
| "failed";
|
|
66
|
+
|
|
67
|
+
export interface SuiteAiAction {
|
|
68
|
+
id: string;
|
|
69
|
+
kind: string;
|
|
70
|
+
status: SuiteAiActionStatus;
|
|
71
|
+
summary: string;
|
|
72
|
+
href?: string;
|
|
73
|
+
error?: string;
|
|
74
|
+
payload?: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
let pendingAskAiPrompt: string | null = null;
|
|
40
78
|
|
|
41
79
|
/** Open the suite Ask AI panel from anywhere (Today card, command palette, …). */
|
|
@@ -65,6 +103,25 @@ export interface SuiteAiChatMessage {
|
|
|
65
103
|
role: "user" | "assistant";
|
|
66
104
|
content: string;
|
|
67
105
|
created_at?: string;
|
|
106
|
+
actions?: SuiteAiAction[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function notifySuiteMutated(actions?: SuiteAiAction[]): void {
|
|
110
|
+
if (typeof window === "undefined" || !actions?.length) return;
|
|
111
|
+
const kinds = actions.map((action) => action.kind);
|
|
112
|
+
if (
|
|
113
|
+
kinds.some(
|
|
114
|
+
(kind) =>
|
|
115
|
+
kind.includes("timer") ||
|
|
116
|
+
kind.includes("time") ||
|
|
117
|
+
kind.includes("hours"),
|
|
118
|
+
)
|
|
119
|
+
) {
|
|
120
|
+
window.dispatchEvent(new Event(TURTLETIME_TIMER_MUTATED_EVENT));
|
|
121
|
+
}
|
|
122
|
+
window.dispatchEvent(
|
|
123
|
+
new CustomEvent(SUITE_MUTATED_EVENT, { detail: { kinds } }),
|
|
124
|
+
);
|
|
68
125
|
}
|
|
69
126
|
|
|
70
127
|
/**
|
|
@@ -84,6 +141,8 @@ export function SuiteAiPanel({
|
|
|
84
141
|
onOpenChange,
|
|
85
142
|
suiteAppBases,
|
|
86
143
|
onSwitchApp,
|
|
144
|
+
onSameAppNavigate,
|
|
145
|
+
resolveAction,
|
|
87
146
|
}: {
|
|
88
147
|
presets?: SuiteAiPreset[];
|
|
89
148
|
emptyState?: ReactNode;
|
|
@@ -105,12 +164,20 @@ export function SuiteAiPanel({
|
|
|
105
164
|
suiteAppBases?: Partial<Record<SuiteAppSlug, string>>;
|
|
106
165
|
/** Cross-app SSO. Same-app links just navigate to `path`. */
|
|
107
166
|
onSwitchApp?: (app: SuiteAppSlug, path: string) => void;
|
|
167
|
+
/** Client-side same-app route change — keeps Shelly mounted. */
|
|
168
|
+
onSameAppNavigate?: (path: string) => void;
|
|
169
|
+
resolveAction?: (
|
|
170
|
+
id: string,
|
|
171
|
+
decision: "confirm" | "cancel",
|
|
172
|
+
) => Promise<{ messages: SuiteAiChatMessage[] }>;
|
|
108
173
|
}) {
|
|
109
174
|
const inputId = useId();
|
|
110
175
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
|
|
176
|
+
const [resolvingId, setResolvingId] = useState("");
|
|
111
177
|
const open = openProp ?? uncontrolledOpen;
|
|
112
178
|
const setOpen = useCallback(
|
|
113
179
|
(next: boolean) => {
|
|
180
|
+
persistSuiteAskAiOpen(next);
|
|
114
181
|
if (openProp === undefined) setUncontrolledOpen(next);
|
|
115
182
|
onOpenChange?.(next);
|
|
116
183
|
},
|
|
@@ -215,7 +282,11 @@ export function SuiteAiPanel({
|
|
|
215
282
|
signal: controller.signal,
|
|
216
283
|
});
|
|
217
284
|
if (controller.signal.aborted) return;
|
|
218
|
-
|
|
285
|
+
const nextMessages = result.messages ?? [];
|
|
286
|
+
setMessages(nextMessages);
|
|
287
|
+
notifySuiteMutated(
|
|
288
|
+
nextMessages.flatMap((message) => message.actions ?? []),
|
|
289
|
+
);
|
|
219
290
|
} catch (err) {
|
|
220
291
|
if (controller.signal.aborted) return;
|
|
221
292
|
setMessages((prev) =>
|
|
@@ -264,6 +335,29 @@ export function SuiteAiPanel({
|
|
|
264
335
|
}
|
|
265
336
|
}
|
|
266
337
|
|
|
338
|
+
async function handleResolve(
|
|
339
|
+
actionId: string,
|
|
340
|
+
decision: "confirm" | "cancel",
|
|
341
|
+
) {
|
|
342
|
+
if (!resolveAction || resolvingId) return;
|
|
343
|
+
setResolvingId(actionId);
|
|
344
|
+
setError("");
|
|
345
|
+
try {
|
|
346
|
+
const result = await resolveAction(actionId, decision);
|
|
347
|
+
const nextMessages = result.messages ?? [];
|
|
348
|
+
setMessages(nextMessages);
|
|
349
|
+
notifySuiteMutated(
|
|
350
|
+
nextMessages.flatMap((message) => message.actions ?? []),
|
|
351
|
+
);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
setError(
|
|
354
|
+
err instanceof Error ? err.message : "Could not apply that action.",
|
|
355
|
+
);
|
|
356
|
+
} finally {
|
|
357
|
+
setResolvingId("");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
267
361
|
function onConversationClick(event: ReactMouseEvent<HTMLDivElement>) {
|
|
268
362
|
if (event.defaultPrevented) return;
|
|
269
363
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
@@ -280,8 +374,10 @@ export function SuiteAiPanel({
|
|
|
280
374
|
);
|
|
281
375
|
if (!match) return;
|
|
282
376
|
event.preventDefault();
|
|
377
|
+
persistSuiteAskAiOpen(true);
|
|
283
378
|
if (match.sameApp) {
|
|
284
|
-
|
|
379
|
+
if (onSameAppNavigate) onSameAppNavigate(match.path);
|
|
380
|
+
else window.location.assign(match.path);
|
|
285
381
|
return;
|
|
286
382
|
}
|
|
287
383
|
if (onSwitchApp) onSwitchApp(match.slug, match.path);
|
|
@@ -391,16 +487,74 @@ export function SuiteAiPanel({
|
|
|
391
487
|
>
|
|
392
488
|
<Icon className="h-3.5 w-3.5" />
|
|
393
489
|
</span>
|
|
394
|
-
<div className="min-w-0 max-w-[85%]
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
490
|
+
<div className="min-w-0 max-w-[85%] space-y-2">
|
|
491
|
+
<div className="rounded-2xl rounded-bl-md bg-ph-muted px-3 py-2 text-sm leading-relaxed text-ph-ink">
|
|
492
|
+
<Suspense
|
|
493
|
+
fallback={
|
|
494
|
+
<p className="whitespace-pre-wrap break-words">
|
|
495
|
+
{message.content}
|
|
496
|
+
</p>
|
|
497
|
+
}
|
|
498
|
+
>
|
|
499
|
+
<SuiteAiBlockNoteMessage markdown={message.content} />
|
|
500
|
+
</Suspense>
|
|
501
|
+
</div>
|
|
502
|
+
{(message.actions ?? []).map((action) => (
|
|
503
|
+
<div
|
|
504
|
+
key={action.id}
|
|
505
|
+
data-test="ask-ai-action"
|
|
506
|
+
data-status={action.status}
|
|
507
|
+
className="rounded-xl border border-ph-border bg-ph-surface px-3 py-2 text-sm text-ph-ink"
|
|
508
|
+
>
|
|
509
|
+
<p className="font-medium">{action.summary}</p>
|
|
510
|
+
{action.status === "proposed" ? (
|
|
511
|
+
<div className="mt-2 flex flex-wrap gap-2">
|
|
512
|
+
<button
|
|
513
|
+
type="button"
|
|
514
|
+
data-test="ask-ai-action-confirm"
|
|
515
|
+
disabled={Boolean(resolvingId) || !resolveAction}
|
|
516
|
+
onClick={() => void handleResolve(action.id, "confirm")}
|
|
517
|
+
className="inline-flex min-h-8 items-center rounded-lg bg-ph-brand px-2.5 text-xs font-medium text-[color:var(--ph-on-accent)] transition-opacity hover:bg-ph-brand-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
|
|
518
|
+
>
|
|
519
|
+
{resolvingId === action.id ? "Working…" : "Confirm"}
|
|
520
|
+
</button>
|
|
521
|
+
<button
|
|
522
|
+
type="button"
|
|
523
|
+
data-test="ask-ai-action-cancel"
|
|
524
|
+
disabled={Boolean(resolvingId) || !resolveAction}
|
|
525
|
+
onClick={() => void handleResolve(action.id, "cancel")}
|
|
526
|
+
className="inline-flex min-h-8 items-center rounded-lg border border-ph-border bg-ph-surface px-2.5 text-xs font-medium text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus disabled:opacity-40"
|
|
527
|
+
>
|
|
528
|
+
Cancel
|
|
529
|
+
</button>
|
|
530
|
+
</div>
|
|
531
|
+
) : null}
|
|
532
|
+
{action.status === "applied" ? (
|
|
533
|
+
<p className="mt-1 text-xs text-ph-subtle">
|
|
534
|
+
Done
|
|
535
|
+
{action.href ? (
|
|
536
|
+
<>
|
|
537
|
+
{" · "}
|
|
538
|
+
<a
|
|
539
|
+
href={action.href}
|
|
540
|
+
className="text-ph-brand underline-offset-2 hover:underline"
|
|
541
|
+
>
|
|
542
|
+
Open
|
|
543
|
+
</a>
|
|
544
|
+
</>
|
|
545
|
+
) : null}
|
|
546
|
+
</p>
|
|
547
|
+
) : null}
|
|
548
|
+
{action.status === "cancelled" ? (
|
|
549
|
+
<p className="mt-1 text-xs text-ph-subtle">Cancelled</p>
|
|
550
|
+
) : null}
|
|
551
|
+
{action.status === "failed" ? (
|
|
552
|
+
<p className="mt-1 text-xs text-ph-danger">
|
|
553
|
+
{action.error || "Could not apply that."}
|
|
554
|
+
</p>
|
|
555
|
+
) : null}
|
|
556
|
+
</div>
|
|
557
|
+
))}
|
|
404
558
|
</div>
|
|
405
559
|
</div>
|
|
406
560
|
);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import { useEffect, type ReactNode } from "react";
|
|
4
|
+
import { useCallback, useEffect, type ReactNode } from "react";
|
|
5
5
|
import { NavArrowLeft, Sparks } from "iconoir-react";
|
|
6
6
|
|
|
7
7
|
import { Tooltip } from "../../components/ui/Tooltip";
|
|
8
8
|
import { cn } from "../lib/cn";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
persistSuiteAskAiOpen,
|
|
11
|
+
SUITE_OPEN_ASK_AI_EVENT,
|
|
12
|
+
} from "./ai-panel";
|
|
10
13
|
import type { SuiteNavIcon } from "./suite-bottom-nav";
|
|
11
14
|
|
|
12
15
|
type CollapsedNode = ReactNode | ((collapsed: boolean) => ReactNode);
|
|
@@ -84,32 +87,40 @@ export function SuiteSidebar({
|
|
|
84
87
|
const showAskAi = Boolean(onAskAiOpenChange && askAi);
|
|
85
88
|
const chatOpen = showAskAi && askAiOpen && !collapsed;
|
|
86
89
|
|
|
90
|
+
const setAskAi = useCallback(
|
|
91
|
+
(next: boolean) => {
|
|
92
|
+
persistSuiteAskAiOpen(next);
|
|
93
|
+
onAskAiOpenChange?.(next);
|
|
94
|
+
},
|
|
95
|
+
[onAskAiOpenChange],
|
|
96
|
+
);
|
|
97
|
+
|
|
87
98
|
useEffect(() => {
|
|
88
99
|
if (!onAskAiOpenChange) return;
|
|
89
100
|
function onOpen() {
|
|
90
101
|
onRequestExpand?.();
|
|
91
|
-
|
|
102
|
+
setAskAi(true);
|
|
92
103
|
}
|
|
93
104
|
window.addEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
94
105
|
return () => window.removeEventListener(SUITE_OPEN_ASK_AI_EVENT, onOpen);
|
|
95
|
-
}, [onAskAiOpenChange, onRequestExpand]);
|
|
106
|
+
}, [onAskAiOpenChange, onRequestExpand, setAskAi]);
|
|
96
107
|
|
|
97
108
|
useEffect(() => {
|
|
98
109
|
if (!chatOpen) return;
|
|
99
110
|
function onKey(event: KeyboardEvent) {
|
|
100
|
-
if (event.key === "Escape")
|
|
111
|
+
if (event.key === "Escape") setAskAi(false);
|
|
101
112
|
}
|
|
102
113
|
window.addEventListener("keydown", onKey);
|
|
103
114
|
return () => window.removeEventListener("keydown", onKey);
|
|
104
|
-
}, [chatOpen,
|
|
115
|
+
}, [chatOpen, setAskAi]);
|
|
105
116
|
|
|
106
117
|
function toggleAskAi() {
|
|
107
118
|
if (askAiOpen) {
|
|
108
|
-
|
|
119
|
+
setAskAi(false);
|
|
109
120
|
return;
|
|
110
121
|
}
|
|
111
122
|
if (collapsed) onRequestExpand?.();
|
|
112
|
-
|
|
123
|
+
setAskAi(true);
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
const askAiButton = (
|
|
@@ -151,7 +162,7 @@ export function SuiteSidebar({
|
|
|
151
162
|
type="button"
|
|
152
163
|
data-test="suite-ask-ai-back"
|
|
153
164
|
aria-label="Back to navigation"
|
|
154
|
-
onClick={() =>
|
|
165
|
+
onClick={() => setAskAi(false)}
|
|
155
166
|
className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
|
|
156
167
|
>
|
|
157
168
|
<NavArrowLeft className="h-4 w-4" aria-hidden />
|
|
@@ -213,7 +224,7 @@ export function SuiteSidebar({
|
|
|
213
224
|
<button
|
|
214
225
|
type="button"
|
|
215
226
|
data-test="suite-ask-ai-back"
|
|
216
|
-
onClick={() =>
|
|
227
|
+
onClick={() => setAskAi(false)}
|
|
217
228
|
className="inline-flex h-9 items-center gap-1 rounded-lg px-2 text-sm font-medium text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
|
|
218
229
|
>
|
|
219
230
|
<NavArrowLeft className="h-4 w-4" aria-hidden />
|
package/src/suite/index.ts
CHANGED
|
@@ -53,7 +53,14 @@ export {
|
|
|
53
53
|
SuiteAiPanel,
|
|
54
54
|
openSuiteAskAi,
|
|
55
55
|
consumePendingAskAiPrompt,
|
|
56
|
+
persistSuiteAskAiOpen,
|
|
57
|
+
readSuiteAskAiOpen,
|
|
56
58
|
SUITE_OPEN_ASK_AI_EVENT,
|
|
59
|
+
SUITE_ASK_AI_OPEN_KEY,
|
|
60
|
+
SUITE_MUTATED_EVENT,
|
|
61
|
+
TURTLETIME_TIMER_MUTATED_EVENT,
|
|
62
|
+
type SuiteAiAction,
|
|
63
|
+
type SuiteAiActionStatus,
|
|
57
64
|
type SuiteAiPreset,
|
|
58
65
|
type SuiteAiChatMessage,
|
|
59
66
|
type SuiteAskAiOpenDetail,
|