goji-search 1.0.0 → 1.1.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/README.md +6 -53
- package/dist/goji-search/components/elements/action-buttons.d.ts +2 -1
- package/dist/goji-search/components/elements/action-buttons.js +52 -19
- package/dist/goji-search/components/elements/auto-expanding-textarea.d.ts +8 -0
- package/dist/goji-search/components/elements/auto-expanding-textarea.js +36 -0
- package/dist/goji-search/components/elements/inspiration-menu.js +17 -17
- package/dist/goji-search/components/elements/message-list.d.ts +1 -3
- package/dist/goji-search/components/elements/message-list.js +81 -103
- package/dist/goji-search/components/elements/search-input.d.ts +2 -2
- package/dist/goji-search/components/elements/search-input.js +37 -33
- package/dist/goji-search/components/elements/suggested-questions.js +1 -1
- package/dist/goji-search/components/goji-search-component.d.ts +9 -1
- package/dist/goji-search/components/goji-search-component.js +295 -105
- package/dist/goji-search/config/company.d.ts +0 -1
- package/dist/goji-search/config/company.js +34 -7
- package/dist/goji-search/lib/goji-client.d.ts +39 -0
- package/dist/goji-search/lib/goji-client.js +55 -2
- package/dist/goji-search.css +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,70 +1,23 @@
|
|
|
1
|
-
# Goji
|
|
2
|
-
|
|
3
|
-
A lightweight React component library providing embeddable form and button components for seamless integration into your React applications.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Embeddable Form**: Easily add a customizable form to your app.
|
|
8
|
-
- **Embeddable Button**: Simple, reusable button component.
|
|
9
|
-
- **TypeScript Support**: Fully typed for a great developer experience.
|
|
10
|
-
- **Zero Dependencies**: Only requires React as a peer dependency.
|
|
1
|
+
# Goji Search
|
|
11
2
|
|
|
12
3
|
## Installation
|
|
13
4
|
|
|
14
5
|
```bash
|
|
15
|
-
npm install goji-
|
|
6
|
+
npm install goji-search
|
|
16
7
|
# or
|
|
17
|
-
yarn add goji-
|
|
8
|
+
yarn add goji-search
|
|
18
9
|
```
|
|
19
10
|
|
|
20
11
|
## Usage
|
|
21
12
|
|
|
22
13
|
```tsx
|
|
23
|
-
import {
|
|
14
|
+
import { GojiSearchComponent } from 'goji-search';
|
|
24
15
|
|
|
25
16
|
function App() {
|
|
26
17
|
return (
|
|
27
18
|
<div>
|
|
28
|
-
<
|
|
29
|
-
<GojiEmbedButton /* props here */ />
|
|
19
|
+
<GojiSearchComponent /* props here */ />
|
|
30
20
|
</div>
|
|
31
21
|
);
|
|
32
22
|
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Components
|
|
36
|
-
|
|
37
|
-
### `<GojiEmbedForm />`
|
|
38
|
-
|
|
39
|
-
A form component for embedding in your React app.
|
|
40
|
-
|
|
41
|
-
**Props:**
|
|
42
|
-
*(Document your props here, e.g. onSubmit, fields, etc.)*
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
### `<GojiEmbedButton />`
|
|
47
|
-
|
|
48
|
-
A reusable button component.
|
|
49
|
-
|
|
50
|
-
**Props:**
|
|
51
|
-
*(Document your props here, e.g. onClick, label, etc.)*
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Development
|
|
56
|
-
|
|
57
|
-
- Built with [Vite](https://vitejs.dev/) and [TypeScript](https://www.typescriptlang.org/).
|
|
58
|
-
- To build the library:
|
|
59
|
-
```bash
|
|
60
|
-
npm run build
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## License
|
|
64
|
-
|
|
65
|
-
MIT
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
*Feel free to open issues or pull requests for improvements or bug fixes!*
|
|
70
|
-
*creating dev branch*
|
|
23
|
+
```
|
|
@@ -3,10 +3,11 @@ interface ActionButtonsProps {
|
|
|
3
3
|
isHovered: boolean;
|
|
4
4
|
isStreaming: boolean;
|
|
5
5
|
searchQuery: string;
|
|
6
|
+
isRecording?: boolean;
|
|
6
7
|
onSubmit: () => void;
|
|
7
8
|
onVoiceSearch: () => void;
|
|
8
9
|
onClose: () => void;
|
|
9
10
|
onCalendarClick: () => void;
|
|
10
11
|
}
|
|
11
|
-
export declare function ActionButtons({ size, isHovered, isStreaming, searchQuery, onSubmit, onVoiceSearch, onClose, onCalendarClick, }: ActionButtonsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
export declare function ActionButtons({ size, isHovered, isStreaming, searchQuery, isRecording, onSubmit, onVoiceSearch, onClose, onCalendarClick, }: ActionButtonsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
12
13
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Mic, Calendar, ArrowUp } from "lucide-react";
|
|
3
3
|
import { useState, useId } from "react";
|
|
4
|
-
export function ActionButtons({ size, isHovered, isStreaming, searchQuery, onSubmit, onVoiceSearch, onClose, onCalendarClick, // Added calendar click handler
|
|
4
|
+
export function ActionButtons({ size, isHovered, isStreaming, searchQuery, isRecording = false, onSubmit, onVoiceSearch, onClose, onCalendarClick, // Added calendar click handler
|
|
5
5
|
}) {
|
|
6
6
|
if (size !== "m" && size !== "l" && size !== "xl")
|
|
7
7
|
return null;
|
|
@@ -49,34 +49,68 @@ export function ActionButtons({ size, isHovered, isStreaming, searchQuery, onSub
|
|
|
49
49
|
pointerEvents: "none",
|
|
50
50
|
transition: "opacity 120ms ease-in-out",
|
|
51
51
|
zIndex: 30,
|
|
52
|
-
}, children: "Send message" })] }), _jsxs("div", { style: { position: "relative", display: "inline-block", marginLeft: "0.25rem" }, onMouseEnter: () => setHoverVoice(true), onMouseLeave: () => setHoverVoice(false), children: [
|
|
53
|
-
setHoverVoice(false);
|
|
54
|
-
e.currentTarget.style.boxShadow = "inset 0 1px 0 rgba(255, 255, 255, 0.3)";
|
|
55
|
-
}, style: {
|
|
52
|
+
}, children: "Send message" })] }), _jsxs("div", { style: { position: "relative", display: "inline-block", marginLeft: "0.25rem" }, onMouseEnter: () => setHoverVoice(true), onMouseLeave: () => setHoverVoice(false), children: [_jsxs("button", { type: "button", onClick: onVoiceSearch, "aria-describedby": hoverVoice ? voiceTooltipId : undefined, onFocus: () => setHoverVoice(true), onBlur: () => setHoverVoice(false), style: {
|
|
56
53
|
borderRadius: "1.3rem",
|
|
57
|
-
border: "1px solid rgba(255, 255, 255, 0.3)",
|
|
58
|
-
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
54
|
+
border: isRecording ? "1px solid rgba(220, 38, 38, 0.5)" : "1px solid rgba(255, 255, 255, 0.3)",
|
|
55
|
+
backgroundColor: isRecording ? "rgba(220, 38, 38, 0.2)" : "rgba(255, 255, 255, 0.2)",
|
|
59
56
|
padding: size === "l" || size === "xl" ? "0.5rem" : "0.3rem",
|
|
60
57
|
display: "flex",
|
|
61
58
|
alignItems: "center",
|
|
62
59
|
justifyContent: "center",
|
|
63
|
-
|
|
64
|
-
color: "rgba(0, 0, 0, 0.75)",
|
|
60
|
+
color: isRecording ? "rgba(220, 38, 38, 0.9)" : "rgba(0, 0, 0, 0.75)",
|
|
65
61
|
backdropFilter: "blur(20px)",
|
|
66
62
|
WebkitBackdropFilter: "blur(20px)",
|
|
67
63
|
transition: "all 0.2s",
|
|
68
64
|
cursor: "pointer",
|
|
69
65
|
outline: "none",
|
|
70
66
|
opacity: size === "m" && !isHovered ? 0.7 : 1,
|
|
71
|
-
boxShadow: "inset 0 1px 0 rgba(255, 255, 255, 0.3)",
|
|
67
|
+
boxShadow: isRecording ? "inset 0 1px 0 rgba(220, 38, 38, 0.2)" : "inset 0 1px 0 rgba(255, 255, 255, 0.3)",
|
|
72
68
|
}, onMouseEnter: (e) => {
|
|
73
|
-
|
|
69
|
+
if (!isRecording) {
|
|
70
|
+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
|
|
71
|
+
}
|
|
74
72
|
}, onMouseLeave: (e) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
if (!isRecording) {
|
|
74
|
+
e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
|
|
75
|
+
}
|
|
76
|
+
}, children: [_jsx(Mic, { style: {
|
|
77
|
+
width: size === "l" || size === "xl" ? "1rem" : "0.875rem",
|
|
78
|
+
height: size === "l" || size === "xl" ? "1rem" : "0.875rem",
|
|
79
|
+
} }), isRecording && (_jsxs("span", { "aria-hidden": "true", style: {
|
|
80
|
+
display: "inline-flex",
|
|
81
|
+
alignItems: "flex-end",
|
|
82
|
+
gap: "2px",
|
|
83
|
+
marginLeft: size === "l" || size === "xl" ? "6px" : "4px",
|
|
84
|
+
height: size === "l" || size === "xl" ? "1rem" : "0.875rem",
|
|
85
|
+
}, children: [_jsx("span", { style: {
|
|
86
|
+
width: "2px",
|
|
87
|
+
height: "55%",
|
|
88
|
+
backgroundColor: "rgba(220, 38, 38, 0.95)",
|
|
89
|
+
borderRadius: "1px",
|
|
90
|
+
transformOrigin: "bottom",
|
|
91
|
+
animation: "eqPulse 900ms ease-in-out 0ms infinite",
|
|
92
|
+
} }), _jsx("span", { style: {
|
|
93
|
+
width: "2px",
|
|
94
|
+
height: "85%",
|
|
95
|
+
backgroundColor: "rgba(220, 38, 38, 0.95)",
|
|
96
|
+
borderRadius: "1px",
|
|
97
|
+
transformOrigin: "bottom",
|
|
98
|
+
animation: "eqPulse 900ms ease-in-out 100ms infinite",
|
|
99
|
+
} }), _jsx("span", { style: {
|
|
100
|
+
width: "2px",
|
|
101
|
+
height: "65%",
|
|
102
|
+
backgroundColor: "rgba(220, 38, 38, 0.95)",
|
|
103
|
+
borderRadius: "1px",
|
|
104
|
+
transformOrigin: "bottom",
|
|
105
|
+
animation: "eqPulse 900ms ease-in-out 200ms infinite",
|
|
106
|
+
} }), _jsx("span", { style: {
|
|
107
|
+
width: "2px",
|
|
108
|
+
height: "78%",
|
|
109
|
+
backgroundColor: "rgba(220, 38, 38, 0.95)",
|
|
110
|
+
borderRadius: "1px",
|
|
111
|
+
transformOrigin: "bottom",
|
|
112
|
+
animation: "eqPulse 900ms ease-in-out 300ms infinite",
|
|
113
|
+
} })] }))] }), _jsx("span", { id: voiceTooltipId, role: "tooltip", style: {
|
|
80
114
|
position: "absolute",
|
|
81
115
|
top: "calc(100% + 0.4rem)",
|
|
82
116
|
left: "50%",
|
|
@@ -92,14 +126,13 @@ export function ActionButtons({ size, isHovered, isStreaming, searchQuery, onSub
|
|
|
92
126
|
pointerEvents: "none",
|
|
93
127
|
transition: "opacity 120ms ease-in-out",
|
|
94
128
|
zIndex: 30,
|
|
95
|
-
}, children: "Voice input" })] }), _jsxs("div", { style: { position: "relative", display: "inline-block", marginRight: "0.25rem" }, onMouseEnter: () => setHoverCalendar(true), onMouseLeave: () => setHoverCalendar(false), children: [_jsxs("button", { type: "button", disabled: isStreaming, onClick: onCalendarClick, "aria-describedby": hoverCalendar ? calendarTooltipId : undefined, onFocus: () => setHoverCalendar(true), onBlur: () => setHoverCalendar(false), style: {
|
|
129
|
+
}, children: isRecording ? "Stop recording" : "Voice input" })] }), _jsxs("div", { style: { position: "relative", display: "inline-block", marginRight: "0.25rem" }, onMouseEnter: () => setHoverCalendar(true), onMouseLeave: () => setHoverCalendar(false), children: [_jsxs("button", { type: "button", disabled: isStreaming, onClick: onCalendarClick, "aria-describedby": hoverCalendar ? calendarTooltipId : undefined, onFocus: () => setHoverCalendar(true), onBlur: () => setHoverCalendar(false), style: {
|
|
96
130
|
borderRadius: "1.3rem",
|
|
97
131
|
border: "1px solid rgba(255, 255, 255, 0.3)",
|
|
98
132
|
backgroundColor: "rgb(0, 0, 0)",
|
|
99
133
|
padding: size === "l" || size === "xl" ? "0.5rem" : "0.4rem",
|
|
100
134
|
display: size === "l" || size === "m" || size === "xl" ? "flex" : "none",
|
|
101
135
|
alignItems: "center",
|
|
102
|
-
outlineColor: "none",
|
|
103
136
|
justifyContent: "center",
|
|
104
137
|
color: "rgb(255, 255, 255)",
|
|
105
138
|
transition: "all 0.2s",
|
|
@@ -113,7 +146,7 @@ export function ActionButtons({ size, isHovered, isStreaming, searchQuery, onSub
|
|
|
113
146
|
marginLeft: "0.5rem",
|
|
114
147
|
fontSize: "0.7rem",
|
|
115
148
|
fontWeight: 400,
|
|
116
|
-
display: size === "m" ? "none" : "block",
|
|
149
|
+
display: size === "m" || searchQuery.length > 0 ? "none" : "block",
|
|
117
150
|
transition: "all 0.4s",
|
|
118
151
|
}, children: "Book a call" })] }), _jsx("span", { id: calendarTooltipId, role: "tooltip", style: {
|
|
119
152
|
position: "absolute",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
interface AutoExpandingTextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
5
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const AutoExpandingTextarea: React.ForwardRefExoticComponent<AutoExpandingTextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
|
|
8
|
+
export default AutoExpandingTextarea;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect, forwardRef } from "react";
|
|
3
|
+
const AutoExpandingTextarea = forwardRef(({ value, onChange, onKeyDown, className, style, ...props }, ref) => {
|
|
4
|
+
const textareaRef = useRef(null);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const el = ref?.current || textareaRef.current;
|
|
7
|
+
if (el) {
|
|
8
|
+
el.style.height = "auto";
|
|
9
|
+
const scrollHeight = el.scrollHeight;
|
|
10
|
+
const minHeight = 24; // one line height
|
|
11
|
+
const maxHeight = 160; // max ~5 lines
|
|
12
|
+
el.style.height = `${Math.min(Math.max(scrollHeight, minHeight), maxHeight)}px`;
|
|
13
|
+
}
|
|
14
|
+
}, [value]);
|
|
15
|
+
return (_jsx("textarea", { ref: ref || textareaRef, value: value, onChange: onChange, onKeyDown: (e) => {
|
|
16
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
onKeyDown?.(e);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
onKeyDown?.(e);
|
|
22
|
+
}
|
|
23
|
+
}, placeholder: "Chat with me...", className: `w-full bg-white placeholder:text-gray-500 rounded-lg text-sm sm:text-base ${className}`, style: {
|
|
24
|
+
lineHeight: "1.5rem",
|
|
25
|
+
padding: "0.5rem 0.75rem",
|
|
26
|
+
minHeight: "1.5rem",
|
|
27
|
+
maxHeight: "160px",
|
|
28
|
+
outline: "none",
|
|
29
|
+
overflow: "hidden", // ← hide scrollbars
|
|
30
|
+
resize: "none", // ← remove corner handle
|
|
31
|
+
transition: "height 0.15s ease-out",
|
|
32
|
+
...style,
|
|
33
|
+
}, rows: 1, ...props }));
|
|
34
|
+
});
|
|
35
|
+
AutoExpandingTextarea.displayName = "AutoExpandingTextarea";
|
|
36
|
+
export default AutoExpandingTextarea;
|
|
@@ -9,11 +9,14 @@ export function InspirationMenu({ questions, onQuestionClick, onClose }) {
|
|
|
9
9
|
maxWidth: "20rem",
|
|
10
10
|
borderRadius: "0.75rem",
|
|
11
11
|
border: "1px solid rgba(85, 85, 85, 0.18)",
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Use opaque background to fully mask underlying text
|
|
13
|
+
backgroundColor: "#ffffff",
|
|
14
|
+
// Remove blur so underlying content does not bleed through visually
|
|
15
|
+
boxShadow: "0 8px 28px rgba(0, 0, 0, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.6)",
|
|
14
16
|
padding: "0.625rem",
|
|
15
17
|
animation: "slideUpMenu 0.25s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
16
|
-
|
|
18
|
+
// Ensure the popover sits above chat bubbles and other content
|
|
19
|
+
zIndex: 1000,
|
|
17
20
|
}, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { style: {
|
|
18
21
|
display: "flex",
|
|
19
22
|
justifyContent: "space-between",
|
|
@@ -27,7 +30,7 @@ export function InspirationMenu({ questions, onQuestionClick, onClose }) {
|
|
|
27
30
|
color: "rgba(0, 0, 0, 0.5)",
|
|
28
31
|
letterSpacing: "0.05em",
|
|
29
32
|
textTransform: "capitalize",
|
|
30
|
-
}, children: "
|
|
33
|
+
}, children: "Suggested" }), _jsx("button", { type: "button", onClick: onClose, style: {
|
|
31
34
|
padding: "0.25rem",
|
|
32
35
|
borderRadius: "0.375rem",
|
|
33
36
|
border: "1px solid rgba(255, 255, 255, 0.2)",
|
|
@@ -52,28 +55,25 @@ export function InspirationMenu({ questions, onQuestionClick, onClose }) {
|
|
|
52
55
|
}, children: _jsx(X, { style: { width: "0.875rem", height: "0.875rem" } }) })] }), _jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.375rem" }, children: questions.map((question, index) => (_jsx("button", { type: "button", onClick: () => onQuestionClick(question), style: {
|
|
53
56
|
padding: "0.5rem 0.75rem",
|
|
54
57
|
borderRadius: "1.3rem",
|
|
55
|
-
border: "1px solid rgba(
|
|
56
|
-
backgroundColor: "
|
|
57
|
-
color: "rgba(0, 0, 0, 0.
|
|
58
|
+
border: "1px solid rgba(0, 0, 0, 0.06)",
|
|
59
|
+
backgroundColor: "#ffffff",
|
|
60
|
+
color: "rgba(0, 0, 0, 0.85)",
|
|
58
61
|
fontSize: "0.75rem",
|
|
59
62
|
textAlign: "left",
|
|
60
63
|
cursor: "pointer",
|
|
61
64
|
transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
62
|
-
|
|
63
|
-
WebkitBackdropFilter: "blur(20px) saturate(180%)",
|
|
64
|
-
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.04), inset 0 1px 0 rgba(255, 255, 255, 0.3)",
|
|
65
|
+
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.06)",
|
|
65
66
|
outline: "none",
|
|
66
67
|
lineHeight: "1.3",
|
|
67
68
|
}, onMouseEnter: (e) => {
|
|
68
|
-
e.currentTarget.style.backgroundColor = "rgba(0, 122, 255, 0.
|
|
69
|
-
e.currentTarget.style.borderColor = "rgba(0, 122, 255, 0.
|
|
69
|
+
e.currentTarget.style.backgroundColor = "rgba(0, 122, 255, 0.1)";
|
|
70
|
+
e.currentTarget.style.borderColor = "rgba(0, 122, 255, 0.25)";
|
|
70
71
|
e.currentTarget.style.transform = "translateY(-1px)";
|
|
71
|
-
e.currentTarget.style.boxShadow =
|
|
72
|
-
"0 3px 8px rgba(0, 122, 255, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.4)";
|
|
72
|
+
e.currentTarget.style.boxShadow = "0 3px 8px rgba(0, 122, 255, 0.12)";
|
|
73
73
|
}, onMouseLeave: (e) => {
|
|
74
|
-
e.currentTarget.style.backgroundColor = "
|
|
75
|
-
e.currentTarget.style.borderColor = "rgba(
|
|
74
|
+
e.currentTarget.style.backgroundColor = "#ffffff";
|
|
75
|
+
e.currentTarget.style.borderColor = "rgba(0, 0, 0, 0.06)";
|
|
76
76
|
e.currentTarget.style.transform = "translateY(0)";
|
|
77
|
-
e.currentTarget.style.boxShadow = "0 1px 2px rgba(0, 0, 0, 0.
|
|
77
|
+
e.currentTarget.style.boxShadow = "0 1px 2px rgba(0, 0, 0, 0.06)";
|
|
78
78
|
}, children: question }, index))) })] }));
|
|
79
79
|
}
|
|
@@ -16,9 +16,7 @@ interface MessageListProps {
|
|
|
16
16
|
messages: Message[];
|
|
17
17
|
isStreaming: boolean;
|
|
18
18
|
aiAvatarSrc?: string;
|
|
19
|
-
onClose?: () => void;
|
|
20
|
-
onExpand?: () => void;
|
|
21
19
|
size?: "m" | "l" | "xl" | "s" | "xs";
|
|
22
20
|
}
|
|
23
|
-
export declare function MessageList({ messages, isStreaming, aiAvatarSrc,
|
|
21
|
+
export declare function MessageList({ messages, isStreaming, aiAvatarSrc, size, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
24
22
|
export {};
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useEffect, useState
|
|
2
|
+
import { useRef, useEffect, useState } from "react";
|
|
3
3
|
import ReactMarkdown from "react-markdown";
|
|
4
4
|
import remarkGfm from "remark-gfm";
|
|
5
|
-
import { ExternalLink
|
|
6
|
-
export function MessageList({ messages, isStreaming, aiAvatarSrc = '',
|
|
5
|
+
import { ExternalLink } from "lucide-react";
|
|
6
|
+
export function MessageList({ messages, isStreaming, aiAvatarSrc = '', size = 'l', }) {
|
|
7
7
|
const messagesEndRef = useRef(null);
|
|
8
|
-
const [
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const [expandedSourcesByMessageIndex, setExpandedSourcesByMessageIndex] = useState({});
|
|
9
|
+
const toggleSources = (messageIndex) => {
|
|
10
|
+
setExpandedSourcesByMessageIndex((prev) => ({
|
|
11
|
+
...prev,
|
|
12
|
+
[messageIndex]: !prev[messageIndex],
|
|
13
|
+
}));
|
|
14
|
+
};
|
|
15
|
+
const convertCitationLinksToUrls = (markdown, sources) => {
|
|
16
|
+
if (!markdown || !sources || sources.length === 0)
|
|
17
|
+
return markdown;
|
|
18
|
+
// Replace [text](N) where N is a positive integer referencing sources[N-1]
|
|
19
|
+
return markdown.replace(/\[([^\]]+)\]\((\d+)\)/g, (match, text, numStr) => {
|
|
20
|
+
const num = parseInt(numStr, 10);
|
|
21
|
+
if (Number.isNaN(num) || num < 1 || num > sources.length)
|
|
22
|
+
return match;
|
|
23
|
+
const url = sources[num - 1]?.url;
|
|
24
|
+
if (!url)
|
|
25
|
+
return match;
|
|
26
|
+
return `[${text}](${url})`;
|
|
27
|
+
});
|
|
28
|
+
};
|
|
13
29
|
useEffect(() => {
|
|
14
30
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
15
31
|
}, [messages]);
|
|
@@ -19,101 +35,39 @@ export function MessageList({ messages, isStreaming, aiAvatarSrc = '', onClose,
|
|
|
19
35
|
position: 'relative',
|
|
20
36
|
flex: 1,
|
|
21
37
|
overflowY: "auto",
|
|
22
|
-
marginTop: "
|
|
38
|
+
marginTop: "1.3rem",
|
|
23
39
|
display: "flex",
|
|
24
40
|
flexDirection: "column",
|
|
25
|
-
gap: "0.
|
|
41
|
+
gap: "0.5rem",
|
|
26
42
|
padding: "0.5rem",
|
|
27
43
|
borderRadius: "0.75rem",
|
|
28
44
|
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
|
29
45
|
border: "1px solid rgba(255, 255, 255, 0.15)",
|
|
30
|
-
}, className: "custom-scrollbar", children: [
|
|
31
|
-
borderRadius: '1.3rem',
|
|
32
|
-
border: '1px solid rgba(255, 255, 255, 0.3)',
|
|
33
|
-
backgroundColor: 'rgba(255, 255, 255, 0.12)',
|
|
34
|
-
padding: '0.375rem',
|
|
35
|
-
display: 'flex',
|
|
36
|
-
alignItems: 'center',
|
|
37
|
-
justifyContent: 'center',
|
|
38
|
-
color: 'rgba(0, 0, 0, 0.75)',
|
|
39
|
-
transition: 'all 0.15s',
|
|
40
|
-
cursor: 'pointer',
|
|
41
|
-
outline: 'none',
|
|
42
|
-
boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.2)',
|
|
43
|
-
}, children: size === 'xl' ? _jsx(Minimize2, { style: { width: '0.9rem', height: '0.9rem' } }) : _jsx(Maximize2, { style: { width: '0.9rem', height: '0.9rem' } }) }), _jsx("span", { id: expandTooltipId, role: "tooltip", style: {
|
|
44
|
-
position: 'absolute',
|
|
45
|
-
top: 'calc(100% + 0.4rem)',
|
|
46
|
-
left: '50%',
|
|
47
|
-
transform: 'translateX(-50%)',
|
|
48
|
-
whiteSpace: 'nowrap',
|
|
49
|
-
background: 'rgba(0,0,0,0.9)',
|
|
50
|
-
color: '#fff',
|
|
51
|
-
padding: '4px 6px',
|
|
52
|
-
borderRadius: '1.3rem',
|
|
53
|
-
fontSize: '0.55rem',
|
|
54
|
-
boxShadow: '0 6px 18px rgba(0,0,0,0.35)',
|
|
55
|
-
opacity: hoverTopExpand ? 1 : 0,
|
|
56
|
-
pointerEvents: 'none',
|
|
57
|
-
transition: 'opacity 120ms ease-in-out',
|
|
58
|
-
zIndex: 30,
|
|
59
|
-
}, children: size === 'xl' ? 'Restore' : 'Expand' })] })), onClose && (_jsxs("div", { style: { position: 'relative' }, children: [_jsx("button", { type: "button", onClick: onClose, onMouseEnter: () => setHoverTopClose(true), onMouseLeave: () => setHoverTopClose(false), "aria-label": "Close", "aria-describedby": hoverTopClose ? xTooltipId : undefined, style: {
|
|
60
|
-
borderRadius: '1.3rem',
|
|
61
|
-
border: '1px solid rgba(255, 255, 255, 0.3)',
|
|
62
|
-
backgroundColor: 'rgba(255, 255, 255, 0.12)',
|
|
63
|
-
padding: '0.375rem',
|
|
64
|
-
display: 'flex',
|
|
65
|
-
alignItems: 'center',
|
|
66
|
-
justifyContent: 'center',
|
|
67
|
-
color: 'rgba(0, 0, 0, 0.75)',
|
|
68
|
-
transition: 'all 0.15s',
|
|
69
|
-
cursor: 'pointer',
|
|
70
|
-
outline: 'none',
|
|
71
|
-
boxShadow: 'inset 0 1px 0 rgba(255, 255, 255, 0.2)',
|
|
72
|
-
}, children: _jsx(X, { style: { width: '0.9rem', height: '0.9rem' } }) }), _jsx("span", { id: xTooltipId, role: "tooltip", style: {
|
|
73
|
-
position: 'absolute',
|
|
74
|
-
top: 'calc(100% + 0.4rem)',
|
|
75
|
-
left: '50%',
|
|
76
|
-
transform: 'translateX(-50%)',
|
|
77
|
-
whiteSpace: 'nowrap',
|
|
78
|
-
background: 'rgba(0,0,0,0.9)',
|
|
79
|
-
color: '#fff',
|
|
80
|
-
padding: '4px 6px',
|
|
81
|
-
borderRadius: '1.3rem',
|
|
82
|
-
fontSize: '0.55rem',
|
|
83
|
-
boxShadow: '0 6px 18px rgba(0,0,0,0.35)',
|
|
84
|
-
opacity: hoverTopClose ? 1 : 0,
|
|
85
|
-
pointerEvents: 'none',
|
|
86
|
-
transition: 'opacity 120ms ease-in-out',
|
|
87
|
-
zIndex: 30,
|
|
88
|
-
}, children: "Close" })] }))] }), messages.map((message, index) => (_jsxs("div", { style: {
|
|
46
|
+
}, className: "custom-scrollbar", children: [messages.map((message, index) => (_jsxs("div", { style: {
|
|
89
47
|
display: "flex",
|
|
90
|
-
|
|
91
|
-
alignItems: message.role === "user" ? "flex-end" : "flex-start",
|
|
48
|
+
justifyContent: message.role === "user" ? "flex-end" : "flex-start",
|
|
92
49
|
animation: "fadeIn 0.3s ease-in",
|
|
93
|
-
gap: "0.
|
|
50
|
+
gap: "0.4rem",
|
|
94
51
|
}, children: [_jsx("div", { style: {
|
|
95
52
|
display: "flex",
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
flexDirection: message.role === "user" ? "row-reverse" : "row"
|
|
53
|
+
gap: "0.4rem",
|
|
54
|
+
alignItems: message.role === "user" ? "flex-end" : "flex-start"
|
|
99
55
|
}, children: _jsx("div", { style: {
|
|
100
|
-
width: "1.
|
|
101
|
-
height:
|
|
102
|
-
borderRadius:
|
|
103
|
-
backgroundColor:
|
|
56
|
+
width: message.role === "user" ? "0" : "1.4rem",
|
|
57
|
+
height: message.role === "user" ? "0" : "auto",
|
|
58
|
+
borderRadius: "none",
|
|
59
|
+
backgroundColor: "transparent",
|
|
104
60
|
display: "flex",
|
|
105
61
|
alignItems: "center",
|
|
106
62
|
justifyContent: "center",
|
|
107
63
|
fontSize: "0.75rem",
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}, children: message.role === "user" ? (_jsx(User, { size: 16, color: "rgb(114, 126, 237)" })) : (aiAvatarSrc ? (_jsx("img", { src: aiAvatarSrc, alt: "AI Avatar", style: {
|
|
64
|
+
border: "none",
|
|
65
|
+
boxShadow: "none",
|
|
66
|
+
}, children: message.role === "user" ? (
|
|
67
|
+
// <User size={16} color="rgba(0, 0, 0, 1)" />
|
|
68
|
+
_jsx("div", {})) : (aiAvatarSrc ? (_jsx("img", { src: aiAvatarSrc, alt: "AI Avatar", style: {
|
|
114
69
|
width: "100%",
|
|
115
70
|
height: "100%",
|
|
116
|
-
borderRadius: "inherit",
|
|
117
71
|
objectFit: "cover"
|
|
118
72
|
} })) : (_jsx("div", { style: {
|
|
119
73
|
width: "100%",
|
|
@@ -122,34 +76,58 @@ export function MessageList({ messages, isStreaming, aiAvatarSrc = '', onClose,
|
|
|
122
76
|
alignItems: "center",
|
|
123
77
|
justifyContent: "center",
|
|
124
78
|
fontSize: "0.75rem",
|
|
125
|
-
color: "
|
|
79
|
+
color: "rgba(217, 219, 234, 1)"
|
|
126
80
|
}, children: "AI" }))) }) }), _jsxs("div", { style: {
|
|
127
81
|
maxWidth: "85%",
|
|
128
|
-
padding: message.role === "user" ? "0.275rem 0.
|
|
82
|
+
padding: message.role === "user" ? "0.275rem 0.5rem" : "0.5rem 0",
|
|
129
83
|
borderRadius: message.role === "user" ? "1rem" : "0",
|
|
130
84
|
fontSize: "0.875rem",
|
|
131
85
|
marginLeft: message.role === "user" ? "0" : "1rem",
|
|
132
86
|
marginRight: message.role === "user" ? "1rem" : "0",
|
|
133
|
-
lineHeight: "1.
|
|
87
|
+
lineHeight: "1.4",
|
|
134
88
|
backgroundColor: message.role === "user" ? "rgb(114, 126, 237)" : "transparent",
|
|
135
89
|
color: message.role === "user" ? "white" : "rgba(0, 0, 0, 0.85)",
|
|
136
90
|
border: message.role === "user" ? "1px solid rgba(255, 255, 255, 0.25)" : "none",
|
|
137
91
|
}, children: [message.role === "user" ? (_jsx("p", { style: { margin: 0, whiteSpace: "pre-wrap" }, children: message.content })) : (_jsx("div", { className: "markdown-content", style: {
|
|
138
92
|
fontSize: "0.875rem",
|
|
139
|
-
lineHeight: "1.
|
|
93
|
+
lineHeight: "1.4",
|
|
140
94
|
color: "rgba(0, 0, 0, 0.85)",
|
|
141
|
-
}, children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm],
|
|
142
|
-
|
|
143
|
-
|
|
95
|
+
}, children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: {
|
|
96
|
+
a: ({ href, children, ...props }) => {
|
|
97
|
+
const hasSources = Array.isArray(message.sources) && message.sources.length > 0;
|
|
98
|
+
if (!hasSources) {
|
|
99
|
+
const anyProps = props;
|
|
100
|
+
return (_jsx("span", { className: anyProps?.className, style: anyProps?.style, children: children }));
|
|
101
|
+
}
|
|
102
|
+
return (_jsx("a", { href: href || '', target: "_blank", rel: "noopener noreferrer", ...props, children: children }));
|
|
103
|
+
},
|
|
104
|
+
}, children: convertCitationLinksToUrls(message.content, message.sources) }) })), message.role === "assistant" && message.sources && message.sources.length > 0 && (_jsxs("div", { style: {
|
|
105
|
+
marginTop: "0.25rem",
|
|
106
|
+
paddingTop: "0.25rem",
|
|
144
107
|
borderTop: "1px solid rgba(0, 0, 0, 0.1)",
|
|
145
|
-
}, children: [
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
108
|
+
}, children: [_jsxs("div", { style: {
|
|
109
|
+
display: "flex",
|
|
110
|
+
alignItems: "center",
|
|
111
|
+
justifyContent: "space-between",
|
|
112
|
+
gap: "0.5rem",
|
|
113
|
+
marginBottom: "0.25rem",
|
|
114
|
+
}, children: [_jsx("p", { style: {
|
|
115
|
+
fontSize: "0.65rem",
|
|
116
|
+
fontWeight: 600,
|
|
117
|
+
textTransform: "uppercase",
|
|
118
|
+
letterSpacing: "0.05em",
|
|
119
|
+
color: "rgba(0, 0, 0, 0.5)",
|
|
120
|
+
margin: 0,
|
|
121
|
+
}, children: "Sources" }), _jsx("button", { type: "button", onClick: () => toggleSources(index), style: {
|
|
122
|
+
borderRadius: "0.5rem",
|
|
123
|
+
border: "1px solid rgba(255, 255, 255, 0.3)",
|
|
124
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
125
|
+
padding: "0.2rem 0.4rem",
|
|
126
|
+
fontSize: "0.7rem",
|
|
127
|
+
color: "rgba(0, 0, 0, 0.75)",
|
|
128
|
+
cursor: "pointer",
|
|
129
|
+
outline: "none",
|
|
130
|
+
}, children: expandedSourcesByMessageIndex[index] ? "Hide" : "Show" })] }), expandedSourcesByMessageIndex[index] && (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: message.sources.map((source) => (_jsxs("a", { href: source.url, target: "_blank", rel: "noopener noreferrer", style: {
|
|
153
131
|
display: "flex",
|
|
154
132
|
alignItems: "center",
|
|
155
133
|
gap: "0.5rem",
|
|
@@ -167,8 +145,8 @@ export function MessageList({ messages, isStreaming, aiAvatarSrc = '', onClose,
|
|
|
167
145
|
borderRadius: "0.375rem",
|
|
168
146
|
fontSize: "0.65rem",
|
|
169
147
|
fontWeight: 600,
|
|
170
|
-
}, children: ["#", source.index] }), _jsx("span", { style: { flex: 1, wordBreak: "break-all" }, children: source.url }), _jsx(ExternalLink, { style: { width: "0.875rem", height: "0.875rem", opacity: 0.6 } })] }, source.index))) })] }))] })] }, index))), showThinkingDots && (_jsx("div", { style: { display: "flex", justifyContent: "flex-start" }, children: _jsxs("div", { style: {
|
|
171
|
-
padding: "0.
|
|
148
|
+
}, children: ["#", source.index] }), _jsx("span", { style: { flex: 1, wordBreak: "break-all" }, children: source.url }), _jsx(ExternalLink, { style: { width: "0.875rem", height: "0.875rem", opacity: 0.6 } })] }, source.index))) }))] }))] })] }, index))), showThinkingDots && (_jsx("div", { style: { display: "flex", justifyContent: "flex-start" }, children: _jsxs("div", { style: {
|
|
149
|
+
padding: "0.25rem 0.6rem",
|
|
172
150
|
borderRadius: "1rem",
|
|
173
151
|
display: "flex",
|
|
174
152
|
gap: "0.375rem",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type React from "react";
|
|
2
2
|
interface SearchInputProps {
|
|
3
3
|
value: string;
|
|
4
|
-
onChange: (e: React.ChangeEvent<
|
|
4
|
+
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
5
5
|
onFocus: () => void;
|
|
6
6
|
onBlur: () => void;
|
|
7
7
|
placeholder: string;
|
|
8
8
|
size: "xs" | "s" | "m" | "l" | "xl";
|
|
9
|
-
inputRef: React.RefObject<
|
|
9
|
+
inputRef: React.RefObject<HTMLTextAreaElement>;
|
|
10
10
|
inspirationQuestions: string[];
|
|
11
11
|
onInspirationClick: (question: string) => void;
|
|
12
12
|
sparkleRef: React.RefObject<HTMLDivElement>;
|