@xenide-io/the-old-ui-theme 0.9.7 → 0.9.9

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.
@@ -3742,6 +3742,166 @@ var Chip = forwardRef11(function Chip2(_a, ref) {
3742
3742
  });
3743
3743
  Chip.displayName = "Chip";
3744
3744
 
3745
+ // src/lib/chart/use-ph-chart-tokens.ts
3746
+ import { useEffect as useEffect5, useState as useState10 } from "react";
3747
+ var DEFAULT_TOKENS = {
3748
+ series: ["#1d4aff", "#621da6", "#42827e", "#ce0e74", "#f14f58", "#529a0a", "#fe729e"],
3749
+ muted: "hsl(220 9% 90%)",
3750
+ surface: "#ffffff",
3751
+ textMuted: "hsl(220 9% 46%)",
3752
+ grid: "hsl(220 13% 91%)",
3753
+ borderStrong: "hsl(220 13% 80%)"
3754
+ };
3755
+ function readTokens() {
3756
+ if (typeof document === "undefined") return DEFAULT_TOKENS;
3757
+ const style = getComputedStyle(document.documentElement);
3758
+ const read = (key, fallback) => style.getPropertyValue(key).trim() || fallback;
3759
+ return {
3760
+ series: [
3761
+ read("--ph-data-1", DEFAULT_TOKENS.series[0]),
3762
+ read("--ph-data-2", DEFAULT_TOKENS.series[1]),
3763
+ read("--ph-data-3", DEFAULT_TOKENS.series[2]),
3764
+ read("--ph-data-4", DEFAULT_TOKENS.series[3]),
3765
+ read("--ph-data-5", DEFAULT_TOKENS.series[4]),
3766
+ read("--ph-data-6", DEFAULT_TOKENS.series[5]),
3767
+ read("--ph-data-7", DEFAULT_TOKENS.series[6])
3768
+ ],
3769
+ muted: read("--ph-muted", DEFAULT_TOKENS.muted),
3770
+ surface: read("--ph-surface", DEFAULT_TOKENS.surface),
3771
+ textMuted: read("--ph-mutedtext", DEFAULT_TOKENS.textMuted),
3772
+ grid: read("--ph-border", DEFAULT_TOKENS.grid),
3773
+ borderStrong: read("--ph-border-strong", DEFAULT_TOKENS.borderStrong)
3774
+ };
3775
+ }
3776
+ function useChartTokens() {
3777
+ const [tokens, setTokens] = useState10(DEFAULT_TOKENS);
3778
+ useEffect5(() => {
3779
+ setTokens(readTokens());
3780
+ }, []);
3781
+ return tokens;
3782
+ }
3783
+
3784
+ // src/components/ui/Charts.tsx
3785
+ import { jsx as jsx42, jsxs as jsxs36 } from "react/jsx-runtime";
3786
+ function percent(value, total) {
3787
+ return total > 0 ? Math.round(value / total * 100) : 0;
3788
+ }
3789
+ function EmptyChart() {
3790
+ return /* @__PURE__ */ jsx42("p", { className: "text-sm text-ph-subtle", children: "No data for this period." });
3791
+ }
3792
+ function sliceColour(slice, index, series) {
3793
+ var _a;
3794
+ return (_a = slice.colour) != null ? _a : series[index % series.length];
3795
+ }
3796
+ function DonutChart({ slices, label, formatValue, className }) {
3797
+ const { series } = useChartTokens();
3798
+ const visible = slices.filter((slice) => slice.value > 0);
3799
+ const total = visible.reduce((sum, slice) => sum + slice.value, 0);
3800
+ if (total <= 0) return /* @__PURE__ */ jsx42(EmptyChart, {});
3801
+ const stops = visible.map((slice, index) => {
3802
+ const colour = sliceColour(slice, index, series);
3803
+ const before = visible.slice(0, index).reduce((sum, item) => sum + item.value, 0);
3804
+ const start = before / total * 360;
3805
+ const end = (before + slice.value) / total * 360;
3806
+ return `${colour} ${start}deg ${end}deg`;
3807
+ }).join(", ");
3808
+ return /* @__PURE__ */ jsxs36("div", { className: cn("flex flex-col items-center gap-4 sm:flex-row sm:items-center", className), children: [
3809
+ /* @__PURE__ */ jsx42(
3810
+ "div",
3811
+ {
3812
+ role: "img",
3813
+ "aria-label": label,
3814
+ className: "relative h-36 w-36 shrink-0 rounded-full",
3815
+ style: { background: `conic-gradient(${stops})` },
3816
+ children: /* @__PURE__ */ jsx42(
3817
+ "span",
3818
+ {
3819
+ className: "absolute inset-[22%] rounded-full bg-ph-surface ring-1 ring-ph-border",
3820
+ "aria-hidden": true
3821
+ }
3822
+ )
3823
+ }
3824
+ ),
3825
+ /* @__PURE__ */ jsx42("ul", { className: "min-w-0 w-full flex-1 space-y-2", children: visible.map((slice, index) => {
3826
+ const colour = sliceColour(slice, index, series);
3827
+ return /* @__PURE__ */ jsxs36("li", { className: "flex items-center justify-between gap-3 text-sm", children: [
3828
+ /* @__PURE__ */ jsxs36("span", { className: "flex min-w-0 items-center gap-2 text-ph-subtle", children: [
3829
+ /* @__PURE__ */ jsx42("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { background: colour } }),
3830
+ /* @__PURE__ */ jsx42("span", { className: "truncate", children: slice.label })
3831
+ ] }),
3832
+ /* @__PURE__ */ jsx42("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%` })
3833
+ ] }, slice.label);
3834
+ }) })
3835
+ ] });
3836
+ }
3837
+ function BarChart({ items, formatValue, className }) {
3838
+ const { series } = useChartTokens();
3839
+ const visible = items.filter((item) => item.value > 0);
3840
+ const max = Math.max(...visible.map((item) => item.value), 1);
3841
+ if (!visible.length) return /* @__PURE__ */ jsx42(EmptyChart, {});
3842
+ return /* @__PURE__ */ jsx42("ul", { className: cn("space-y-3", className), children: visible.map((item, index) => {
3843
+ const colour = sliceColour(item, index, series);
3844
+ return /* @__PURE__ */ jsxs36("li", { children: [
3845
+ /* @__PURE__ */ jsxs36("div", { className: "mb-1 flex items-center justify-between gap-3 text-sm", children: [
3846
+ /* @__PURE__ */ jsx42("span", { className: "truncate text-ph-subtle", children: item.label }),
3847
+ /* @__PURE__ */ jsx42("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(item.value) : item.value.toLocaleString("en-AU") })
3848
+ ] }),
3849
+ /* @__PURE__ */ jsx42("div", { className: "h-2.5 rounded-full bg-ph-muted ring-1 ring-ph-border", children: /* @__PURE__ */ jsx42(
3850
+ "div",
3851
+ {
3852
+ className: "h-full rounded-full transition-[width] duration-300 ease-out motion-reduce:transition-none",
3853
+ style: {
3854
+ width: `${Math.max(item.value / max * 100, 2)}%`,
3855
+ background: colour
3856
+ }
3857
+ }
3858
+ ) })
3859
+ ] }, item.label);
3860
+ }) });
3861
+ }
3862
+ function StackedBarChart({
3863
+ slices,
3864
+ label,
3865
+ formatValue,
3866
+ className
3867
+ }) {
3868
+ const { series } = useChartTokens();
3869
+ const visible = slices.filter((slice) => slice.value > 0);
3870
+ const total = visible.reduce((sum, slice) => sum + slice.value, 0);
3871
+ if (total <= 0) return /* @__PURE__ */ jsx42(EmptyChart, {});
3872
+ return /* @__PURE__ */ jsxs36("div", { className: cn("space-y-3", className), children: [
3873
+ /* @__PURE__ */ jsx42(
3874
+ "div",
3875
+ {
3876
+ role: "img",
3877
+ "aria-label": label,
3878
+ className: "flex h-3 overflow-hidden rounded-full bg-ph-muted ring-1 ring-ph-border",
3879
+ children: visible.map((slice, index) => /* @__PURE__ */ jsx42(
3880
+ "span",
3881
+ {
3882
+ className: "h-full",
3883
+ style: {
3884
+ width: `${Math.max(slice.value / total * 100, 2)}%`,
3885
+ background: sliceColour(slice, index, series)
3886
+ }
3887
+ },
3888
+ slice.label
3889
+ ))
3890
+ }
3891
+ ),
3892
+ /* @__PURE__ */ jsx42("ul", { className: "space-y-2", children: visible.map((slice, index) => {
3893
+ const colour = sliceColour(slice, index, series);
3894
+ return /* @__PURE__ */ jsxs36("li", { className: "flex items-center justify-between gap-3 text-sm", children: [
3895
+ /* @__PURE__ */ jsxs36("span", { className: "flex min-w-0 items-center gap-2 text-ph-subtle", children: [
3896
+ /* @__PURE__ */ jsx42("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { background: colour } }),
3897
+ /* @__PURE__ */ jsx42("span", { className: "truncate", children: slice.label })
3898
+ ] }),
3899
+ /* @__PURE__ */ jsx42("span", { className: "shrink-0 tabular-nums text-ph-ink", children: formatValue ? formatValue(slice.value) : `${percent(slice.value, total)}%` })
3900
+ ] }, slice.label);
3901
+ }) })
3902
+ ] });
3903
+ }
3904
+
3745
3905
  export {
3746
3906
  ButtonChrome,
3747
3907
  cn,
@@ -3908,5 +4068,9 @@ export {
3908
4068
  Link,
3909
4069
  Spinner,
3910
4070
  Dot,
3911
- Chip
4071
+ Chip,
4072
+ useChartTokens,
4073
+ DonutChart,
4074
+ BarChart,
4075
+ StackedBarChart
3912
4076
  };
package/dist/index.mjs CHANGED
@@ -158,7 +158,7 @@ import {
158
158
  isThemeId,
159
159
  persistTheme,
160
160
  readStoredTheme
161
- } from "./chunk-2EO5VCXP.mjs";
161
+ } from "./chunk-6SVJAU5K.mjs";
162
162
  import "./chunk-FWCSY2DS.mjs";
163
163
  export {
164
164
  Accordion,
package/dist/suite.d.mts CHANGED
@@ -486,7 +486,7 @@ interface SuiteAiChatMessage {
486
486
  * Suite-wide Ask AI chat — Notion-style continuous thread.
487
487
  * Lives in the sidebar (or any parent that mounts it). Not Kraken Deep Research.
488
488
  */
489
- declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, isWaitingForReply, }: {
489
+ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, editMessage, cancelMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, isWaitingForReply, }: {
490
490
  presets?: SuiteAiPreset[];
491
491
  emptyState?: ReactNode;
492
492
  title?: string;
@@ -505,6 +505,19 @@ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessa
505
505
  messages: SuiteAiChatMessage[];
506
506
  reply: string;
507
507
  }>;
508
+ /** Rewrite the thread from a user message. Falls back to sendMessage. */
509
+ editMessage?: (params: {
510
+ index: number;
511
+ prompt: string;
512
+ signal?: AbortSignal;
513
+ }) => Promise<{
514
+ messages: SuiteAiChatMessage[];
515
+ reply: string;
516
+ }>;
517
+ /** Stop the in-flight reply on the server. */
518
+ cancelMessage?: () => Promise<{
519
+ messages?: SuiteAiChatMessage[];
520
+ }>;
508
521
  clearChat: () => Promise<void>;
509
522
  brandIcon?: ComponentType<{
510
523
  className?: string;
package/dist/suite.d.ts CHANGED
@@ -486,7 +486,7 @@ interface SuiteAiChatMessage {
486
486
  * Suite-wide Ask AI chat — Notion-style continuous thread.
487
487
  * Lives in the sidebar (or any parent that mounts it). Not Kraken Deep Research.
488
488
  */
489
- declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, isWaitingForReply, }: {
489
+ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessage, editMessage, cancelMessage, clearChat, brandIcon: BrandIcon, spinner: Spinner, open: openProp, onOpenChange, suiteAppBases, onSwitchApp, onSameAppNavigate, resolveAction, isWaitingForReply, }: {
490
490
  presets?: SuiteAiPreset[];
491
491
  emptyState?: ReactNode;
492
492
  title?: string;
@@ -505,6 +505,19 @@ declare function SuiteAiPanel({ presets, emptyState, title, fetchChat, sendMessa
505
505
  messages: SuiteAiChatMessage[];
506
506
  reply: string;
507
507
  }>;
508
+ /** Rewrite the thread from a user message. Falls back to sendMessage. */
509
+ editMessage?: (params: {
510
+ index: number;
511
+ prompt: string;
512
+ signal?: AbortSignal;
513
+ }) => Promise<{
514
+ messages: SuiteAiChatMessage[];
515
+ reply: string;
516
+ }>;
517
+ /** Stop the in-flight reply on the server. */
518
+ cancelMessage?: () => Promise<{
519
+ messages?: SuiteAiChatMessage[];
520
+ }>;
508
521
  clearChat: () => Promise<void>;
509
522
  brandIcon?: ComponentType<{
510
523
  className?: string;
package/dist/suite.js CHANGED
@@ -3493,6 +3493,8 @@ function SuiteAiPanel({
3493
3493
  title = "Shelly AI",
3494
3494
  fetchChat,
3495
3495
  sendMessage,
3496
+ editMessage,
3497
+ cancelMessage,
3496
3498
  clearChat,
3497
3499
  brandIcon: BrandIcon,
3498
3500
  spinner: Spinner,
@@ -3526,6 +3528,9 @@ function SuiteAiPanel({
3526
3528
  const textareaRef = (0, import_react12.useRef)(null);
3527
3529
  const abortRef = (0, import_react12.useRef)(null);
3528
3530
  const pendingPromptRef = (0, import_react12.useRef)(null);
3531
+ const [editingIndex, setEditingIndex] = (0, import_react12.useState)(null);
3532
+ const [editDraft, setEditDraft] = (0, import_react12.useState)("");
3533
+ const [copiedIndex, setCopiedIndex] = (0, import_react12.useState)(null);
3529
3534
  const waiting = loading || isWaitingForReply;
3530
3535
  (0, import_react12.useEffect)(() => {
3531
3536
  function onOpen(event) {
@@ -3652,6 +3657,11 @@ function SuiteAiPanel({
3652
3657
  (_a = abortRef.current) == null ? void 0 : _a.abort();
3653
3658
  abortRef.current = null;
3654
3659
  setLoading(false);
3660
+ if (!cancelMessage) return;
3661
+ void cancelMessage().then((result) => {
3662
+ if (result == null ? void 0 : result.messages) setMessages(result.messages);
3663
+ }).catch(() => {
3664
+ });
3655
3665
  }
3656
3666
  function retry() {
3657
3667
  var _a;
@@ -3660,6 +3670,55 @@ function SuiteAiPanel({
3660
3670
  if (lastUser) void ask(lastUser);
3661
3671
  else void hydrate();
3662
3672
  }
3673
+ async function saveEdit(index) {
3674
+ var _a;
3675
+ const trimmed = editDraft.trim();
3676
+ if (!trimmed || waiting) return;
3677
+ setEditingIndex(null);
3678
+ setEditDraft("");
3679
+ setError("");
3680
+ setAtBottom(true);
3681
+ setMessages((prev) => prev.slice(0, index));
3682
+ const controller = new AbortController();
3683
+ abortRef.current = controller;
3684
+ setLoading(true);
3685
+ try {
3686
+ const result = editMessage ? await editMessage({
3687
+ index,
3688
+ prompt: trimmed,
3689
+ signal: controller.signal
3690
+ }) : await sendMessage({ prompt: trimmed, signal: controller.signal });
3691
+ if (controller.signal.aborted) return;
3692
+ const nextMessages = (_a = result.messages) != null ? _a : [];
3693
+ setMessages(nextMessages);
3694
+ notifySuiteMutated(
3695
+ nextMessages.flatMap((message) => {
3696
+ var _a2;
3697
+ return (_a2 = message.actions) != null ? _a2 : [];
3698
+ })
3699
+ );
3700
+ } catch (err) {
3701
+ if (controller.signal.aborted) return;
3702
+ setError(
3703
+ err instanceof Error ? err.message : "The AI request failed. Try again."
3704
+ );
3705
+ void hydrate();
3706
+ } finally {
3707
+ if (abortRef.current === controller) abortRef.current = null;
3708
+ setLoading(false);
3709
+ }
3710
+ }
3711
+ async function handleCopy(index, content) {
3712
+ try {
3713
+ await navigator.clipboard.writeText(content);
3714
+ setCopiedIndex(index);
3715
+ window.setTimeout(
3716
+ () => setCopiedIndex((current) => current === index ? null : current),
3717
+ 1500
3718
+ );
3719
+ } catch (e) {
3720
+ }
3721
+ }
3663
3722
  async function handleClear() {
3664
3723
  if (loading) return;
3665
3724
  try {
@@ -3792,12 +3851,99 @@ function SuiteAiPanel({
3792
3851
  var _a;
3793
3852
  const isUser = message.role === "user";
3794
3853
  if (isUser) {
3854
+ if (editingIndex === index) {
3855
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3856
+ "div",
3857
+ {
3858
+ className: "suite-msg-in suite-msg-in--user flex justify-end",
3859
+ "data-test": "ask-ai-user",
3860
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "w-full max-w-[85%] space-y-2", children: [
3861
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3862
+ "textarea",
3863
+ {
3864
+ autoFocus: true,
3865
+ value: editDraft,
3866
+ onChange: (event) => setEditDraft(event.target.value),
3867
+ onKeyDown: (event) => {
3868
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
3869
+ event.preventDefault();
3870
+ void saveEdit(index);
3871
+ }
3872
+ if (event.key === "Escape") {
3873
+ setEditingIndex(null);
3874
+ setEditDraft("");
3875
+ }
3876
+ },
3877
+ rows: 2,
3878
+ "data-test": "ask-ai-edit-input",
3879
+ className: "w-full resize-none rounded-2xl rounded-br-md border border-ph-border bg-ph-surface px-3 py-2 text-sm leading-relaxed text-ph-ink outline-none focus-visible:border-ph-brand/45 focus-visible:ring-2 focus-visible:ring-ph-focus/35"
3880
+ }
3881
+ ),
3882
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex justify-end gap-2", children: [
3883
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3884
+ "button",
3885
+ {
3886
+ type: "button",
3887
+ onClick: () => {
3888
+ setEditingIndex(null);
3889
+ setEditDraft("");
3890
+ },
3891
+ 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",
3892
+ children: "Cancel"
3893
+ }
3894
+ ),
3895
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3896
+ "button",
3897
+ {
3898
+ type: "button",
3899
+ "data-test": "ask-ai-edit-save",
3900
+ disabled: !editDraft.trim() || waiting,
3901
+ onClick: () => void saveEdit(index),
3902
+ 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",
3903
+ children: "Save"
3904
+ }
3905
+ )
3906
+ ] })
3907
+ ] })
3908
+ },
3909
+ `user-${index}`
3910
+ );
3911
+ }
3795
3912
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3796
3913
  "div",
3797
3914
  {
3798
3915
  className: "suite-msg-in suite-msg-in--user flex justify-end",
3799
3916
  "data-test": "ask-ai-user",
3800
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]", children: message.content })
3917
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "group flex items-end gap-1.5", children: [
3918
+ !waiting ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "mb-0.5 flex items-center gap-0.5 opacity-100 transition-opacity [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-focus-within:opacity-100 [@media(hover:hover)]:group-hover:opacity-100", children: [
3919
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3920
+ "button",
3921
+ {
3922
+ type: "button",
3923
+ onClick: () => void handleCopy(index, message.content),
3924
+ "aria-label": copiedIndex === index ? "Copied" : "Copy message",
3925
+ "data-test": "ask-ai-copy",
3926
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus",
3927
+ children: copiedIndex === index ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_iconoir_react8.Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_iconoir_react8.Copy, { className: "h-3.5 w-3.5" })
3928
+ }
3929
+ ),
3930
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3931
+ "button",
3932
+ {
3933
+ type: "button",
3934
+ onClick: () => {
3935
+ setEditDraft(message.content);
3936
+ setEditingIndex(index);
3937
+ },
3938
+ "aria-label": "Edit message",
3939
+ "data-test": "ask-ai-edit",
3940
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus",
3941
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_iconoir_react8.EditPencil, { className: "h-3.5 w-3.5" })
3942
+ }
3943
+ )
3944
+ ] }) : null,
3945
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]", children: message.content })
3946
+ ] })
3801
3947
  },
3802
3948
  `user-${index}`
3803
3949
  );
package/dist/suite.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  Toggle,
13
13
  Tooltip,
14
14
  cn
15
- } from "./chunk-2EO5VCXP.mjs";
15
+ } from "./chunk-6SVJAU5K.mjs";
16
16
  import {
17
17
  SUITE_THEME_CHANNEL,
18
18
  SUITE_THEME_COOKIE,
@@ -2390,6 +2390,9 @@ import {
2390
2390
  import {
2391
2391
  ArrowDown,
2392
2392
  ArrowUp,
2393
+ Check,
2394
+ Copy,
2395
+ EditPencil,
2393
2396
  Erase,
2394
2397
  Sparks,
2395
2398
  Square,
@@ -2470,6 +2473,8 @@ function SuiteAiPanel({
2470
2473
  title = "Shelly AI",
2471
2474
  fetchChat,
2472
2475
  sendMessage,
2476
+ editMessage,
2477
+ cancelMessage,
2473
2478
  clearChat,
2474
2479
  brandIcon: BrandIcon,
2475
2480
  spinner: Spinner,
@@ -2503,6 +2508,9 @@ function SuiteAiPanel({
2503
2508
  const textareaRef = useRef3(null);
2504
2509
  const abortRef = useRef3(null);
2505
2510
  const pendingPromptRef = useRef3(null);
2511
+ const [editingIndex, setEditingIndex] = useState8(null);
2512
+ const [editDraft, setEditDraft] = useState8("");
2513
+ const [copiedIndex, setCopiedIndex] = useState8(null);
2506
2514
  const waiting = loading || isWaitingForReply;
2507
2515
  useEffect7(() => {
2508
2516
  function onOpen(event) {
@@ -2629,6 +2637,11 @@ function SuiteAiPanel({
2629
2637
  (_a = abortRef.current) == null ? void 0 : _a.abort();
2630
2638
  abortRef.current = null;
2631
2639
  setLoading(false);
2640
+ if (!cancelMessage) return;
2641
+ void cancelMessage().then((result) => {
2642
+ if (result == null ? void 0 : result.messages) setMessages(result.messages);
2643
+ }).catch(() => {
2644
+ });
2632
2645
  }
2633
2646
  function retry() {
2634
2647
  var _a;
@@ -2637,6 +2650,55 @@ function SuiteAiPanel({
2637
2650
  if (lastUser) void ask(lastUser);
2638
2651
  else void hydrate();
2639
2652
  }
2653
+ async function saveEdit(index) {
2654
+ var _a;
2655
+ const trimmed = editDraft.trim();
2656
+ if (!trimmed || waiting) return;
2657
+ setEditingIndex(null);
2658
+ setEditDraft("");
2659
+ setError("");
2660
+ setAtBottom(true);
2661
+ setMessages((prev) => prev.slice(0, index));
2662
+ const controller = new AbortController();
2663
+ abortRef.current = controller;
2664
+ setLoading(true);
2665
+ try {
2666
+ const result = editMessage ? await editMessage({
2667
+ index,
2668
+ prompt: trimmed,
2669
+ signal: controller.signal
2670
+ }) : await sendMessage({ prompt: trimmed, signal: controller.signal });
2671
+ if (controller.signal.aborted) return;
2672
+ const nextMessages = (_a = result.messages) != null ? _a : [];
2673
+ setMessages(nextMessages);
2674
+ notifySuiteMutated(
2675
+ nextMessages.flatMap((message) => {
2676
+ var _a2;
2677
+ return (_a2 = message.actions) != null ? _a2 : [];
2678
+ })
2679
+ );
2680
+ } catch (err) {
2681
+ if (controller.signal.aborted) return;
2682
+ setError(
2683
+ err instanceof Error ? err.message : "The AI request failed. Try again."
2684
+ );
2685
+ void hydrate();
2686
+ } finally {
2687
+ if (abortRef.current === controller) abortRef.current = null;
2688
+ setLoading(false);
2689
+ }
2690
+ }
2691
+ async function handleCopy(index, content) {
2692
+ try {
2693
+ await navigator.clipboard.writeText(content);
2694
+ setCopiedIndex(index);
2695
+ window.setTimeout(
2696
+ () => setCopiedIndex((current) => current === index ? null : current),
2697
+ 1500
2698
+ );
2699
+ } catch (e) {
2700
+ }
2701
+ }
2640
2702
  async function handleClear() {
2641
2703
  if (loading) return;
2642
2704
  try {
@@ -2769,12 +2831,99 @@ function SuiteAiPanel({
2769
2831
  var _a;
2770
2832
  const isUser = message.role === "user";
2771
2833
  if (isUser) {
2834
+ if (editingIndex === index) {
2835
+ return /* @__PURE__ */ jsx17(
2836
+ "div",
2837
+ {
2838
+ className: "suite-msg-in suite-msg-in--user flex justify-end",
2839
+ "data-test": "ask-ai-user",
2840
+ children: /* @__PURE__ */ jsxs14("div", { className: "w-full max-w-[85%] space-y-2", children: [
2841
+ /* @__PURE__ */ jsx17(
2842
+ "textarea",
2843
+ {
2844
+ autoFocus: true,
2845
+ value: editDraft,
2846
+ onChange: (event) => setEditDraft(event.target.value),
2847
+ onKeyDown: (event) => {
2848
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
2849
+ event.preventDefault();
2850
+ void saveEdit(index);
2851
+ }
2852
+ if (event.key === "Escape") {
2853
+ setEditingIndex(null);
2854
+ setEditDraft("");
2855
+ }
2856
+ },
2857
+ rows: 2,
2858
+ "data-test": "ask-ai-edit-input",
2859
+ className: "w-full resize-none rounded-2xl rounded-br-md border border-ph-border bg-ph-surface px-3 py-2 text-sm leading-relaxed text-ph-ink outline-none focus-visible:border-ph-brand/45 focus-visible:ring-2 focus-visible:ring-ph-focus/35"
2860
+ }
2861
+ ),
2862
+ /* @__PURE__ */ jsxs14("div", { className: "flex justify-end gap-2", children: [
2863
+ /* @__PURE__ */ jsx17(
2864
+ "button",
2865
+ {
2866
+ type: "button",
2867
+ onClick: () => {
2868
+ setEditingIndex(null);
2869
+ setEditDraft("");
2870
+ },
2871
+ 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",
2872
+ children: "Cancel"
2873
+ }
2874
+ ),
2875
+ /* @__PURE__ */ jsx17(
2876
+ "button",
2877
+ {
2878
+ type: "button",
2879
+ "data-test": "ask-ai-edit-save",
2880
+ disabled: !editDraft.trim() || waiting,
2881
+ onClick: () => void saveEdit(index),
2882
+ 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",
2883
+ children: "Save"
2884
+ }
2885
+ )
2886
+ ] })
2887
+ ] })
2888
+ },
2889
+ `user-${index}`
2890
+ );
2891
+ }
2772
2892
  return /* @__PURE__ */ jsx17(
2773
2893
  "div",
2774
2894
  {
2775
2895
  className: "suite-msg-in suite-msg-in--user flex justify-end",
2776
2896
  "data-test": "ask-ai-user",
2777
- children: /* @__PURE__ */ jsx17("div", { className: "w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]", children: message.content })
2897
+ children: /* @__PURE__ */ jsxs14("div", { className: "group flex items-end gap-1.5", children: [
2898
+ !waiting ? /* @__PURE__ */ jsxs14("div", { className: "mb-0.5 flex items-center gap-0.5 opacity-100 transition-opacity [@media(hover:hover)]:opacity-0 [@media(hover:hover)]:group-focus-within:opacity-100 [@media(hover:hover)]:group-hover:opacity-100", children: [
2899
+ /* @__PURE__ */ jsx17(
2900
+ "button",
2901
+ {
2902
+ type: "button",
2903
+ onClick: () => void handleCopy(index, message.content),
2904
+ "aria-label": copiedIndex === index ? "Copied" : "Copy message",
2905
+ "data-test": "ask-ai-copy",
2906
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus",
2907
+ children: copiedIndex === index ? /* @__PURE__ */ jsx17(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx17(Copy, { className: "h-3.5 w-3.5" })
2908
+ }
2909
+ ),
2910
+ /* @__PURE__ */ jsx17(
2911
+ "button",
2912
+ {
2913
+ type: "button",
2914
+ onClick: () => {
2915
+ setEditDraft(message.content);
2916
+ setEditingIndex(index);
2917
+ },
2918
+ "aria-label": "Edit message",
2919
+ "data-test": "ask-ai-edit",
2920
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-focus",
2921
+ children: /* @__PURE__ */ jsx17(EditPencil, { className: "h-3.5 w-3.5" })
2922
+ }
2923
+ )
2924
+ ] }) : null,
2925
+ /* @__PURE__ */ jsx17("div", { className: "w-fit max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-ph-brand px-3 py-2 text-sm leading-relaxed text-[var(--ph-on-accent)] [overflow-wrap:anywhere]", children: message.content })
2926
+ ] })
2778
2927
  },
2779
2928
  `user-${index}`
2780
2929
  );
@@ -2977,7 +3126,7 @@ function SuiteAiPanel({
2977
3126
 
2978
3127
  // src/suite/components/workspace-switcher.tsx
2979
3128
  import { useState as useState9 } from "react";
2980
- import { Building, Check, NavArrowDown } from "iconoir-react";
3129
+ import { Building, Check as Check2, NavArrowDown } from "iconoir-react";
2981
3130
  import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
2982
3131
  function SuiteWorkspaceSwitcher({
2983
3132
  workspaces = [],
@@ -3062,7 +3211,7 @@ function SuiteWorkspaceSwitcher({
3062
3211
  workspace.description ? /* @__PURE__ */ jsx18("span", { className: "block truncate text-xs text-ph-mutedtext", children: workspace.description }) : null
3063
3212
  ] }),
3064
3213
  workspace.id === currentId ? /* @__PURE__ */ jsx18(
3065
- Check,
3214
+ Check2,
3066
3215
  {
3067
3216
  className: "h-4 w-4 shrink-0 text-ph-brand",
3068
3217
  "aria-hidden": "true"