@ewjdev/anyclick-react 3.0.0 → 4.0.0
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/chunk-7KW4HSJM.mjs +35 -0
- package/dist/chunk-7KW4HSJM.mjs.map +1 -0
- package/dist/chunk-YBHJXEH6.mjs +305 -0
- package/dist/chunk-YBHJXEH6.mjs.map +1 -0
- package/dist/index.css +23 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +55 -18
- package/dist/index.d.ts +55 -18
- package/dist/index.js +1077 -485
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +942 -352
- package/dist/index.mjs.map +1 -1
- package/dist/token-EXBG54PO.mjs +66 -0
- package/dist/token-EXBG54PO.mjs.map +1 -0
- package/dist/token-util-4XOQ3GFX.mjs +6 -0
- package/dist/token-util-4XOQ3GFX.mjs.map +1 -0
- package/package.json +16 -5
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useEffect as useEffect4,
|
|
7
7
|
useId,
|
|
8
8
|
useLayoutEffect,
|
|
9
|
-
useMemo as
|
|
9
|
+
useMemo as useMemo4,
|
|
10
10
|
useRef as useRef4,
|
|
11
11
|
useState as useState5
|
|
12
12
|
} from "react";
|
|
@@ -34,10 +34,11 @@ import {
|
|
|
34
34
|
import React, {
|
|
35
35
|
useCallback as useCallback2,
|
|
36
36
|
useEffect as useEffect2,
|
|
37
|
-
useMemo,
|
|
37
|
+
useMemo as useMemo2,
|
|
38
38
|
useRef as useRef2,
|
|
39
39
|
useState as useState2
|
|
40
40
|
} from "react";
|
|
41
|
+
import { buildAnyclickPayload } from "@ewjdev/anyclick-core";
|
|
41
42
|
import {
|
|
42
43
|
AlertCircle,
|
|
43
44
|
ChevronDown,
|
|
@@ -231,6 +232,7 @@ var quickChatStyles = {
|
|
|
231
232
|
// Suggestions - compact horizontal scroll
|
|
232
233
|
suggestionsContainer: {
|
|
233
234
|
display: "flex",
|
|
235
|
+
flexDirection: "column",
|
|
234
236
|
gap: "6px",
|
|
235
237
|
padding: "6px 8px",
|
|
236
238
|
overflowX: "auto",
|
|
@@ -493,8 +495,131 @@ var quickChatKeyframes = `
|
|
|
493
495
|
`;
|
|
494
496
|
|
|
495
497
|
// src/QuickChat/useQuickChat.ts
|
|
496
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
497
|
-
import {
|
|
498
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
499
|
+
import { useChat } from "@ai-sdk/react";
|
|
500
|
+
import { DefaultChatTransport } from "ai";
|
|
501
|
+
|
|
502
|
+
// src/QuickChat/store.ts
|
|
503
|
+
import { create } from "zustand";
|
|
504
|
+
import { createJSONStorage, persist } from "zustand/middleware";
|
|
505
|
+
var STORE_NAME = "anyclick-quick-chat-store";
|
|
506
|
+
var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
507
|
+
function generateMessageId() {
|
|
508
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
509
|
+
}
|
|
510
|
+
var storage = createJSONStorage(() => localStorage);
|
|
511
|
+
function filterExpiredMessages(messages) {
|
|
512
|
+
const now = Date.now();
|
|
513
|
+
return messages.filter((msg) => msg.expiresAt > now);
|
|
514
|
+
}
|
|
515
|
+
var useQuickChatStore = create()(
|
|
516
|
+
persist(
|
|
517
|
+
(set, get) => ({
|
|
518
|
+
messages: [],
|
|
519
|
+
isPinned: false,
|
|
520
|
+
input: "",
|
|
521
|
+
contextChunks: [],
|
|
522
|
+
suggestedPrompts: [],
|
|
523
|
+
isLoadingSuggestions: false,
|
|
524
|
+
isSending: false,
|
|
525
|
+
isStreaming: false,
|
|
526
|
+
error: null,
|
|
527
|
+
lastSyncedAt: null,
|
|
528
|
+
setInput: (input) => set({ input }),
|
|
529
|
+
addMessage: (message) => {
|
|
530
|
+
const id = generateMessageId();
|
|
531
|
+
const now = Date.now();
|
|
532
|
+
const storedMessage = {
|
|
533
|
+
...message,
|
|
534
|
+
id,
|
|
535
|
+
timestamp: now,
|
|
536
|
+
expiresAt: now + TWENTY_FOUR_HOURS_MS
|
|
537
|
+
};
|
|
538
|
+
set((state) => {
|
|
539
|
+
const newMessages = [...state.messages, storedMessage];
|
|
540
|
+
return {
|
|
541
|
+
messages: newMessages
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
return id;
|
|
545
|
+
},
|
|
546
|
+
updateMessage: (id, updates) => {
|
|
547
|
+
set((state) => ({
|
|
548
|
+
messages: state.messages.map(
|
|
549
|
+
(msg) => msg.id === id ? { ...msg, ...updates } : msg
|
|
550
|
+
)
|
|
551
|
+
}));
|
|
552
|
+
},
|
|
553
|
+
clearMessages: () => set({ messages: [], error: null }),
|
|
554
|
+
setIsPinned: (pinned) => set({ isPinned: pinned }),
|
|
555
|
+
setContextChunks: (chunks) => set({ contextChunks: chunks }),
|
|
556
|
+
toggleChunk: (chunkId) => {
|
|
557
|
+
set((state) => ({
|
|
558
|
+
contextChunks: state.contextChunks.map(
|
|
559
|
+
(chunk) => chunk.id === chunkId ? { ...chunk, included: !chunk.included } : chunk
|
|
560
|
+
)
|
|
561
|
+
}));
|
|
562
|
+
},
|
|
563
|
+
toggleAllChunks: (included) => {
|
|
564
|
+
set((state) => ({
|
|
565
|
+
contextChunks: state.contextChunks.map((chunk) => ({
|
|
566
|
+
...chunk,
|
|
567
|
+
included
|
|
568
|
+
}))
|
|
569
|
+
}));
|
|
570
|
+
},
|
|
571
|
+
setSuggestedPrompts: (prompts) => set({ suggestedPrompts: prompts }),
|
|
572
|
+
setIsLoadingSuggestions: (loading) => set({ isLoadingSuggestions: loading }),
|
|
573
|
+
setIsSending: (sending) => set({ isSending: sending }),
|
|
574
|
+
setIsStreaming: (streaming) => set({ isStreaming: streaming }),
|
|
575
|
+
setError: (error) => set({ error }),
|
|
576
|
+
setLastSyncedAt: (timestamp) => set({ lastSyncedAt: timestamp }),
|
|
577
|
+
pruneExpiredMessages: () => {
|
|
578
|
+
set((state) => ({
|
|
579
|
+
messages: filterExpiredMessages(state.messages)
|
|
580
|
+
}));
|
|
581
|
+
},
|
|
582
|
+
getActiveMessages: () => {
|
|
583
|
+
const state = get();
|
|
584
|
+
const now = Date.now();
|
|
585
|
+
const active = state.messages.filter((msg) => msg.expiresAt > now).map(({ expiresAt, ...msg }) => msg);
|
|
586
|
+
console.log("[store] getActiveMessages", {
|
|
587
|
+
totalMessages: state.messages.length,
|
|
588
|
+
activeMessages: active.length,
|
|
589
|
+
activeIds: active.map((m) => m.id)
|
|
590
|
+
});
|
|
591
|
+
return active;
|
|
592
|
+
},
|
|
593
|
+
hydrate: (messages) => {
|
|
594
|
+
const now = Date.now();
|
|
595
|
+
const storedMessages = messages.map((msg) => ({
|
|
596
|
+
...msg,
|
|
597
|
+
expiresAt: now + TWENTY_FOUR_HOURS_MS
|
|
598
|
+
}));
|
|
599
|
+
set({ messages: storedMessages, lastSyncedAt: now });
|
|
600
|
+
}
|
|
601
|
+
}),
|
|
602
|
+
{
|
|
603
|
+
name: STORE_NAME,
|
|
604
|
+
storage,
|
|
605
|
+
partialize: (state) => ({
|
|
606
|
+
messages: state.messages,
|
|
607
|
+
isPinned: state.isPinned,
|
|
608
|
+
lastSyncedAt: state.lastSyncedAt
|
|
609
|
+
}),
|
|
610
|
+
onRehydrateStorage: () => (state) => {
|
|
611
|
+
if (state) {
|
|
612
|
+
state.pruneExpiredMessages();
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
)
|
|
617
|
+
);
|
|
618
|
+
function useActiveMessages() {
|
|
619
|
+
const messages = useQuickChatStore((state) => state.messages);
|
|
620
|
+
const now = Date.now();
|
|
621
|
+
return messages.filter((msg) => msg.expiresAt > now).map(({ expiresAt, ...msg }) => msg);
|
|
622
|
+
}
|
|
498
623
|
|
|
499
624
|
// src/QuickChat/types.ts
|
|
500
625
|
var DEFAULT_T3CHAT_CONFIG = {
|
|
@@ -504,9 +629,9 @@ var DEFAULT_T3CHAT_CONFIG = {
|
|
|
504
629
|
};
|
|
505
630
|
var DEFAULT_QUICK_CHAT_CONFIG = {
|
|
506
631
|
endpoint: "/api/anyclick/chat",
|
|
507
|
-
model: "gpt-5-
|
|
632
|
+
model: "gpt-5-nano",
|
|
508
633
|
prePassModel: "gpt-5-nano",
|
|
509
|
-
maxResponseLength:
|
|
634
|
+
maxResponseLength: 1e4,
|
|
510
635
|
showRedactionUI: true,
|
|
511
636
|
showSuggestions: true,
|
|
512
637
|
systemPrompt: "You are a helpful assistant that provides quick, concise answers about web elements and UI. Keep responses brief and actionable.",
|
|
@@ -515,57 +640,9 @@ var DEFAULT_QUICK_CHAT_CONFIG = {
|
|
|
515
640
|
t3chat: DEFAULT_T3CHAT_CONFIG
|
|
516
641
|
};
|
|
517
642
|
|
|
518
|
-
// src/QuickChat/useQuickChat.ts
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
function generateId() {
|
|
522
|
-
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
523
|
-
}
|
|
524
|
-
function getPinnedState() {
|
|
525
|
-
if (typeof window === "undefined") return false;
|
|
526
|
-
try {
|
|
527
|
-
return sessionStorage.getItem(PINNED_STATE_KEY) === "true";
|
|
528
|
-
} catch {
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
function getChatHistory() {
|
|
533
|
-
if (typeof window === "undefined") return [];
|
|
534
|
-
try {
|
|
535
|
-
const stored = sessionStorage.getItem(CHAT_HISTORY_KEY);
|
|
536
|
-
if (stored) {
|
|
537
|
-
const parsed = JSON.parse(stored);
|
|
538
|
-
return parsed.map((msg) => ({
|
|
539
|
-
...msg,
|
|
540
|
-
isStreaming: false,
|
|
541
|
-
actions: void 0
|
|
542
|
-
}));
|
|
543
|
-
}
|
|
544
|
-
} catch {
|
|
545
|
-
}
|
|
546
|
-
return [];
|
|
547
|
-
}
|
|
548
|
-
function saveChatHistory(messages) {
|
|
549
|
-
if (typeof window === "undefined") return;
|
|
550
|
-
try {
|
|
551
|
-
const toStore = messages.slice(-10).map((msg) => ({
|
|
552
|
-
id: msg.id,
|
|
553
|
-
role: msg.role,
|
|
554
|
-
content: msg.content,
|
|
555
|
-
timestamp: msg.timestamp
|
|
556
|
-
}));
|
|
557
|
-
sessionStorage.setItem(CHAT_HISTORY_KEY, JSON.stringify(toStore));
|
|
558
|
-
} catch {
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
function clearChatHistory() {
|
|
562
|
-
if (typeof window === "undefined") return;
|
|
563
|
-
try {
|
|
564
|
-
sessionStorage.removeItem(CHAT_HISTORY_KEY);
|
|
565
|
-
} catch {
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
function extractContextChunks(targetElement, containerElement) {
|
|
643
|
+
// src/QuickChat/useQuickChat.context.ts
|
|
644
|
+
import { getElementInspectInfo } from "@ewjdev/anyclick-core";
|
|
645
|
+
function extractContextChunks(targetElement, _containerElement) {
|
|
569
646
|
const chunks = [];
|
|
570
647
|
if (!targetElement) return chunks;
|
|
571
648
|
try {
|
|
@@ -592,7 +669,7 @@ function extractContextChunks(targetElement, containerElement) {
|
|
|
592
669
|
}
|
|
593
670
|
if (info.computedStyles) {
|
|
594
671
|
const styleEntries = [];
|
|
595
|
-
for (const [
|
|
672
|
+
for (const [, styles] of Object.entries(info.computedStyles)) {
|
|
596
673
|
if (styles && typeof styles === "object") {
|
|
597
674
|
const entries = Object.entries(styles).slice(0, 2);
|
|
598
675
|
for (const [k, v] of entries) {
|
|
@@ -630,7 +707,9 @@ function extractContextChunks(targetElement, containerElement) {
|
|
|
630
707
|
}
|
|
631
708
|
}
|
|
632
709
|
if (info.boxModel) {
|
|
633
|
-
const boxContent = `${Math.round(info.boxModel.content.width)}x${Math.round(
|
|
710
|
+
const boxContent = `${Math.round(info.boxModel.content.width)}x${Math.round(
|
|
711
|
+
info.boxModel.content.height
|
|
712
|
+
)}px`;
|
|
634
713
|
chunks.push({
|
|
635
714
|
id: "element-dimensions",
|
|
636
715
|
label: "Dimensions",
|
|
@@ -641,54 +720,358 @@ function extractContextChunks(targetElement, containerElement) {
|
|
|
641
720
|
});
|
|
642
721
|
}
|
|
643
722
|
} catch (error) {
|
|
644
|
-
console.error("Failed to extract context:", error);
|
|
723
|
+
console.error("[useQuickChat] Failed to extract context:", error);
|
|
645
724
|
}
|
|
646
725
|
return chunks;
|
|
647
726
|
}
|
|
648
727
|
function buildContextString(chunks) {
|
|
649
|
-
const
|
|
650
|
-
if (
|
|
651
|
-
return
|
|
728
|
+
const included = chunks.filter((c) => c.included);
|
|
729
|
+
if (included.length === 0) return "";
|
|
730
|
+
return included.map((c) => `[${c.label}]: ${c.content}`).join("\n");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// src/QuickChat/useQuickChat.debug.ts
|
|
734
|
+
function createDebugInfo(args) {
|
|
735
|
+
return {
|
|
736
|
+
status: args.status,
|
|
737
|
+
ok: args.ok,
|
|
738
|
+
contentType: args.contentType ?? null,
|
|
739
|
+
rawTextPreview: args.rawText ?? "",
|
|
740
|
+
timestamp: Date.now(),
|
|
741
|
+
error: args.error
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/QuickChat/useQuickChat.messages.ts
|
|
746
|
+
function getUIMessageText(msg) {
|
|
747
|
+
const partsText = msg.parts?.map((p) => {
|
|
748
|
+
const part = p;
|
|
749
|
+
if (typeof part.text === "string") return part.text;
|
|
750
|
+
if (typeof part.content === "string") return part.content;
|
|
751
|
+
return "";
|
|
752
|
+
}).join("") ?? "";
|
|
753
|
+
if (partsText) return partsText;
|
|
754
|
+
const maybeContent = msg.content;
|
|
755
|
+
return typeof maybeContent === "string" ? maybeContent : "";
|
|
756
|
+
}
|
|
757
|
+
function chatMessagesToUiMessages(messages) {
|
|
758
|
+
return messages.map((msg) => ({
|
|
759
|
+
id: msg.id,
|
|
760
|
+
role: msg.role,
|
|
761
|
+
parts: [{ type: "text", text: msg.content }]
|
|
762
|
+
}));
|
|
763
|
+
}
|
|
764
|
+
function safeCopyToClipboard(text) {
|
|
765
|
+
try {
|
|
766
|
+
if (typeof navigator === "undefined") return;
|
|
767
|
+
void navigator.clipboard.writeText(text);
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function buildAssistantActions(messageText, setInput) {
|
|
772
|
+
return [
|
|
773
|
+
{
|
|
774
|
+
id: "copy",
|
|
775
|
+
label: "Copy",
|
|
776
|
+
onClick: () => safeCopyToClipboard(messageText)
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
id: "research",
|
|
780
|
+
label: "Research more",
|
|
781
|
+
onClick: () => setInput(`Tell me more about: ${messageText.slice(0, 50)}`)
|
|
782
|
+
}
|
|
783
|
+
];
|
|
784
|
+
}
|
|
785
|
+
function uiMessagesToChatMessages(args) {
|
|
786
|
+
const { uiMessages, status, setInput } = args;
|
|
787
|
+
const last = uiMessages[uiMessages.length - 1];
|
|
788
|
+
return uiMessages.map((msg) => {
|
|
789
|
+
const text = getUIMessageText(msg);
|
|
790
|
+
const isStreaming = status === "streaming" && msg.role === "assistant" && msg === last;
|
|
791
|
+
const actions = msg.role === "assistant" && status === "ready" ? buildAssistantActions(text, setInput) : void 0;
|
|
792
|
+
return {
|
|
793
|
+
id: msg.id,
|
|
794
|
+
role: msg.role,
|
|
795
|
+
content: text,
|
|
796
|
+
timestamp: Date.now(),
|
|
797
|
+
isStreaming,
|
|
798
|
+
actions
|
|
799
|
+
};
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/QuickChat/useQuickChat.rateLimit.ts
|
|
804
|
+
function safeJsonParse(text) {
|
|
805
|
+
try {
|
|
806
|
+
return JSON.parse(text);
|
|
807
|
+
} catch {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function formatRetryAt(retryAtMs) {
|
|
812
|
+
try {
|
|
813
|
+
const d = new Date(retryAtMs);
|
|
814
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
815
|
+
hour: "numeric",
|
|
816
|
+
minute: "2-digit"
|
|
817
|
+
}).format(d);
|
|
818
|
+
} catch {
|
|
819
|
+
return new Date(retryAtMs).toLocaleTimeString();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function getRequestIdFromHeaders(res) {
|
|
823
|
+
return res?.headers?.get?.("X-Anyclick-Request-Id") ?? res?.headers?.get?.("x-anyclick-request-id") ?? void 0;
|
|
824
|
+
}
|
|
825
|
+
function parseRetryAfterSeconds(args) {
|
|
826
|
+
const { payload, res } = args;
|
|
827
|
+
if (typeof payload?.retryAfterSeconds === "number")
|
|
828
|
+
return payload.retryAfterSeconds;
|
|
829
|
+
const headerRetryAfter = res?.headers?.get?.("Retry-After");
|
|
830
|
+
if (!headerRetryAfter) return void 0;
|
|
831
|
+
const n = Number(headerRetryAfter);
|
|
832
|
+
return Number.isFinite(n) ? n : void 0;
|
|
652
833
|
}
|
|
834
|
+
function parseRetryAt(args) {
|
|
835
|
+
const { payload, retryAfterSeconds, nowMs } = args;
|
|
836
|
+
if (typeof payload?.retryAt === "number" && Number.isFinite(payload.retryAt)) {
|
|
837
|
+
return payload.retryAt;
|
|
838
|
+
}
|
|
839
|
+
if (typeof retryAfterSeconds === "number" && Number.isFinite(retryAfterSeconds)) {
|
|
840
|
+
return nowMs + Math.max(0, retryAfterSeconds) * 1e3;
|
|
841
|
+
}
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
function buildNotice(args) {
|
|
845
|
+
const { rawText, endpoint, res, nowMs } = args;
|
|
846
|
+
const parsed = safeJsonParse(rawText);
|
|
847
|
+
const payload = parsed && typeof parsed === "object" ? parsed : null;
|
|
848
|
+
const retryAfterSeconds = parseRetryAfterSeconds({ payload, res });
|
|
849
|
+
const retryAt = parseRetryAt({ payload, retryAfterSeconds, nowMs });
|
|
850
|
+
const timePart = retryAt ? `Try again at ${formatRetryAt(retryAt)}.` : "";
|
|
851
|
+
const message = timePart ? `Rate limited. ${timePart}` : "Rate limited.";
|
|
852
|
+
const requestId = payload?.requestId ?? getRequestIdFromHeaders(res);
|
|
853
|
+
return {
|
|
854
|
+
status: 429,
|
|
855
|
+
message,
|
|
856
|
+
retryAt,
|
|
857
|
+
retryAfterSeconds,
|
|
858
|
+
requestId,
|
|
859
|
+
endpoint,
|
|
860
|
+
raw: rawText
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
async function rateLimitNoticeFromResponse(res, endpoint) {
|
|
864
|
+
if (res.status !== 429) return null;
|
|
865
|
+
const raw = await res.text().catch(() => "");
|
|
866
|
+
return buildNotice({ rawText: raw, endpoint, res, nowMs: Date.now() });
|
|
867
|
+
}
|
|
868
|
+
function rateLimitNoticeFromError(args) {
|
|
869
|
+
const { statusCode, response, responseText, endpoint } = args;
|
|
870
|
+
if (statusCode !== 429) return null;
|
|
871
|
+
const raw = responseText ?? "";
|
|
872
|
+
return buildNotice({
|
|
873
|
+
rawText: raw,
|
|
874
|
+
endpoint,
|
|
875
|
+
res: response,
|
|
876
|
+
nowMs: Date.now()
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/QuickChat/useQuickChat.ts
|
|
653
881
|
function useQuickChat(targetElement, containerElement, config = {}) {
|
|
882
|
+
console.count("useQuickChat");
|
|
654
883
|
const mergedConfig = { ...DEFAULT_QUICK_CHAT_CONFIG, ...config };
|
|
655
|
-
const abortControllerRef = useRef(null);
|
|
656
884
|
const initializedRef = useRef(false);
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
885
|
+
const syncTimeoutRef = useRef(null);
|
|
886
|
+
const [manualSending, setManualSending] = useState(false);
|
|
887
|
+
const [debugInfo, setDebugInfo] = useState(null);
|
|
888
|
+
const [rateLimitNotice, setRateLimitNotice] = useState(null);
|
|
889
|
+
const {
|
|
890
|
+
input,
|
|
891
|
+
setInput,
|
|
892
|
+
isPinned,
|
|
893
|
+
setIsPinned,
|
|
894
|
+
contextChunks,
|
|
895
|
+
setContextChunks,
|
|
896
|
+
toggleChunk,
|
|
897
|
+
toggleAllChunks,
|
|
898
|
+
suggestedPrompts,
|
|
899
|
+
setSuggestedPrompts,
|
|
900
|
+
isLoadingSuggestions,
|
|
901
|
+
setIsLoadingSuggestions,
|
|
902
|
+
error,
|
|
903
|
+
setError,
|
|
904
|
+
addMessage,
|
|
905
|
+
clearMessages: storeClearMessages,
|
|
906
|
+
setLastSyncedAt,
|
|
907
|
+
lastSyncedAt,
|
|
908
|
+
setIsSending: setStoreIsSending,
|
|
909
|
+
setIsStreaming: setStoreIsStreaming
|
|
910
|
+
} = useQuickChatStore();
|
|
911
|
+
const storedMessages = useActiveMessages();
|
|
912
|
+
const contextString = useMemo(
|
|
913
|
+
() => buildContextString(contextChunks),
|
|
914
|
+
[contextChunks]
|
|
915
|
+
);
|
|
916
|
+
const chatBody = useMemo(
|
|
917
|
+
() => ({
|
|
918
|
+
action: "chat",
|
|
919
|
+
context: contextString,
|
|
920
|
+
model: mergedConfig.model,
|
|
921
|
+
systemPrompt: mergedConfig.systemPrompt,
|
|
922
|
+
maxLength: mergedConfig.maxResponseLength
|
|
923
|
+
}),
|
|
924
|
+
[
|
|
925
|
+
contextString,
|
|
926
|
+
mergedConfig.model,
|
|
927
|
+
mergedConfig.systemPrompt,
|
|
928
|
+
mergedConfig.maxResponseLength
|
|
929
|
+
]
|
|
930
|
+
);
|
|
931
|
+
const transport = useMemo(
|
|
932
|
+
() => new DefaultChatTransport({
|
|
933
|
+
api: mergedConfig.endpoint,
|
|
934
|
+
body: chatBody
|
|
935
|
+
}),
|
|
936
|
+
[mergedConfig.endpoint, chatBody]
|
|
937
|
+
);
|
|
938
|
+
const handleRateLimitResponse = useCallback(
|
|
939
|
+
async (res, endpoint) => {
|
|
940
|
+
const notice = await rateLimitNoticeFromResponse(res, endpoint);
|
|
941
|
+
if (!notice) return false;
|
|
942
|
+
setRateLimitNotice(notice);
|
|
943
|
+
setError(null);
|
|
944
|
+
return true;
|
|
945
|
+
},
|
|
946
|
+
[setError]
|
|
947
|
+
);
|
|
948
|
+
const scheduleBackendSync = useCallback(() => {
|
|
949
|
+
if (syncTimeoutRef.current) clearTimeout(syncTimeoutRef.current);
|
|
950
|
+
syncTimeoutRef.current = setTimeout(async () => {
|
|
951
|
+
try {
|
|
952
|
+
const currentMessages = useQuickChatStore.getState().getActiveMessages();
|
|
953
|
+
if (currentMessages.length === 0) return;
|
|
954
|
+
const endpoint = `${mergedConfig.endpoint}/history`;
|
|
955
|
+
const res = await fetch(endpoint, {
|
|
956
|
+
method: "POST",
|
|
957
|
+
headers: { "Content-Type": "application/json" },
|
|
958
|
+
body: JSON.stringify({
|
|
959
|
+
action: "save",
|
|
960
|
+
messages: currentMessages
|
|
961
|
+
})
|
|
962
|
+
});
|
|
963
|
+
if (await handleRateLimitResponse(res, endpoint)) return;
|
|
964
|
+
if (res.ok) setLastSyncedAt(Date.now());
|
|
965
|
+
} catch (err) {
|
|
966
|
+
console.error("[useQuickChat] Failed to sync chat history:", err);
|
|
967
|
+
}
|
|
968
|
+
}, 1e3);
|
|
969
|
+
}, [handleRateLimitResponse, mergedConfig.endpoint, setLastSyncedAt]);
|
|
970
|
+
const {
|
|
971
|
+
messages: aiMessages,
|
|
972
|
+
sendMessage: aiSendMessage,
|
|
973
|
+
status,
|
|
974
|
+
stop,
|
|
975
|
+
setMessages: setAiMessages
|
|
976
|
+
} = useChat({
|
|
977
|
+
transport,
|
|
978
|
+
onError: (err) => {
|
|
979
|
+
const response = err.response ?? void 0;
|
|
980
|
+
const statusCode = err.status ?? (response ? response.status : void 0) ?? -1;
|
|
981
|
+
const responseText = err.responseText ?? err.message ?? "";
|
|
982
|
+
setDebugInfo(
|
|
983
|
+
createDebugInfo({
|
|
984
|
+
status: statusCode,
|
|
985
|
+
ok: false,
|
|
986
|
+
contentType: response?.headers?.get?.("content-type") ?? null,
|
|
987
|
+
rawText: responseText,
|
|
988
|
+
error: err.message
|
|
989
|
+
})
|
|
990
|
+
);
|
|
991
|
+
setStoreIsStreaming(false);
|
|
992
|
+
setStoreIsSending(false);
|
|
993
|
+
const notice = rateLimitNoticeFromError({
|
|
994
|
+
statusCode,
|
|
995
|
+
response,
|
|
996
|
+
responseText,
|
|
997
|
+
endpoint: mergedConfig.endpoint
|
|
998
|
+
});
|
|
999
|
+
if (notice) {
|
|
1000
|
+
setRateLimitNotice(notice);
|
|
1001
|
+
setError(null);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
setRateLimitNotice(null);
|
|
1005
|
+
setError(err.message);
|
|
1006
|
+
},
|
|
1007
|
+
onFinish: ({ message }) => {
|
|
1008
|
+
const messageText = getUIMessageText(message);
|
|
1009
|
+
const current = useQuickChatStore.getState().getActiveMessages();
|
|
1010
|
+
const last = current[current.length - 1];
|
|
1011
|
+
const alreadyHaveSameTail = last?.role === "assistant" && last.content === messageText;
|
|
1012
|
+
if (!alreadyHaveSameTail && messageText) {
|
|
1013
|
+
addMessage({
|
|
1014
|
+
role: message.role,
|
|
1015
|
+
content: messageText,
|
|
1016
|
+
isStreaming: false
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
scheduleBackendSync();
|
|
1020
|
+
setStoreIsStreaming(false);
|
|
1021
|
+
setStoreIsSending(false);
|
|
1022
|
+
}
|
|
666
1023
|
});
|
|
1024
|
+
const loadFromBackend = useCallback(async () => {
|
|
1025
|
+
try {
|
|
1026
|
+
const endpoint = `${mergedConfig.endpoint}/history`;
|
|
1027
|
+
const response = await fetch(endpoint, {
|
|
1028
|
+
method: "POST",
|
|
1029
|
+
headers: { "Content-Type": "application/json" },
|
|
1030
|
+
body: JSON.stringify({ action: "load" })
|
|
1031
|
+
});
|
|
1032
|
+
if (await handleRateLimitResponse(response, endpoint)) return;
|
|
1033
|
+
if (!response.ok) return;
|
|
1034
|
+
const data = await response.json().catch(() => null);
|
|
1035
|
+
if (!data?.messages || !Array.isArray(data.messages)) return;
|
|
1036
|
+
if (data.messages.length === 0) return;
|
|
1037
|
+
useQuickChatStore.getState().hydrate(data.messages);
|
|
1038
|
+
setAiMessages(chatMessagesToUiMessages(data.messages));
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
console.error("[useQuickChat] Failed to load chat history:", err);
|
|
1041
|
+
}
|
|
1042
|
+
}, [handleRateLimitResponse, mergedConfig.endpoint, setAiMessages]);
|
|
1043
|
+
const messages = useMemo(
|
|
1044
|
+
() => uiMessagesToChatMessages({
|
|
1045
|
+
uiMessages: aiMessages,
|
|
1046
|
+
status,
|
|
1047
|
+
setInput
|
|
1048
|
+
}),
|
|
1049
|
+
[aiMessages, setInput, status]
|
|
1050
|
+
);
|
|
1051
|
+
const isStreaming = status === "streaming";
|
|
1052
|
+
const isSending = status === "submitted" || status === "streaming" || manualSending;
|
|
667
1053
|
useEffect(() => {
|
|
668
1054
|
if (initializedRef.current) return;
|
|
669
1055
|
initializedRef.current = true;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
setState((prev) => ({ ...prev, messages: history }));
|
|
675
|
-
}
|
|
1056
|
+
if (storedMessages.length > 0) {
|
|
1057
|
+
setAiMessages(chatMessagesToUiMessages(storedMessages));
|
|
1058
|
+
} else {
|
|
1059
|
+
void loadFromBackend();
|
|
676
1060
|
}
|
|
677
|
-
}, []);
|
|
1061
|
+
}, [loadFromBackend, setAiMessages, storedMessages]);
|
|
678
1062
|
useEffect(() => {
|
|
679
|
-
if (targetElement)
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}, [targetElement, containerElement]);
|
|
1063
|
+
if (!targetElement) return;
|
|
1064
|
+
const chunks = extractContextChunks(targetElement, containerElement);
|
|
1065
|
+
setContextChunks(chunks);
|
|
1066
|
+
}, [targetElement, containerElement, setContextChunks]);
|
|
684
1067
|
useEffect(() => {
|
|
685
|
-
if (!mergedConfig.showSuggestions
|
|
686
|
-
|
|
687
|
-
|
|
1068
|
+
if (!mergedConfig.showSuggestions) return;
|
|
1069
|
+
if (!contextString) return;
|
|
1070
|
+
if (suggestedPrompts.length > 0) return;
|
|
1071
|
+
let cancelled = false;
|
|
688
1072
|
const fetchSuggestions = async () => {
|
|
689
|
-
|
|
1073
|
+
setIsLoadingSuggestions(true);
|
|
690
1074
|
try {
|
|
691
|
-
const contextString = buildContextString(state.contextChunks);
|
|
692
1075
|
const response = await fetch(mergedConfig.endpoint, {
|
|
693
1076
|
method: "POST",
|
|
694
1077
|
headers: { "Content-Type": "application/json" },
|
|
@@ -698,250 +1081,142 @@ function useQuickChat(targetElement, containerElement, config = {}) {
|
|
|
698
1081
|
model: mergedConfig.prePassModel
|
|
699
1082
|
})
|
|
700
1083
|
});
|
|
1084
|
+
if (await handleRateLimitResponse(response, mergedConfig.endpoint)) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
701
1087
|
if (response.ok) {
|
|
702
|
-
const data = await response.json();
|
|
703
|
-
if (data
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
(text, i) => ({
|
|
1088
|
+
const data = await response.json().catch(() => null);
|
|
1089
|
+
if (data?.suggestions && Array.isArray(data.suggestions)) {
|
|
1090
|
+
if (!cancelled) {
|
|
1091
|
+
setSuggestedPrompts(
|
|
1092
|
+
data.suggestions.map((text, i) => ({
|
|
708
1093
|
id: `suggestion-${i}`,
|
|
709
1094
|
text
|
|
710
|
-
})
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
}
|
|
1095
|
+
}))
|
|
1096
|
+
);
|
|
1097
|
+
setIsLoadingSuggestions(false);
|
|
1098
|
+
}
|
|
714
1099
|
return;
|
|
715
1100
|
}
|
|
716
1101
|
}
|
|
717
|
-
} catch (
|
|
718
|
-
console.error("Failed to fetch suggestions:",
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
console.error("[useQuickChat] Failed to fetch suggestions:", err);
|
|
719
1104
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
suggestedPrompts: [
|
|
1105
|
+
if (!cancelled) {
|
|
1106
|
+
setSuggestedPrompts([
|
|
723
1107
|
{ id: "s1", text: "What is this element?" },
|
|
724
1108
|
{ id: "s2", text: "How can I style this?" },
|
|
725
1109
|
{ id: "s3", text: "Is this accessible?" }
|
|
726
|
-
]
|
|
727
|
-
|
|
728
|
-
}
|
|
1110
|
+
]);
|
|
1111
|
+
setIsLoadingSuggestions(false);
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
void fetchSuggestions();
|
|
1115
|
+
return () => {
|
|
1116
|
+
cancelled = true;
|
|
729
1117
|
};
|
|
730
|
-
fetchSuggestions();
|
|
731
1118
|
}, [
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
mergedConfig.showSuggestions,
|
|
1119
|
+
contextString,
|
|
1120
|
+
handleRateLimitResponse,
|
|
735
1121
|
mergedConfig.endpoint,
|
|
736
|
-
mergedConfig.prePassModel
|
|
1122
|
+
mergedConfig.prePassModel,
|
|
1123
|
+
mergedConfig.showSuggestions,
|
|
1124
|
+
setIsLoadingSuggestions,
|
|
1125
|
+
setSuggestedPrompts,
|
|
1126
|
+
suggestedPrompts.length
|
|
737
1127
|
]);
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
contextChunks: prev.contextChunks.map(
|
|
745
|
-
(chunk) => chunk.id === chunkId ? { ...chunk, included: !chunk.included } : chunk
|
|
746
|
-
)
|
|
747
|
-
}));
|
|
748
|
-
}, []);
|
|
749
|
-
const toggleAllChunks = useCallback((included) => {
|
|
750
|
-
setState((prev) => ({
|
|
751
|
-
...prev,
|
|
752
|
-
contextChunks: prev.contextChunks.map((chunk) => ({
|
|
753
|
-
...chunk,
|
|
754
|
-
included
|
|
755
|
-
}))
|
|
756
|
-
}));
|
|
757
|
-
}, []);
|
|
758
|
-
const selectSuggestion = useCallback((prompt) => {
|
|
759
|
-
setState((prev) => ({ ...prev, input: prompt.text }));
|
|
760
|
-
}, []);
|
|
1128
|
+
const selectSuggestion = useCallback(
|
|
1129
|
+
(prompt) => {
|
|
1130
|
+
setInput(prompt.text);
|
|
1131
|
+
},
|
|
1132
|
+
[setInput]
|
|
1133
|
+
);
|
|
761
1134
|
const sendMessage = useCallback(
|
|
762
1135
|
async (messageText) => {
|
|
763
|
-
const text = (messageText ||
|
|
1136
|
+
const text = (messageText || input).trim();
|
|
764
1137
|
if (!text) return;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
role: "user",
|
|
772
|
-
content: text,
|
|
773
|
-
timestamp: Date.now()
|
|
774
|
-
};
|
|
775
|
-
const assistantMessageId = generateId();
|
|
776
|
-
const assistantMessage = {
|
|
777
|
-
id: assistantMessageId,
|
|
778
|
-
role: "assistant",
|
|
779
|
-
content: "",
|
|
780
|
-
timestamp: Date.now(),
|
|
781
|
-
isStreaming: true
|
|
782
|
-
};
|
|
783
|
-
setState((prev) => ({
|
|
784
|
-
...prev,
|
|
785
|
-
input: "",
|
|
786
|
-
messages: [...prev.messages, userMessage, assistantMessage],
|
|
787
|
-
isSending: true,
|
|
788
|
-
isStreaming: true,
|
|
789
|
-
error: null
|
|
790
|
-
}));
|
|
1138
|
+
setInput("");
|
|
1139
|
+
setError(null);
|
|
1140
|
+
setManualSending(true);
|
|
1141
|
+
setStoreIsSending(true);
|
|
1142
|
+
setStoreIsStreaming(true);
|
|
1143
|
+
setDebugInfo(null);
|
|
791
1144
|
try {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
headers: { "Content-Type": "application/json" },
|
|
796
|
-
body: JSON.stringify({
|
|
797
|
-
action: "chat",
|
|
798
|
-
message: text,
|
|
799
|
-
context: contextString,
|
|
800
|
-
model: mergedConfig.model,
|
|
801
|
-
systemPrompt: mergedConfig.systemPrompt,
|
|
802
|
-
maxLength: mergedConfig.maxResponseLength
|
|
803
|
-
}),
|
|
804
|
-
signal: abortControllerRef.current.signal
|
|
1145
|
+
addMessage({
|
|
1146
|
+
role: "user",
|
|
1147
|
+
content: text
|
|
805
1148
|
});
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
let fullContent = "";
|
|
815
|
-
while (true) {
|
|
816
|
-
const { done, value } = await reader.read();
|
|
817
|
-
if (done) break;
|
|
818
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
819
|
-
fullContent += chunk;
|
|
820
|
-
if (fullContent.length > mergedConfig.maxResponseLength) {
|
|
821
|
-
fullContent = fullContent.slice(0, mergedConfig.maxResponseLength) + "...";
|
|
822
|
-
}
|
|
823
|
-
setState((prev) => ({
|
|
824
|
-
...prev,
|
|
825
|
-
messages: prev.messages.map(
|
|
826
|
-
(msg) => msg.id === assistantMessageId ? { ...msg, content: fullContent } : msg
|
|
827
|
-
)
|
|
828
|
-
}));
|
|
829
|
-
}
|
|
830
|
-
setState((prev) => ({
|
|
831
|
-
...prev,
|
|
832
|
-
messages: prev.messages.map(
|
|
833
|
-
(msg) => msg.id === assistantMessageId ? {
|
|
834
|
-
...msg,
|
|
835
|
-
content: fullContent,
|
|
836
|
-
isStreaming: false,
|
|
837
|
-
actions: [
|
|
838
|
-
{
|
|
839
|
-
id: "copy",
|
|
840
|
-
label: "Copy",
|
|
841
|
-
onClick: () => {
|
|
842
|
-
navigator.clipboard.writeText(fullContent);
|
|
843
|
-
}
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
id: "research",
|
|
847
|
-
label: "Research more",
|
|
848
|
-
onClick: () => {
|
|
849
|
-
setState((p) => ({
|
|
850
|
-
...p,
|
|
851
|
-
input: `Tell me more about: ${text}`
|
|
852
|
-
}));
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
]
|
|
856
|
-
} : msg
|
|
857
|
-
),
|
|
858
|
-
isSending: false,
|
|
859
|
-
isStreaming: false
|
|
860
|
-
}));
|
|
861
|
-
} catch (error) {
|
|
862
|
-
if (error.name === "AbortError") {
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
console.error("Chat error:", error);
|
|
866
|
-
setState((prev) => ({
|
|
867
|
-
...prev,
|
|
868
|
-
messages: prev.messages.map(
|
|
869
|
-
(msg) => msg.id === assistantMessageId ? {
|
|
870
|
-
...msg,
|
|
871
|
-
content: "Sorry, I couldn't process your request. Please try again.",
|
|
872
|
-
isStreaming: false
|
|
873
|
-
} : msg
|
|
874
|
-
),
|
|
875
|
-
isSending: false,
|
|
876
|
-
isStreaming: false,
|
|
877
|
-
error: error.message
|
|
878
|
-
}));
|
|
1149
|
+
await aiSendMessage({ text });
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1152
|
+
setError(msg);
|
|
1153
|
+
} finally {
|
|
1154
|
+
setManualSending(false);
|
|
1155
|
+
setStoreIsSending(false);
|
|
1156
|
+
setStoreIsStreaming(false);
|
|
879
1157
|
}
|
|
880
1158
|
},
|
|
881
|
-
[
|
|
1159
|
+
[
|
|
1160
|
+
addMessage,
|
|
1161
|
+
aiSendMessage,
|
|
1162
|
+
input,
|
|
1163
|
+
setError,
|
|
1164
|
+
setInput,
|
|
1165
|
+
setStoreIsSending,
|
|
1166
|
+
setStoreIsStreaming
|
|
1167
|
+
]
|
|
882
1168
|
);
|
|
883
1169
|
const clearMessages = useCallback(() => {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}))
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
}, [state.messages]);
|
|
1170
|
+
stop();
|
|
1171
|
+
storeClearMessages();
|
|
1172
|
+
setAiMessages([]);
|
|
1173
|
+
const endpoint = `${mergedConfig.endpoint}/history`;
|
|
1174
|
+
fetch(endpoint, {
|
|
1175
|
+
method: "POST",
|
|
1176
|
+
headers: { "Content-Type": "application/json" },
|
|
1177
|
+
body: JSON.stringify({ action: "clear" })
|
|
1178
|
+
}).then((res) => handleRateLimitResponse(res, endpoint)).catch(
|
|
1179
|
+
(err) => console.error("[useQuickChat] Failed to clear backend history:", err)
|
|
1180
|
+
);
|
|
1181
|
+
}, [
|
|
1182
|
+
handleRateLimitResponse,
|
|
1183
|
+
mergedConfig.endpoint,
|
|
1184
|
+
setAiMessages,
|
|
1185
|
+
stop,
|
|
1186
|
+
storeClearMessages
|
|
1187
|
+
]);
|
|
904
1188
|
useEffect(() => {
|
|
905
1189
|
return () => {
|
|
906
|
-
if (
|
|
907
|
-
abortControllerRef.current.abort();
|
|
908
|
-
}
|
|
1190
|
+
if (syncTimeoutRef.current) clearTimeout(syncTimeoutRef.current);
|
|
909
1191
|
};
|
|
910
1192
|
}, []);
|
|
911
1193
|
return {
|
|
912
|
-
|
|
1194
|
+
input,
|
|
1195
|
+
messages,
|
|
1196
|
+
isLoadingSuggestions,
|
|
1197
|
+
isSending,
|
|
1198
|
+
isStreaming,
|
|
1199
|
+
suggestedPrompts,
|
|
1200
|
+
contextChunks,
|
|
1201
|
+
error,
|
|
1202
|
+
debugInfo,
|
|
1203
|
+
rateLimitNotice,
|
|
1204
|
+
isPinned,
|
|
1205
|
+
lastSyncedAt,
|
|
913
1206
|
config: mergedConfig,
|
|
914
1207
|
setInput,
|
|
915
1208
|
toggleChunk,
|
|
916
1209
|
toggleAllChunks,
|
|
917
1210
|
selectSuggestion,
|
|
918
1211
|
sendMessage,
|
|
919
|
-
clearMessages
|
|
1212
|
+
clearMessages,
|
|
1213
|
+
setIsPinned,
|
|
1214
|
+
clearRateLimitNotice: () => setRateLimitNotice(null)
|
|
920
1215
|
};
|
|
921
1216
|
}
|
|
922
1217
|
|
|
923
1218
|
// src/QuickChat/QuickChat.tsx
|
|
924
1219
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
925
|
-
var PINNED_STATE_KEY2 = "anyclick-quick-chat-pinned";
|
|
926
|
-
function getStoredPinnedState() {
|
|
927
|
-
if (typeof window === "undefined") return false;
|
|
928
|
-
try {
|
|
929
|
-
return sessionStorage.getItem(PINNED_STATE_KEY2) === "true";
|
|
930
|
-
} catch {
|
|
931
|
-
return false;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
function setStoredPinnedState(pinned) {
|
|
935
|
-
if (typeof window === "undefined") return;
|
|
936
|
-
try {
|
|
937
|
-
if (pinned) {
|
|
938
|
-
sessionStorage.setItem(PINNED_STATE_KEY2, "true");
|
|
939
|
-
} else {
|
|
940
|
-
sessionStorage.removeItem(PINNED_STATE_KEY2);
|
|
941
|
-
}
|
|
942
|
-
} catch {
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
1220
|
var stylesInjected = false;
|
|
946
1221
|
function injectStyles() {
|
|
947
1222
|
if (stylesInjected || typeof document === "undefined") return;
|
|
@@ -982,39 +1257,41 @@ function QuickChat({
|
|
|
982
1257
|
const [hoveredSuggestion, setHoveredSuggestion] = useState2(
|
|
983
1258
|
null
|
|
984
1259
|
);
|
|
985
|
-
const [localPinned, setLocalPinned] = useState2(() => getStoredPinnedState());
|
|
986
|
-
const isPinned = isPinnedProp || localPinned;
|
|
987
|
-
const handlePinToggle = useCallback2(() => {
|
|
988
|
-
const newPinned = !isPinned;
|
|
989
|
-
setLocalPinned(newPinned);
|
|
990
|
-
setStoredPinnedState(newPinned);
|
|
991
|
-
onPin?.(newPinned);
|
|
992
|
-
}, [isPinned, onPin]);
|
|
993
|
-
const handleClose = useCallback2(() => {
|
|
994
|
-
if (isPinned) {
|
|
995
|
-
setLocalPinned(false);
|
|
996
|
-
setStoredPinnedState(false);
|
|
997
|
-
onPin?.(false);
|
|
998
|
-
}
|
|
999
|
-
onClose();
|
|
1000
|
-
}, [isPinned, onPin, onClose]);
|
|
1001
1260
|
const {
|
|
1002
1261
|
input,
|
|
1003
1262
|
messages,
|
|
1004
1263
|
isLoadingSuggestions,
|
|
1005
1264
|
isSending,
|
|
1006
1265
|
isStreaming,
|
|
1266
|
+
debugInfo,
|
|
1267
|
+
rateLimitNotice,
|
|
1007
1268
|
suggestedPrompts,
|
|
1008
1269
|
contextChunks,
|
|
1009
1270
|
error,
|
|
1271
|
+
isPinned: storePinned,
|
|
1010
1272
|
setInput,
|
|
1011
1273
|
toggleChunk,
|
|
1012
1274
|
toggleAllChunks,
|
|
1013
1275
|
selectSuggestion,
|
|
1014
1276
|
sendMessage,
|
|
1015
1277
|
clearMessages,
|
|
1278
|
+
setIsPinned,
|
|
1279
|
+
clearRateLimitNotice,
|
|
1016
1280
|
config: mergedConfig
|
|
1017
1281
|
} = useQuickChat(targetElement, containerElement, config);
|
|
1282
|
+
const isPinned = isPinnedProp || storePinned;
|
|
1283
|
+
const handlePinToggle = useCallback2(() => {
|
|
1284
|
+
const newPinned = !isPinned;
|
|
1285
|
+
setIsPinned(newPinned);
|
|
1286
|
+
onPin?.(newPinned);
|
|
1287
|
+
}, [isPinned, setIsPinned, onPin]);
|
|
1288
|
+
const handleClose = useCallback2(() => {
|
|
1289
|
+
if (isPinned) {
|
|
1290
|
+
setIsPinned(false);
|
|
1291
|
+
onPin?.(false);
|
|
1292
|
+
}
|
|
1293
|
+
onClose();
|
|
1294
|
+
}, [isPinned, setIsPinned, onPin, onClose]);
|
|
1018
1295
|
useEffect2(() => {
|
|
1019
1296
|
injectStyles();
|
|
1020
1297
|
}, []);
|
|
@@ -1077,10 +1354,65 @@ function QuickChat({
|
|
|
1077
1354
|
const handleCopy = useCallback2((text) => {
|
|
1078
1355
|
navigator.clipboard.writeText(text);
|
|
1079
1356
|
}, []);
|
|
1080
|
-
const includedCount =
|
|
1357
|
+
const includedCount = useMemo2(
|
|
1081
1358
|
() => contextChunks.filter((c) => c.included).length,
|
|
1082
1359
|
[contextChunks]
|
|
1083
1360
|
);
|
|
1361
|
+
const [rateLimitExpanded, setRateLimitExpanded] = useState2(false);
|
|
1362
|
+
const [reportStatus, setReportStatus] = useState2("idle");
|
|
1363
|
+
const [reportUrl, setReportUrl] = useState2(null);
|
|
1364
|
+
const [reportError, setReportError] = useState2(null);
|
|
1365
|
+
useEffect2(() => {
|
|
1366
|
+
if (rateLimitNotice) {
|
|
1367
|
+
setReportStatus("idle");
|
|
1368
|
+
setReportUrl(null);
|
|
1369
|
+
setReportError(null);
|
|
1370
|
+
setRateLimitExpanded(false);
|
|
1371
|
+
}
|
|
1372
|
+
}, [rateLimitNotice]);
|
|
1373
|
+
const handleReportIssue = useCallback2(async () => {
|
|
1374
|
+
if (!rateLimitNotice) return;
|
|
1375
|
+
if (!targetElement) {
|
|
1376
|
+
setReportStatus("error");
|
|
1377
|
+
setReportError("No target element available to report.");
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
setReportStatus("sending");
|
|
1381
|
+
setReportError(null);
|
|
1382
|
+
try {
|
|
1383
|
+
const payload = buildAnyclickPayload(targetElement, "issue", {
|
|
1384
|
+
comment: `QuickChat: ${rateLimitNotice.message}`,
|
|
1385
|
+
metadata: {
|
|
1386
|
+
source: "quickchat",
|
|
1387
|
+
kind: "rate_limit",
|
|
1388
|
+
endpoint: rateLimitNotice.endpoint ?? mergedConfig.endpoint,
|
|
1389
|
+
retryAt: rateLimitNotice.retryAt,
|
|
1390
|
+
retryAfterSeconds: rateLimitNotice.retryAfterSeconds,
|
|
1391
|
+
requestId: rateLimitNotice.requestId,
|
|
1392
|
+
debugInfo: debugInfo ?? void 0,
|
|
1393
|
+
raw: rateLimitNotice.raw ?? void 0
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
const res = await fetch("/api/feedback", {
|
|
1397
|
+
method: "POST",
|
|
1398
|
+
headers: { "Content-Type": "application/json" },
|
|
1399
|
+
body: JSON.stringify(payload)
|
|
1400
|
+
});
|
|
1401
|
+
const json = await res.json().catch(() => null);
|
|
1402
|
+
if (!res.ok || !json?.success) {
|
|
1403
|
+
const msg = json?.error || (res.status ? `Failed to create issue (${res.status}).` : "Failed to create issue.");
|
|
1404
|
+
throw new Error(msg);
|
|
1405
|
+
}
|
|
1406
|
+
const firstUrl = json.results?.find(
|
|
1407
|
+
(r) => typeof r.url === "string"
|
|
1408
|
+
)?.url;
|
|
1409
|
+
setReportUrl(firstUrl ?? null);
|
|
1410
|
+
setReportStatus("sent");
|
|
1411
|
+
} catch (e) {
|
|
1412
|
+
setReportStatus("error");
|
|
1413
|
+
setReportError(e instanceof Error ? e.message : String(e));
|
|
1414
|
+
}
|
|
1415
|
+
}, [rateLimitNotice, targetElement, mergedConfig.endpoint, debugInfo]);
|
|
1084
1416
|
if (!visible) return null;
|
|
1085
1417
|
const containerStyles = isPinned ? {
|
|
1086
1418
|
...quickChatStyles.pinnedContainer,
|
|
@@ -1239,7 +1571,7 @@ function QuickChat({
|
|
|
1239
1571
|
{
|
|
1240
1572
|
style: isPinned ? quickChatStyles.pinnedMessagesArea : quickChatStyles.messagesArea,
|
|
1241
1573
|
children: [
|
|
1242
|
-
error && /* @__PURE__ */ jsxs("div", { style: quickChatStyles.errorContainer, children: [
|
|
1574
|
+
error && !rateLimitNotice && /* @__PURE__ */ jsxs("div", { style: quickChatStyles.errorContainer, children: [
|
|
1243
1575
|
/* @__PURE__ */ jsx(AlertCircle, { size: 20, style: quickChatStyles.errorIcon }),
|
|
1244
1576
|
/* @__PURE__ */ jsx("span", { style: quickChatStyles.errorText, children: error }),
|
|
1245
1577
|
/* @__PURE__ */ jsxs(
|
|
@@ -1255,6 +1587,55 @@ function QuickChat({
|
|
|
1255
1587
|
}
|
|
1256
1588
|
)
|
|
1257
1589
|
] }),
|
|
1590
|
+
debugInfo && /* @__PURE__ */ jsxs(
|
|
1591
|
+
"div",
|
|
1592
|
+
{
|
|
1593
|
+
style: {
|
|
1594
|
+
backgroundColor: "#0f172a",
|
|
1595
|
+
color: "#e2e8f0",
|
|
1596
|
+
border: "1px solid #334155",
|
|
1597
|
+
borderRadius: "8px",
|
|
1598
|
+
padding: "8px",
|
|
1599
|
+
margin: "0 0 8px",
|
|
1600
|
+
fontSize: "12px",
|
|
1601
|
+
lineHeight: 1.4,
|
|
1602
|
+
wordBreak: "break-word"
|
|
1603
|
+
},
|
|
1604
|
+
children: [
|
|
1605
|
+
/* @__PURE__ */ jsxs(
|
|
1606
|
+
"div",
|
|
1607
|
+
{
|
|
1608
|
+
style: {
|
|
1609
|
+
display: "flex",
|
|
1610
|
+
justifyContent: "space-between",
|
|
1611
|
+
gap: "8px"
|
|
1612
|
+
},
|
|
1613
|
+
children: [
|
|
1614
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1615
|
+
"Last request: ",
|
|
1616
|
+
debugInfo.status,
|
|
1617
|
+
" ",
|
|
1618
|
+
debugInfo.ok ? "(ok)" : "(error)"
|
|
1619
|
+
] }),
|
|
1620
|
+
/* @__PURE__ */ jsx("span", { style: { opacity: 0.7 }, children: new Date(debugInfo.timestamp).toLocaleTimeString() })
|
|
1621
|
+
]
|
|
1622
|
+
}
|
|
1623
|
+
),
|
|
1624
|
+
debugInfo.error && /* @__PURE__ */ jsxs("div", { style: { color: "#f87171", marginTop: "4px" }, children: [
|
|
1625
|
+
"Error: ",
|
|
1626
|
+
debugInfo.error
|
|
1627
|
+
] }),
|
|
1628
|
+
debugInfo.contentPreview && /* @__PURE__ */ jsxs("div", { style: { marginTop: "4px" }, children: [
|
|
1629
|
+
"Content: ",
|
|
1630
|
+
debugInfo.contentPreview
|
|
1631
|
+
] }),
|
|
1632
|
+
/* @__PURE__ */ jsxs("div", { style: { marginTop: "4px", opacity: 0.8 }, children: [
|
|
1633
|
+
"Raw: ",
|
|
1634
|
+
debugInfo.rawTextPreview || "(empty)"
|
|
1635
|
+
] })
|
|
1636
|
+
]
|
|
1637
|
+
}
|
|
1638
|
+
),
|
|
1258
1639
|
messages.length > 0 && messages.map((message) => /* @__PURE__ */ jsxs(
|
|
1259
1640
|
"div",
|
|
1260
1641
|
{
|
|
@@ -1309,6 +1690,153 @@ function QuickChat({
|
|
|
1309
1690
|
]
|
|
1310
1691
|
}
|
|
1311
1692
|
),
|
|
1693
|
+
rateLimitNotice && /* @__PURE__ */ jsxs(
|
|
1694
|
+
"div",
|
|
1695
|
+
{
|
|
1696
|
+
style: {
|
|
1697
|
+
borderTop: "1px solid rgba(148, 163, 184, 0.25)",
|
|
1698
|
+
background: "linear-gradient(180deg, rgba(15, 23, 42, 0.92), rgba(15, 23, 42, 0.96))",
|
|
1699
|
+
color: "#e2e8f0",
|
|
1700
|
+
padding: "8px 10px"
|
|
1701
|
+
},
|
|
1702
|
+
children: [
|
|
1703
|
+
/* @__PURE__ */ jsxs(
|
|
1704
|
+
"div",
|
|
1705
|
+
{
|
|
1706
|
+
style: {
|
|
1707
|
+
display: "flex",
|
|
1708
|
+
alignItems: "center",
|
|
1709
|
+
justifyContent: "space-between",
|
|
1710
|
+
gap: "8px"
|
|
1711
|
+
},
|
|
1712
|
+
children: [
|
|
1713
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
1714
|
+
/* @__PURE__ */ jsx(AlertCircle, { size: 16, style: { color: "#fbbf24" } }),
|
|
1715
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "13px", lineHeight: 1.2 }, children: rateLimitNotice.message })
|
|
1716
|
+
] }),
|
|
1717
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
|
|
1718
|
+
/* @__PURE__ */ jsx(
|
|
1719
|
+
"button",
|
|
1720
|
+
{
|
|
1721
|
+
type: "button",
|
|
1722
|
+
onClick: () => setRateLimitExpanded((v) => !v),
|
|
1723
|
+
style: {
|
|
1724
|
+
border: "1px solid rgba(148, 163, 184, 0.25)",
|
|
1725
|
+
background: "rgba(30, 41, 59, 0.6)",
|
|
1726
|
+
color: "#e2e8f0",
|
|
1727
|
+
borderRadius: "6px",
|
|
1728
|
+
padding: "4px 8px",
|
|
1729
|
+
fontSize: "12px",
|
|
1730
|
+
cursor: "pointer"
|
|
1731
|
+
},
|
|
1732
|
+
children: rateLimitExpanded ? "Hide" : "Details"
|
|
1733
|
+
}
|
|
1734
|
+
),
|
|
1735
|
+
/* @__PURE__ */ jsx(
|
|
1736
|
+
"button",
|
|
1737
|
+
{
|
|
1738
|
+
type: "button",
|
|
1739
|
+
onClick: handleReportIssue,
|
|
1740
|
+
disabled: reportStatus === "sending" || reportStatus === "sent",
|
|
1741
|
+
style: {
|
|
1742
|
+
border: "1px solid rgba(148, 163, 184, 0.25)",
|
|
1743
|
+
background: reportStatus === "sent" ? "rgba(34, 197, 94, 0.22)" : "rgba(30, 41, 59, 0.6)",
|
|
1744
|
+
color: "#e2e8f0",
|
|
1745
|
+
borderRadius: "6px",
|
|
1746
|
+
padding: "4px 8px",
|
|
1747
|
+
fontSize: "12px",
|
|
1748
|
+
cursor: reportStatus === "sending" || reportStatus === "sent" ? "not-allowed" : "pointer",
|
|
1749
|
+
opacity: reportStatus === "sending" ? 0.7 : 1
|
|
1750
|
+
},
|
|
1751
|
+
title: "Create a GitHub issue via /api/feedback",
|
|
1752
|
+
children: reportStatus === "sending" ? "Reporting..." : reportStatus === "sent" ? "Reported" : "Report"
|
|
1753
|
+
}
|
|
1754
|
+
),
|
|
1755
|
+
/* @__PURE__ */ jsx(
|
|
1756
|
+
"button",
|
|
1757
|
+
{
|
|
1758
|
+
type: "button",
|
|
1759
|
+
onClick: () => {
|
|
1760
|
+
clearRateLimitNotice();
|
|
1761
|
+
setRateLimitExpanded(false);
|
|
1762
|
+
},
|
|
1763
|
+
style: {
|
|
1764
|
+
border: "none",
|
|
1765
|
+
background: "transparent",
|
|
1766
|
+
color: "rgba(226, 232, 240, 0.8)",
|
|
1767
|
+
padding: "4px",
|
|
1768
|
+
cursor: "pointer"
|
|
1769
|
+
},
|
|
1770
|
+
title: "Dismiss",
|
|
1771
|
+
children: /* @__PURE__ */ jsx(X, { size: 14 })
|
|
1772
|
+
}
|
|
1773
|
+
)
|
|
1774
|
+
] })
|
|
1775
|
+
]
|
|
1776
|
+
}
|
|
1777
|
+
),
|
|
1778
|
+
reportUrl && /* @__PURE__ */ jsxs("div", { style: { marginTop: "6px", fontSize: "12px" }, children: [
|
|
1779
|
+
"Created:",
|
|
1780
|
+
" ",
|
|
1781
|
+
/* @__PURE__ */ jsx(
|
|
1782
|
+
"a",
|
|
1783
|
+
{
|
|
1784
|
+
href: reportUrl,
|
|
1785
|
+
target: "_blank",
|
|
1786
|
+
rel: "noopener noreferrer",
|
|
1787
|
+
style: { color: "#93c5fd" },
|
|
1788
|
+
children: "Open issue"
|
|
1789
|
+
}
|
|
1790
|
+
)
|
|
1791
|
+
] }),
|
|
1792
|
+
reportError && /* @__PURE__ */ jsx(
|
|
1793
|
+
"div",
|
|
1794
|
+
{
|
|
1795
|
+
style: { marginTop: "6px", fontSize: "12px", color: "#fca5a5" },
|
|
1796
|
+
children: reportError
|
|
1797
|
+
}
|
|
1798
|
+
),
|
|
1799
|
+
rateLimitExpanded && /* @__PURE__ */ jsxs(
|
|
1800
|
+
"div",
|
|
1801
|
+
{
|
|
1802
|
+
style: {
|
|
1803
|
+
marginTop: "8px",
|
|
1804
|
+
fontSize: "12px",
|
|
1805
|
+
lineHeight: 1.4,
|
|
1806
|
+
backgroundColor: "rgba(2, 6, 23, 0.55)",
|
|
1807
|
+
border: "1px solid rgba(148, 163, 184, 0.25)",
|
|
1808
|
+
borderRadius: "8px",
|
|
1809
|
+
padding: "8px",
|
|
1810
|
+
wordBreak: "break-word"
|
|
1811
|
+
},
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ jsxs("div", { style: { opacity: 0.85 }, children: [
|
|
1814
|
+
"Status: ",
|
|
1815
|
+
rateLimitNotice.status,
|
|
1816
|
+
rateLimitNotice.requestId ? ` \u2022 Request: ${rateLimitNotice.requestId}` : ""
|
|
1817
|
+
] }),
|
|
1818
|
+
rateLimitNotice.endpoint && /* @__PURE__ */ jsxs("div", { style: { opacity: 0.75, marginTop: "4px" }, children: [
|
|
1819
|
+
"Endpoint: ",
|
|
1820
|
+
rateLimitNotice.endpoint
|
|
1821
|
+
] }),
|
|
1822
|
+
rateLimitNotice.retryAt && /* @__PURE__ */ jsxs("div", { style: { opacity: 0.75, marginTop: "4px" }, children: [
|
|
1823
|
+
"RetryAt: ",
|
|
1824
|
+
new Date(rateLimitNotice.retryAt).toLocaleString()
|
|
1825
|
+
] }),
|
|
1826
|
+
rateLimitNotice.raw && /* @__PURE__ */ jsxs("div", { style: { marginTop: "6px", opacity: 0.85 }, children: [
|
|
1827
|
+
"Raw: ",
|
|
1828
|
+
rateLimitNotice.raw
|
|
1829
|
+
] }),
|
|
1830
|
+
debugInfo && /* @__PURE__ */ jsxs("div", { style: { marginTop: "6px", opacity: 0.85 }, children: [
|
|
1831
|
+
"Debug: ",
|
|
1832
|
+
debugInfo.rawTextPreview || "(empty)"
|
|
1833
|
+
] })
|
|
1834
|
+
]
|
|
1835
|
+
}
|
|
1836
|
+
)
|
|
1837
|
+
]
|
|
1838
|
+
}
|
|
1839
|
+
),
|
|
1312
1840
|
/* @__PURE__ */ jsxs("div", { style: quickChatStyles.inputContainer, children: [
|
|
1313
1841
|
/* @__PURE__ */ jsx(
|
|
1314
1842
|
"textarea",
|
|
@@ -1375,7 +1903,7 @@ function QuickChat({
|
|
|
1375
1903
|
}
|
|
1376
1904
|
|
|
1377
1905
|
// src/ScreenshotPreview.tsx
|
|
1378
|
-
import React2, { useMemo as
|
|
1906
|
+
import React2, { useMemo as useMemo3, useState as useState3 } from "react";
|
|
1379
1907
|
import { estimateTotalSize, formatBytes } from "@ewjdev/anyclick-core";
|
|
1380
1908
|
import {
|
|
1381
1909
|
AlertCircleIcon,
|
|
@@ -1875,7 +2403,7 @@ var ScreenshotPreview = React2.memo(function ScreenshotPreview2({
|
|
|
1875
2403
|
const getError = (key) => {
|
|
1876
2404
|
return screenshots?.errors?.[key];
|
|
1877
2405
|
};
|
|
1878
|
-
const tabs =
|
|
2406
|
+
const tabs = useMemo3(() => {
|
|
1879
2407
|
if (!screenshots) return [];
|
|
1880
2408
|
const allTabs = [
|
|
1881
2409
|
{
|
|
@@ -1899,7 +2427,7 @@ var ScreenshotPreview = React2.memo(function ScreenshotPreview2({
|
|
|
1899
2427
|
];
|
|
1900
2428
|
return allTabs.filter((tab) => tab.data || tab.error);
|
|
1901
2429
|
}, [screenshots]);
|
|
1902
|
-
const totalSize =
|
|
2430
|
+
const totalSize = useMemo3(
|
|
1903
2431
|
() => screenshots ? estimateTotalSize(screenshots) : 0,
|
|
1904
2432
|
[screenshots]
|
|
1905
2433
|
);
|
|
@@ -2917,12 +3445,12 @@ function useFeedback() {
|
|
|
2917
3445
|
}
|
|
2918
3446
|
|
|
2919
3447
|
// src/store.ts
|
|
2920
|
-
import { create } from "zustand";
|
|
3448
|
+
import { create as create2 } from "zustand";
|
|
2921
3449
|
var providerIdCounter = 0;
|
|
2922
3450
|
function generateProviderId() {
|
|
2923
3451
|
return `anyclick-provider-${++providerIdCounter}`;
|
|
2924
3452
|
}
|
|
2925
|
-
var useProviderStore =
|
|
3453
|
+
var useProviderStore = create2((set, get) => ({
|
|
2926
3454
|
providers: /* @__PURE__ */ new Map(),
|
|
2927
3455
|
findProvidersForElement: (element) => {
|
|
2928
3456
|
const { providers } = get();
|
|
@@ -3150,7 +3678,7 @@ function AnyclickProvider({
|
|
|
3150
3678
|
updateProvider
|
|
3151
3679
|
} = useProviderStore();
|
|
3152
3680
|
const parentId = parentContext?.providerId ?? null;
|
|
3153
|
-
const actualDepth =
|
|
3681
|
+
const actualDepth = useMemo4(() => {
|
|
3154
3682
|
if (!parentContext) return 0;
|
|
3155
3683
|
let d = 0;
|
|
3156
3684
|
let currentId = parentId;
|
|
@@ -3164,7 +3692,7 @@ function AnyclickProvider({
|
|
|
3164
3692
|
}, [parentContext, parentId]);
|
|
3165
3693
|
const isDisabledByTheme = theme === null || theme?.disabled === true;
|
|
3166
3694
|
const effectiveDisabled = disabled || isDisabledByTheme;
|
|
3167
|
-
const localTheme =
|
|
3695
|
+
const localTheme = useMemo4(() => {
|
|
3168
3696
|
if (theme === null) {
|
|
3169
3697
|
return { disabled: true };
|
|
3170
3698
|
}
|
|
@@ -3344,7 +3872,7 @@ function AnyclickProvider({
|
|
|
3344
3872
|
[submitAnyclick, targetElement]
|
|
3345
3873
|
);
|
|
3346
3874
|
const inheritedTheme = getMergedTheme(providerId);
|
|
3347
|
-
const mergedTheme =
|
|
3875
|
+
const mergedTheme = useMemo4(
|
|
3348
3876
|
() => ({
|
|
3349
3877
|
...inheritedTheme,
|
|
3350
3878
|
...localTheme,
|
|
@@ -3367,7 +3895,7 @@ function AnyclickProvider({
|
|
|
3367
3895
|
const effectiveMenuClassName = mergedTheme.menuClassName ?? menuClassName;
|
|
3368
3896
|
const effectiveHighlightConfig = mergedTheme.highlightConfig ?? highlightConfig;
|
|
3369
3897
|
const effectiveScreenshotConfig = mergedTheme.screenshotConfig ?? screenshotConfig;
|
|
3370
|
-
const contextValue =
|
|
3898
|
+
const contextValue = useMemo4(
|
|
3371
3899
|
() => ({
|
|
3372
3900
|
closeMenu,
|
|
3373
3901
|
isEnabled: !effectiveDisabled && !isDisabledByAncestor(providerId),
|
|
@@ -3399,7 +3927,7 @@ function AnyclickProvider({
|
|
|
3399
3927
|
children
|
|
3400
3928
|
}
|
|
3401
3929
|
) : children;
|
|
3402
|
-
return /* @__PURE__ */
|
|
3930
|
+
return /* @__PURE__ */ jsx4(AnyclickContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs4("div", { "data-anyclick-root": true, children: [
|
|
3403
3931
|
content,
|
|
3404
3932
|
/* @__PURE__ */ jsx4(
|
|
3405
3933
|
ContextMenu,
|
|
@@ -3420,12 +3948,12 @@ function AnyclickProvider({
|
|
|
3420
3948
|
visible: menuVisible && !effectiveDisabled
|
|
3421
3949
|
}
|
|
3422
3950
|
)
|
|
3423
|
-
] });
|
|
3951
|
+
] }) });
|
|
3424
3952
|
}
|
|
3425
3953
|
var FeedbackProvider = AnyclickProvider;
|
|
3426
3954
|
|
|
3427
3955
|
// src/FunModeBridge.tsx
|
|
3428
|
-
import { useEffect as useEffect5, useMemo as
|
|
3956
|
+
import { useEffect as useEffect5, useMemo as useMemo5, useRef as useRef5 } from "react";
|
|
3429
3957
|
import {
|
|
3430
3958
|
usePointer
|
|
3431
3959
|
} from "@ewjdev/anyclick-pointer";
|
|
@@ -3476,7 +4004,7 @@ function FunModeBridge() {
|
|
|
3476
4004
|
const providerStore = useProviderStore();
|
|
3477
4005
|
const activeProviderRef = useRef5(null);
|
|
3478
4006
|
const cachedConfigs = useRef5({});
|
|
3479
|
-
const findActiveFunProvider =
|
|
4007
|
+
const findActiveFunProvider = useMemo5(() => {
|
|
3480
4008
|
return (el) => {
|
|
3481
4009
|
if (!el) return null;
|
|
3482
4010
|
const providers = providerStore.findProvidersForElement(el);
|
|
@@ -3522,11 +4050,71 @@ function FunModeBridge() {
|
|
|
3522
4050
|
return null;
|
|
3523
4051
|
}
|
|
3524
4052
|
|
|
4053
|
+
// src/ui/button.tsx
|
|
4054
|
+
import { forwardRef } from "react";
|
|
4055
|
+
|
|
4056
|
+
// src/utils/cn.ts
|
|
4057
|
+
function cn(...classes) {
|
|
4058
|
+
return classes.filter(Boolean).join(" ");
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
// src/ui/button.tsx
|
|
4062
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
4063
|
+
var variantClasses = {
|
|
4064
|
+
default: "ac-bg-accent ac-text-accent-foreground hover:ac-bg-accent-muted ac-border ac-border-border",
|
|
4065
|
+
ghost: "ac-bg-transparent hover:ac-bg-surface-muted ac-text-text",
|
|
4066
|
+
outline: "ac-bg-transparent ac-text-text ac-border ac-border-border hover:ac-bg-surface-muted",
|
|
4067
|
+
destructive: "ac-bg-destructive ac-text-accent-foreground hover:ac-bg-destructive/80"
|
|
4068
|
+
};
|
|
4069
|
+
var sizeClasses = {
|
|
4070
|
+
sm: "ac-h-8 ac-px-3 ac-text-sm",
|
|
4071
|
+
md: "ac-h-10 ac-px-4 ac-text-sm",
|
|
4072
|
+
lg: "ac-h-11 ac-px-5 ac-text-base"
|
|
4073
|
+
};
|
|
4074
|
+
var Button = forwardRef(
|
|
4075
|
+
({ className, variant = "default", size = "md", ...props }, ref) => {
|
|
4076
|
+
return /* @__PURE__ */ jsx5(
|
|
4077
|
+
"button",
|
|
4078
|
+
{
|
|
4079
|
+
ref,
|
|
4080
|
+
className: cn(
|
|
4081
|
+
"ac-inline-flex ac-items-center ac-justify-center ac-gap-2 ac-rounded-md ac-font-medium ac-transition-colors focus-visible:ac-outline focus-visible:ac-outline-2 focus-visible:ac-outline-offset-2 focus-visible:ac-outline-accent disabled:ac-opacity-50 disabled:ac-cursor-not-allowed",
|
|
4082
|
+
variantClasses[variant],
|
|
4083
|
+
sizeClasses[size],
|
|
4084
|
+
className
|
|
4085
|
+
),
|
|
4086
|
+
...props
|
|
4087
|
+
}
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
4090
|
+
);
|
|
4091
|
+
Button.displayName = "Button";
|
|
4092
|
+
|
|
4093
|
+
// src/ui/input.tsx
|
|
4094
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
4095
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
4096
|
+
var Input = forwardRef2(
|
|
4097
|
+
({ className, ...props }, ref) => {
|
|
4098
|
+
return /* @__PURE__ */ jsx6(
|
|
4099
|
+
"input",
|
|
4100
|
+
{
|
|
4101
|
+
ref,
|
|
4102
|
+
className: cn(
|
|
4103
|
+
"ac-h-10 ac-w-full ac-rounded-md ac-border ac-border-border ac-bg-surface ac-text-text ac-placeholder-text-muted ac-px-3 ac-py-2 ac-text-sm focus-visible:ac-outline focus-visible:ac-outline-2 focus-visible:ac-outline-offset-2 focus-visible:ac-outline-accent disabled:ac-opacity-50 disabled:ac-cursor-not-allowed",
|
|
4104
|
+
className
|
|
4105
|
+
),
|
|
4106
|
+
...props
|
|
4107
|
+
}
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
);
|
|
4111
|
+
Input.displayName = "Input";
|
|
4112
|
+
|
|
3525
4113
|
// src/InspectDialog/InspectDialogManager.tsx
|
|
3526
4114
|
import { useCallback as useCallback5, useEffect as useEffect7, useState as useState7 } from "react";
|
|
3527
4115
|
|
|
3528
4116
|
// src/InspectDialog/InspectSimple.tsx
|
|
3529
|
-
import { useEffect as useEffect6, useMemo as
|
|
4117
|
+
import { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef6, useState as useState6 } from "react";
|
|
3530
4118
|
import {
|
|
3531
4119
|
captureScreenshot,
|
|
3532
4120
|
getElementInspectInfo as getElementInspectInfo2
|
|
@@ -3633,7 +4221,7 @@ function formatSourceLocation(location) {
|
|
|
3633
4221
|
}
|
|
3634
4222
|
|
|
3635
4223
|
// src/InspectDialog/InspectSimple.tsx
|
|
3636
|
-
import { Fragment as Fragment4, jsx as
|
|
4224
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3637
4225
|
var DEFAULT_COMPACT_CONFIG = {
|
|
3638
4226
|
scale: 0.5,
|
|
3639
4227
|
fonts: {
|
|
@@ -3760,7 +4348,7 @@ function InspectSimple({
|
|
|
3760
4348
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
3761
4349
|
};
|
|
3762
4350
|
}, [visible, onClose]);
|
|
3763
|
-
const identityLabel =
|
|
4351
|
+
const identityLabel = useMemo6(() => {
|
|
3764
4352
|
if (!info) return "Select an element";
|
|
3765
4353
|
const classes = info.classNames[0] ? `.${info.classNames[0]}` : "";
|
|
3766
4354
|
const id = info.id ? `#${info.id}` : "";
|
|
@@ -3847,7 +4435,7 @@ function InspectSimple({
|
|
|
3847
4435
|
...style
|
|
3848
4436
|
};
|
|
3849
4437
|
return /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
3850
|
-
/* @__PURE__ */
|
|
4438
|
+
/* @__PURE__ */ jsx7(
|
|
3851
4439
|
"div",
|
|
3852
4440
|
{
|
|
3853
4441
|
style: {
|
|
@@ -3881,7 +4469,7 @@ function InspectSimple({
|
|
|
3881
4469
|
borderBottom: "1px solid #1e293b"
|
|
3882
4470
|
},
|
|
3883
4471
|
children: [
|
|
3884
|
-
isMobile && /* @__PURE__ */
|
|
4472
|
+
isMobile && /* @__PURE__ */ jsx7(
|
|
3885
4473
|
"div",
|
|
3886
4474
|
{
|
|
3887
4475
|
style: {
|
|
@@ -3897,7 +4485,7 @@ function InspectSimple({
|
|
|
3897
4485
|
}
|
|
3898
4486
|
),
|
|
3899
4487
|
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
3900
|
-
/* @__PURE__ */
|
|
4488
|
+
/* @__PURE__ */ jsx7(
|
|
3901
4489
|
"span",
|
|
3902
4490
|
{
|
|
3903
4491
|
style: {
|
|
@@ -3912,25 +4500,25 @@ function InspectSimple({
|
|
|
3912
4500
|
children: identityLabel
|
|
3913
4501
|
}
|
|
3914
4502
|
),
|
|
3915
|
-
sourceLocation && /* @__PURE__ */
|
|
4503
|
+
sourceLocation && /* @__PURE__ */ jsx7(
|
|
3916
4504
|
"button",
|
|
3917
4505
|
{
|
|
3918
4506
|
type: "button",
|
|
3919
4507
|
onClick: handleOpenIDE,
|
|
3920
4508
|
title: formatSourceLocation(sourceLocation),
|
|
3921
4509
|
style: iconBtnStyle,
|
|
3922
|
-
children: /* @__PURE__ */
|
|
4510
|
+
children: /* @__PURE__ */ jsx7(ExternalLink2, { size: 14 })
|
|
3923
4511
|
}
|
|
3924
4512
|
)
|
|
3925
4513
|
] }),
|
|
3926
|
-
/* @__PURE__ */
|
|
4514
|
+
/* @__PURE__ */ jsx7(
|
|
3927
4515
|
"button",
|
|
3928
4516
|
{
|
|
3929
4517
|
type: "button",
|
|
3930
4518
|
onClick: onClose,
|
|
3931
4519
|
style: iconBtnStyle,
|
|
3932
4520
|
"aria-label": "Close inspector",
|
|
3933
|
-
children: /* @__PURE__ */
|
|
4521
|
+
children: /* @__PURE__ */ jsx7(X2, { size: 16 })
|
|
3934
4522
|
}
|
|
3935
4523
|
)
|
|
3936
4524
|
]
|
|
@@ -3946,7 +4534,7 @@ function InspectSimple({
|
|
|
3946
4534
|
gap: 8
|
|
3947
4535
|
},
|
|
3948
4536
|
children: [
|
|
3949
|
-
info?.selector && /* @__PURE__ */
|
|
4537
|
+
info?.selector && /* @__PURE__ */ jsx7(
|
|
3950
4538
|
"code",
|
|
3951
4539
|
{
|
|
3952
4540
|
style: {
|
|
@@ -3961,7 +4549,7 @@ function InspectSimple({
|
|
|
3961
4549
|
children: info.selector
|
|
3962
4550
|
}
|
|
3963
4551
|
),
|
|
3964
|
-
status && /* @__PURE__ */
|
|
4552
|
+
status && /* @__PURE__ */ jsx7(
|
|
3965
4553
|
"div",
|
|
3966
4554
|
{
|
|
3967
4555
|
style: {
|
|
@@ -3982,7 +4570,7 @@ function InspectSimple({
|
|
|
3982
4570
|
gap: 6
|
|
3983
4571
|
},
|
|
3984
4572
|
children: [
|
|
3985
|
-
/* @__PURE__ */
|
|
4573
|
+
/* @__PURE__ */ jsx7(
|
|
3986
4574
|
"button",
|
|
3987
4575
|
{
|
|
3988
4576
|
type: "button",
|
|
@@ -3990,10 +4578,10 @@ function InspectSimple({
|
|
|
3990
4578
|
style: iconActionStyle,
|
|
3991
4579
|
title: "Copy CSS selector",
|
|
3992
4580
|
"aria-label": "Copy CSS selector",
|
|
3993
|
-
children: /* @__PURE__ */
|
|
4581
|
+
children: /* @__PURE__ */ jsx7(Copy2, { size: 15 })
|
|
3994
4582
|
}
|
|
3995
4583
|
),
|
|
3996
|
-
/* @__PURE__ */
|
|
4584
|
+
/* @__PURE__ */ jsx7(
|
|
3997
4585
|
"button",
|
|
3998
4586
|
{
|
|
3999
4587
|
type: "button",
|
|
@@ -4001,10 +4589,10 @@ function InspectSimple({
|
|
|
4001
4589
|
style: iconActionStyle,
|
|
4002
4590
|
title: "Copy text content",
|
|
4003
4591
|
"aria-label": "Copy text content",
|
|
4004
|
-
children: /* @__PURE__ */
|
|
4592
|
+
children: /* @__PURE__ */ jsx7(FileText, { size: 15 })
|
|
4005
4593
|
}
|
|
4006
4594
|
),
|
|
4007
|
-
/* @__PURE__ */
|
|
4595
|
+
/* @__PURE__ */ jsx7(
|
|
4008
4596
|
"button",
|
|
4009
4597
|
{
|
|
4010
4598
|
type: "button",
|
|
@@ -4012,10 +4600,10 @@ function InspectSimple({
|
|
|
4012
4600
|
style: iconActionStyle,
|
|
4013
4601
|
title: "Copy HTML markup",
|
|
4014
4602
|
"aria-label": "Copy HTML markup",
|
|
4015
|
-
children: /* @__PURE__ */
|
|
4603
|
+
children: /* @__PURE__ */ jsx7(Code, { size: 15 })
|
|
4016
4604
|
}
|
|
4017
4605
|
),
|
|
4018
|
-
/* @__PURE__ */
|
|
4606
|
+
/* @__PURE__ */ jsx7(
|
|
4019
4607
|
"button",
|
|
4020
4608
|
{
|
|
4021
4609
|
type: "button",
|
|
@@ -4027,7 +4615,7 @@ function InspectSimple({
|
|
|
4027
4615
|
disabled: saving,
|
|
4028
4616
|
title: "Save screenshot",
|
|
4029
4617
|
"aria-label": "Save screenshot",
|
|
4030
|
-
children: /* @__PURE__ */
|
|
4618
|
+
children: /* @__PURE__ */ jsx7(Camera, { size: 15 })
|
|
4031
4619
|
}
|
|
4032
4620
|
)
|
|
4033
4621
|
]
|
|
@@ -4070,7 +4658,7 @@ var iconActionStyle = {
|
|
|
4070
4658
|
};
|
|
4071
4659
|
|
|
4072
4660
|
// src/InspectDialog/InspectDialogManager.tsx
|
|
4073
|
-
import { jsx as
|
|
4661
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
4074
4662
|
var INSPECT_DIALOG_EVENT = "anyclick:inspect";
|
|
4075
4663
|
function openInspectDialog(targetElement) {
|
|
4076
4664
|
if (typeof window === "undefined") return;
|
|
@@ -4115,7 +4703,7 @@ function InspectDialogManager({
|
|
|
4115
4703
|
window.removeEventListener(INSPECT_DIALOG_EVENT, handleInspectEvent);
|
|
4116
4704
|
};
|
|
4117
4705
|
}, []);
|
|
4118
|
-
return /* @__PURE__ */
|
|
4706
|
+
return /* @__PURE__ */ jsx8(
|
|
4119
4707
|
InspectSimple,
|
|
4120
4708
|
{
|
|
4121
4709
|
visible,
|
|
@@ -4655,7 +5243,7 @@ import {
|
|
|
4655
5243
|
} from "@ewjdev/anyclick-core";
|
|
4656
5244
|
|
|
4657
5245
|
// src/AnyclickLogo.tsx
|
|
4658
|
-
import { jsx as
|
|
5246
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4659
5247
|
function AnyclickLogo({
|
|
4660
5248
|
size = 64,
|
|
4661
5249
|
borderWidth = 2,
|
|
@@ -4681,7 +5269,7 @@ function AnyclickLogo({
|
|
|
4681
5269
|
role: onClick ? "button" : "img",
|
|
4682
5270
|
"aria-label": "Anyclick Logo",
|
|
4683
5271
|
children: [
|
|
4684
|
-
/* @__PURE__ */
|
|
5272
|
+
/* @__PURE__ */ jsx9(
|
|
4685
5273
|
"circle",
|
|
4686
5274
|
{
|
|
4687
5275
|
cx: size / 2,
|
|
@@ -4692,11 +5280,11 @@ function AnyclickLogo({
|
|
|
4692
5280
|
strokeWidth: borderWidth
|
|
4693
5281
|
}
|
|
4694
5282
|
),
|
|
4695
|
-
/* @__PURE__ */
|
|
5283
|
+
/* @__PURE__ */ jsx9(
|
|
4696
5284
|
"g",
|
|
4697
5285
|
{
|
|
4698
5286
|
transform: `translate(${(size - cursorSize) / 2}, ${(size - cursorSize) / 2})`,
|
|
4699
|
-
children: /* @__PURE__ */
|
|
5287
|
+
children: /* @__PURE__ */ jsx9(
|
|
4700
5288
|
"path",
|
|
4701
5289
|
{
|
|
4702
5290
|
d: `
|
|
@@ -4740,6 +5328,7 @@ export {
|
|
|
4740
5328
|
AnyclickContext,
|
|
4741
5329
|
AnyclickLogo,
|
|
4742
5330
|
AnyclickProvider,
|
|
5331
|
+
Button,
|
|
4743
5332
|
CURATED_STYLE_PROPERTIES,
|
|
4744
5333
|
ContextMenu,
|
|
4745
5334
|
DEFAULT_COMPACT_CONFIG,
|
|
@@ -4750,6 +5339,7 @@ export {
|
|
|
4750
5339
|
FeedbackProvider,
|
|
4751
5340
|
FunModeBridge,
|
|
4752
5341
|
INSPECT_DIALOG_EVENT,
|
|
5342
|
+
Input,
|
|
4753
5343
|
InspectDialogManager,
|
|
4754
5344
|
InspectSimple,
|
|
4755
5345
|
QuickChat,
|