@xenide-io/the-old-ui-theme 0.9.8 → 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.
- package/dist/suite.d.mts +14 -1
- package/dist/suite.d.ts +14 -1
- package/dist/suite.js +147 -1
- package/dist/suite.mjs +152 -3
- package/package.json +1 -1
- package/src/suite/components/ai-panel.tsx +166 -2
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.
|
|
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
|
@@ -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__ */
|
|
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
|
-
|
|
3214
|
+
Check2,
|
|
3066
3215
|
{
|
|
3067
3216
|
className: "h-4 w-4 shrink-0 text-ph-brand",
|
|
3068
3217
|
"aria-hidden": "true"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenide-io/the-old-ui-theme",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "Props-driven React components and theme tokens inspired by classic interface design — optimized for Next.js apps and internal tools.",
|
|
5
5
|
"author": "Xenide",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
ArrowDown,
|
|
18
18
|
ArrowUp,
|
|
19
|
+
Check,
|
|
20
|
+
Copy,
|
|
21
|
+
EditPencil,
|
|
19
22
|
Erase,
|
|
20
23
|
Sparks,
|
|
21
24
|
Square,
|
|
@@ -157,6 +160,8 @@ export function SuiteAiPanel({
|
|
|
157
160
|
title = "Shelly AI",
|
|
158
161
|
fetchChat,
|
|
159
162
|
sendMessage,
|
|
163
|
+
editMessage,
|
|
164
|
+
cancelMessage,
|
|
160
165
|
clearChat,
|
|
161
166
|
brandIcon: BrandIcon,
|
|
162
167
|
spinner: Spinner,
|
|
@@ -180,6 +185,14 @@ export function SuiteAiPanel({
|
|
|
180
185
|
prompt: string;
|
|
181
186
|
signal?: AbortSignal;
|
|
182
187
|
}) => Promise<{ messages: SuiteAiChatMessage[]; reply: string }>;
|
|
188
|
+
/** Rewrite the thread from a user message. Falls back to sendMessage. */
|
|
189
|
+
editMessage?: (params: {
|
|
190
|
+
index: number;
|
|
191
|
+
prompt: string;
|
|
192
|
+
signal?: AbortSignal;
|
|
193
|
+
}) => Promise<{ messages: SuiteAiChatMessage[]; reply: string }>;
|
|
194
|
+
/** Stop the in-flight reply on the server. */
|
|
195
|
+
cancelMessage?: () => Promise<{ messages?: SuiteAiChatMessage[] }>;
|
|
183
196
|
clearChat: () => Promise<void>;
|
|
184
197
|
brandIcon?: ComponentType<{ className?: string }>;
|
|
185
198
|
spinner: ComponentType<{ className?: string }>;
|
|
@@ -220,6 +233,9 @@ export function SuiteAiPanel({
|
|
|
220
233
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
221
234
|
const abortRef = useRef<AbortController | null>(null);
|
|
222
235
|
const pendingPromptRef = useRef<string | null>(null);
|
|
236
|
+
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
|
237
|
+
const [editDraft, setEditDraft] = useState("");
|
|
238
|
+
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
|
223
239
|
const waiting = loading || isWaitingForReply;
|
|
224
240
|
|
|
225
241
|
useEffect(() => {
|
|
@@ -356,6 +372,14 @@ export function SuiteAiPanel({
|
|
|
356
372
|
abortRef.current?.abort();
|
|
357
373
|
abortRef.current = null;
|
|
358
374
|
setLoading(false);
|
|
375
|
+
if (!cancelMessage) return;
|
|
376
|
+
void cancelMessage()
|
|
377
|
+
.then((result) => {
|
|
378
|
+
if (result?.messages) setMessages(result.messages);
|
|
379
|
+
})
|
|
380
|
+
.catch(() => {
|
|
381
|
+
// Best-effort server cancel; the local abort already stopped the UI.
|
|
382
|
+
});
|
|
359
383
|
}
|
|
360
384
|
|
|
361
385
|
function retry() {
|
|
@@ -367,6 +391,58 @@ export function SuiteAiPanel({
|
|
|
367
391
|
else void hydrate();
|
|
368
392
|
}
|
|
369
393
|
|
|
394
|
+
async function saveEdit(index: number) {
|
|
395
|
+
const trimmed = editDraft.trim();
|
|
396
|
+
if (!trimmed || waiting) return;
|
|
397
|
+
setEditingIndex(null);
|
|
398
|
+
setEditDraft("");
|
|
399
|
+
setError("");
|
|
400
|
+
setAtBottom(true);
|
|
401
|
+
setMessages((prev) => prev.slice(0, index));
|
|
402
|
+
const controller = new AbortController();
|
|
403
|
+
abortRef.current = controller;
|
|
404
|
+
setLoading(true);
|
|
405
|
+
try {
|
|
406
|
+
const result = editMessage
|
|
407
|
+
? await editMessage({
|
|
408
|
+
index,
|
|
409
|
+
prompt: trimmed,
|
|
410
|
+
signal: controller.signal,
|
|
411
|
+
})
|
|
412
|
+
: await sendMessage({ prompt: trimmed, signal: controller.signal });
|
|
413
|
+
if (controller.signal.aborted) return;
|
|
414
|
+
const nextMessages = result.messages ?? [];
|
|
415
|
+
setMessages(nextMessages);
|
|
416
|
+
notifySuiteMutated(
|
|
417
|
+
nextMessages.flatMap((message) => message.actions ?? []),
|
|
418
|
+
);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
if (controller.signal.aborted) return;
|
|
421
|
+
setError(
|
|
422
|
+
err instanceof Error
|
|
423
|
+
? err.message
|
|
424
|
+
: "The AI request failed. Try again.",
|
|
425
|
+
);
|
|
426
|
+
void hydrate();
|
|
427
|
+
} finally {
|
|
428
|
+
if (abortRef.current === controller) abortRef.current = null;
|
|
429
|
+
setLoading(false);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function handleCopy(index: number, content: string) {
|
|
434
|
+
try {
|
|
435
|
+
await navigator.clipboard.writeText(content);
|
|
436
|
+
setCopiedIndex(index);
|
|
437
|
+
window.setTimeout(
|
|
438
|
+
() => setCopiedIndex((current) => (current === index ? null : current)),
|
|
439
|
+
1500,
|
|
440
|
+
);
|
|
441
|
+
} catch {
|
|
442
|
+
// clipboard unavailable
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
370
446
|
async function handleClear() {
|
|
371
447
|
if (loading) return;
|
|
372
448
|
try {
|
|
@@ -516,14 +592,102 @@ export function SuiteAiPanel({
|
|
|
516
592
|
{messages.map((message, index) => {
|
|
517
593
|
const isUser = message.role === "user";
|
|
518
594
|
if (isUser) {
|
|
595
|
+
if (editingIndex === index) {
|
|
596
|
+
return (
|
|
597
|
+
<div
|
|
598
|
+
key={`user-${index}`}
|
|
599
|
+
className="suite-msg-in suite-msg-in--user flex justify-end"
|
|
600
|
+
data-test="ask-ai-user"
|
|
601
|
+
>
|
|
602
|
+
<div className="w-full max-w-[85%] space-y-2">
|
|
603
|
+
<textarea
|
|
604
|
+
autoFocus
|
|
605
|
+
value={editDraft}
|
|
606
|
+
onChange={(event) => setEditDraft(event.target.value)}
|
|
607
|
+
onKeyDown={(event) => {
|
|
608
|
+
if (
|
|
609
|
+
event.key === "Enter" &&
|
|
610
|
+
!event.shiftKey &&
|
|
611
|
+
!event.nativeEvent.isComposing
|
|
612
|
+
) {
|
|
613
|
+
event.preventDefault();
|
|
614
|
+
void saveEdit(index);
|
|
615
|
+
}
|
|
616
|
+
if (event.key === "Escape") {
|
|
617
|
+
setEditingIndex(null);
|
|
618
|
+
setEditDraft("");
|
|
619
|
+
}
|
|
620
|
+
}}
|
|
621
|
+
rows={2}
|
|
622
|
+
data-test="ask-ai-edit-input"
|
|
623
|
+
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"
|
|
624
|
+
/>
|
|
625
|
+
<div className="flex justify-end gap-2">
|
|
626
|
+
<button
|
|
627
|
+
type="button"
|
|
628
|
+
onClick={() => {
|
|
629
|
+
setEditingIndex(null);
|
|
630
|
+
setEditDraft("");
|
|
631
|
+
}}
|
|
632
|
+
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"
|
|
633
|
+
>
|
|
634
|
+
Cancel
|
|
635
|
+
</button>
|
|
636
|
+
<button
|
|
637
|
+
type="button"
|
|
638
|
+
data-test="ask-ai-edit-save"
|
|
639
|
+
disabled={!editDraft.trim() || waiting}
|
|
640
|
+
onClick={() => void saveEdit(index)}
|
|
641
|
+
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"
|
|
642
|
+
>
|
|
643
|
+
Save
|
|
644
|
+
</button>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
);
|
|
649
|
+
}
|
|
519
650
|
return (
|
|
520
651
|
<div
|
|
521
652
|
key={`user-${index}`}
|
|
522
653
|
className="suite-msg-in suite-msg-in--user flex justify-end"
|
|
523
654
|
data-test="ask-ai-user"
|
|
524
655
|
>
|
|
525
|
-
<div className="
|
|
526
|
-
{
|
|
656
|
+
<div className="group flex items-end gap-1.5">
|
|
657
|
+
{!waiting ? (
|
|
658
|
+
<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">
|
|
659
|
+
<button
|
|
660
|
+
type="button"
|
|
661
|
+
onClick={() => void handleCopy(index, message.content)}
|
|
662
|
+
aria-label={
|
|
663
|
+
copiedIndex === index ? "Copied" : "Copy message"
|
|
664
|
+
}
|
|
665
|
+
data-test="ask-ai-copy"
|
|
666
|
+
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"
|
|
667
|
+
>
|
|
668
|
+
{copiedIndex === index ? (
|
|
669
|
+
<Check className="h-3.5 w-3.5" />
|
|
670
|
+
) : (
|
|
671
|
+
<Copy className="h-3.5 w-3.5" />
|
|
672
|
+
)}
|
|
673
|
+
</button>
|
|
674
|
+
<button
|
|
675
|
+
type="button"
|
|
676
|
+
onClick={() => {
|
|
677
|
+
setEditDraft(message.content);
|
|
678
|
+
setEditingIndex(index);
|
|
679
|
+
}}
|
|
680
|
+
aria-label="Edit message"
|
|
681
|
+
data-test="ask-ai-edit"
|
|
682
|
+
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"
|
|
683
|
+
>
|
|
684
|
+
<EditPencil className="h-3.5 w-3.5" />
|
|
685
|
+
</button>
|
|
686
|
+
</div>
|
|
687
|
+
) : null}
|
|
688
|
+
<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]">
|
|
689
|
+
{message.content}
|
|
690
|
+
</div>
|
|
527
691
|
</div>
|
|
528
692
|
</div>
|
|
529
693
|
);
|