@hef2024/llmasaservice-ui 0.16.8

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.
@@ -0,0 +1,40 @@
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ interface ChatStatusProps {
4
+ isLoading: boolean;
5
+ }
6
+
7
+ const ChatStatus: React.FC<ChatStatusProps> = ({ isLoading }) => {
8
+ const [dots, setDots] = useState<string[]>(['', '', '']);
9
+ const [dotIndex, setDotIndex] = useState<number>(0);
10
+
11
+ useEffect(() => {
12
+ if (isLoading) {
13
+ const interval = setInterval(() => {
14
+ setDots((prevDots) => {
15
+ const newDots = [...prevDots];
16
+ newDots[dotIndex] = '.';
17
+ setDotIndex((prevIndex) => (prevIndex + 1) % 3);
18
+ return newDots;
19
+ });
20
+ }, 500);
21
+
22
+ return () => clearInterval(interval);
23
+ } else {
24
+ setDots(['', '', '']);
25
+ setDotIndex(0);
26
+ }
27
+ }, [isLoading, dotIndex]);
28
+
29
+ return (
30
+ <div className="chat-status">
31
+ {isLoading ? (
32
+ <span className="loading-dots">
33
+ {dots.join('')}
34
+ </span>
35
+ ) : null}
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export default ChatStatus;
@@ -0,0 +1,56 @@
1
+ import React, { useState } from "react";
2
+ import "./ChatPanel.css"; // Ensure this file contains the modal styles
3
+
4
+ interface EmailModalProps {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ onSend: (to: string, from: string) => void;
8
+ defaultEmail?: string;
9
+ }
10
+
11
+ const EmailModal: React.FC<EmailModalProps> = ({ isOpen, onClose, onSend, defaultEmail }) => {
12
+ const [email, setEmail] = useState("");
13
+ const [emailFrom, setEmailFrom] = useState(defaultEmail || "");
14
+
15
+ const handleSend = () => {
16
+ onSend(email, emailFrom);
17
+ onClose();
18
+ };
19
+
20
+ if (!isOpen) return null;
21
+
22
+ return (
23
+ <div className="modal-overlay">
24
+ <div className="modal-content">
25
+ <p className="modal-text">
26
+ Email Addresses
27
+ <br /> (If multiple, comma separate them)
28
+ </p>
29
+ <p>
30
+ <input
31
+ type="email"
32
+ width="100%"
33
+ value={email}
34
+ onChange={(e) => setEmail(e.target.value)}
35
+ placeholder="To email address"
36
+ />
37
+ </p>
38
+ <p>
39
+ <input
40
+ type="email"
41
+ width="100%"
42
+ value={emailFrom}
43
+ onChange={(e) => setEmailFrom(e.target.value)}
44
+ placeholder="From email address (optional)"
45
+ />
46
+ </p>
47
+ <div className="modal-buttons">
48
+ <button onClick={onClose}>Cancel</button>
49
+ <button onClick={handleSend}>Send</button>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export default EmailModal;
@@ -0,0 +1,49 @@
1
+ import React, { useEffect } from "react";
2
+ import "./ChatPanel.css"; // Reuse styles or create specific ones
3
+
4
+ interface ToolInfoModalProps {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ data: { calls: any[]; responses: any[] } | null;
8
+ }
9
+
10
+ const ToolInfoModal: React.FC<ToolInfoModalProps> = ({
11
+ isOpen,
12
+ onClose,
13
+ data,
14
+ }) => {
15
+ if (!isOpen || !data) return null;
16
+
17
+ return (
18
+ <div className="modal-overlay" onClick={onClose}>
19
+ <div
20
+ className="modal-content tool-info-modal-content"
21
+ onClick={(e) => e.stopPropagation()}
22
+ >
23
+ <div className="tool-info-container">
24
+ <div className="tool-info-section">
25
+ <b>Tool Calls</b>
26
+ <textarea
27
+ className="tool-info-json"
28
+ readOnly
29
+ value={JSON.stringify(data.calls, null, 2)}
30
+ />
31
+ </div>
32
+ <div className="tool-info-section">
33
+ <b>Tool Responses</b>
34
+ <textarea
35
+ className="tool-info-json"
36
+ readOnly
37
+ value={JSON.stringify(data.responses, null, 2)}
38
+ />
39
+ </div>
40
+ </div>
41
+ <div className="modal-buttons">
42
+ <button onClick={onClose}>Close</button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export default ToolInfoModal;
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+
3
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ variant?: 'default' | 'secondary' | 'ghost' | 'outline' | 'destructive';
5
+ size?: 'default' | 'sm' | 'lg' | 'icon';
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * shadcn-inspired Button component
11
+ */
12
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
13
+ ({ className = '', variant = 'default', size = 'default', children, ...props }, ref) => {
14
+ const baseStyles = `
15
+ ai-button
16
+ inline-flex items-center justify-center
17
+ font-medium transition-colors
18
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
19
+ disabled:pointer-events-none disabled:opacity-50
20
+ `.trim().replace(/\s+/g, ' ');
21
+
22
+ const variantStyles: Record<string, string> = {
23
+ default: 'ai-button--primary',
24
+ secondary: 'ai-button--secondary',
25
+ ghost: 'ai-button--ghost',
26
+ outline: 'ai-button--outline',
27
+ destructive: 'ai-button--destructive',
28
+ };
29
+
30
+ const sizeStyles: Record<string, string> = {
31
+ default: 'ai-button--default',
32
+ sm: 'ai-button--sm',
33
+ lg: 'ai-button--lg',
34
+ icon: 'ai-button--icon',
35
+ };
36
+
37
+ const classes = [
38
+ baseStyles,
39
+ variantStyles[variant] || variantStyles.default,
40
+ sizeStyles[size] || sizeStyles.default,
41
+ className,
42
+ ].filter(Boolean).join(' ');
43
+
44
+ return (
45
+ <button ref={ref} className={classes} {...props}>
46
+ {children}
47
+ </button>
48
+ );
49
+ }
50
+ );
51
+
52
+ Button.displayName = 'Button';
53
+
54
+ export default Button;
55
+
56
+
57
+
@@ -0,0 +1,153 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+
3
+ export interface DialogProps {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ title?: string;
7
+ description?: string;
8
+ children: React.ReactNode;
9
+ className?: string;
10
+ }
11
+
12
+ /**
13
+ * shadcn-inspired Dialog component
14
+ */
15
+ export const Dialog: React.FC<DialogProps> = ({
16
+ isOpen,
17
+ onClose,
18
+ title,
19
+ description,
20
+ children,
21
+ className = '',
22
+ }) => {
23
+ const dialogRef = useRef<HTMLDivElement>(null);
24
+
25
+ // Close on escape key
26
+ useEffect(() => {
27
+ const handleKeyDown = (event: KeyboardEvent) => {
28
+ if (event.key === 'Escape' && isOpen) {
29
+ onClose();
30
+ }
31
+ };
32
+
33
+ document.addEventListener('keydown', handleKeyDown);
34
+ return () => document.removeEventListener('keydown', handleKeyDown);
35
+ }, [isOpen, onClose]);
36
+
37
+ // Trap focus within dialog
38
+ useEffect(() => {
39
+ if (!isOpen) return;
40
+
41
+ const dialog = dialogRef.current;
42
+ if (!dialog) return;
43
+
44
+ const focusableElements = dialog.querySelectorAll(
45
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
46
+ );
47
+ const firstFocusable = focusableElements[0] as HTMLElement;
48
+ const lastFocusable = focusableElements[focusableElements.length - 1] as HTMLElement;
49
+
50
+ const handleTabKey = (event: KeyboardEvent) => {
51
+ if (event.key !== 'Tab') return;
52
+
53
+ if (event.shiftKey) {
54
+ if (document.activeElement === firstFocusable) {
55
+ event.preventDefault();
56
+ lastFocusable?.focus();
57
+ }
58
+ } else {
59
+ if (document.activeElement === lastFocusable) {
60
+ event.preventDefault();
61
+ firstFocusable?.focus();
62
+ }
63
+ }
64
+ };
65
+
66
+ document.addEventListener('keydown', handleTabKey);
67
+ firstFocusable?.focus();
68
+
69
+ return () => document.removeEventListener('keydown', handleTabKey);
70
+ }, [isOpen]);
71
+
72
+ // Prevent body scroll when open
73
+ useEffect(() => {
74
+ if (isOpen) {
75
+ document.body.style.overflow = 'hidden';
76
+ } else {
77
+ document.body.style.overflow = '';
78
+ }
79
+
80
+ return () => {
81
+ document.body.style.overflow = '';
82
+ };
83
+ }, [isOpen]);
84
+
85
+ if (!isOpen) return null;
86
+
87
+ return (
88
+ <div className="ai-dialog-overlay" onClick={onClose}>
89
+ <div
90
+ ref={dialogRef}
91
+ className={`ai-dialog ${className}`}
92
+ role="dialog"
93
+ aria-modal="true"
94
+ aria-labelledby={title ? 'dialog-title' : undefined}
95
+ aria-describedby={description ? 'dialog-description' : undefined}
96
+ onClick={(e) => e.stopPropagation()}
97
+ >
98
+ {title && (
99
+ <div className="ai-dialog-header">
100
+ <h2 id="dialog-title" className="ai-dialog-title">
101
+ {title}
102
+ </h2>
103
+ {description && (
104
+ <p id="dialog-description" className="ai-dialog-description">
105
+ {description}
106
+ </p>
107
+ )}
108
+ </div>
109
+ )}
110
+ <div className="ai-dialog-content">{children}</div>
111
+ <button
112
+ type="button"
113
+ className="ai-dialog-close"
114
+ onClick={onClose}
115
+ aria-label="Close dialog"
116
+ >
117
+ <svg
118
+ width="16"
119
+ height="16"
120
+ viewBox="0 0 16 16"
121
+ fill="none"
122
+ xmlns="http://www.w3.org/2000/svg"
123
+ >
124
+ <path
125
+ d="M12 4L4 12M4 4L12 12"
126
+ stroke="currentColor"
127
+ strokeWidth="1.5"
128
+ strokeLinecap="round"
129
+ strokeLinejoin="round"
130
+ />
131
+ </svg>
132
+ </button>
133
+ </div>
134
+ </div>
135
+ );
136
+ };
137
+
138
+ export interface DialogFooterProps {
139
+ children: React.ReactNode;
140
+ className?: string;
141
+ }
142
+
143
+ export const DialogFooter: React.FC<DialogFooterProps> = ({
144
+ children,
145
+ className = '',
146
+ }) => {
147
+ return <div className={`ai-dialog-footer ${className}`}>{children}</div>;
148
+ };
149
+
150
+ export default Dialog;
151
+
152
+
153
+
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+
3
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
4
+ icon?: React.ReactNode;
5
+ }
6
+
7
+ /**
8
+ * shadcn-inspired Input component
9
+ */
10
+ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
11
+ ({ className = '', icon, ...props }, ref) => {
12
+ const baseStyles = 'ai-input';
13
+ const classes = [baseStyles, className].filter(Boolean).join(' ');
14
+
15
+ if (icon) {
16
+ return (
17
+ <div className="ai-input-wrapper">
18
+ <span className="ai-input-icon">{icon}</span>
19
+ <input ref={ref} className={classes} {...props} />
20
+ </div>
21
+ );
22
+ }
23
+
24
+ return <input ref={ref} className={classes} {...props} />;
25
+ }
26
+ );
27
+
28
+ Input.displayName = 'Input';
29
+
30
+ export default Input;
31
+
32
+
33
+
@@ -0,0 +1,29 @@
1
+ import React, { forwardRef } from 'react';
2
+
3
+ export interface ScrollAreaProps {
4
+ children: React.ReactNode;
5
+ className?: string;
6
+ maxHeight?: string | number;
7
+ }
8
+
9
+ /**
10
+ * shadcn-inspired ScrollArea component with ref forwarding
11
+ */
12
+ export const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(
13
+ ({ children, className = '', maxHeight }, ref) => {
14
+ const style: React.CSSProperties = maxHeight
15
+ ? { maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight }
16
+ : {};
17
+
18
+ return (
19
+ <div className={`ai-scroll-area ${className}`} style={style} ref={ref}>
20
+ <div className="ai-scroll-area-viewport">{children}</div>
21
+ </div>
22
+ );
23
+ }
24
+ );
25
+
26
+ ScrollArea.displayName = 'ScrollArea';
27
+
28
+ export default ScrollArea;
29
+
@@ -0,0 +1,156 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+
3
+ export interface SelectOption {
4
+ value: string;
5
+ label: string;
6
+ description?: string;
7
+ icon?: React.ReactNode;
8
+ }
9
+
10
+ export interface SelectProps {
11
+ value: string;
12
+ onChange: (value: string) => void;
13
+ options: SelectOption[];
14
+ placeholder?: string;
15
+ disabled?: boolean;
16
+ className?: string;
17
+ }
18
+
19
+ /**
20
+ * shadcn-inspired Select component
21
+ */
22
+ export const Select: React.FC<SelectProps> = ({
23
+ value,
24
+ onChange,
25
+ options,
26
+ placeholder = 'Select...',
27
+ disabled = false,
28
+ className = '',
29
+ }) => {
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const containerRef = useRef<HTMLDivElement>(null);
32
+
33
+ // Close on outside click
34
+ useEffect(() => {
35
+ const handleClickOutside = (event: MouseEvent) => {
36
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
37
+ setIsOpen(false);
38
+ }
39
+ };
40
+
41
+ document.addEventListener('mousedown', handleClickOutside);
42
+ return () => document.removeEventListener('mousedown', handleClickOutside);
43
+ }, []);
44
+
45
+ // Close on escape
46
+ useEffect(() => {
47
+ const handleKeyDown = (event: KeyboardEvent) => {
48
+ if (event.key === 'Escape') {
49
+ setIsOpen(false);
50
+ }
51
+ };
52
+
53
+ document.addEventListener('keydown', handleKeyDown);
54
+ return () => document.removeEventListener('keydown', handleKeyDown);
55
+ }, []);
56
+
57
+ const selectedOption = options.find((opt) => opt.value === value);
58
+
59
+ const handleSelect = (optionValue: string) => {
60
+ onChange(optionValue);
61
+ setIsOpen(false);
62
+ };
63
+
64
+ return (
65
+ <div
66
+ ref={containerRef}
67
+ className={`ai-select ${disabled ? 'ai-select--disabled' : ''} ${className}`}
68
+ >
69
+ <button
70
+ type="button"
71
+ className="ai-select-trigger"
72
+ onClick={() => !disabled && setIsOpen(!isOpen)}
73
+ disabled={disabled}
74
+ aria-haspopup="listbox"
75
+ aria-expanded={isOpen}
76
+ >
77
+ <span className="ai-select-value">
78
+ {selectedOption ? (
79
+ <>
80
+ {selectedOption.icon && (
81
+ <span className="ai-select-icon">{selectedOption.icon}</span>
82
+ )}
83
+ {selectedOption.label}
84
+ </>
85
+ ) : (
86
+ <span className="ai-select-placeholder">{placeholder}</span>
87
+ )}
88
+ </span>
89
+ <span className="ai-select-chevron">
90
+ <svg
91
+ width="12"
92
+ height="12"
93
+ viewBox="0 0 12 12"
94
+ fill="none"
95
+ xmlns="http://www.w3.org/2000/svg"
96
+ >
97
+ <path
98
+ d="M2.5 4.5L6 8L9.5 4.5"
99
+ stroke="currentColor"
100
+ strokeWidth="1.5"
101
+ strokeLinecap="round"
102
+ strokeLinejoin="round"
103
+ />
104
+ </svg>
105
+ </span>
106
+ </button>
107
+
108
+ {isOpen && (
109
+ <div className="ai-select-content" role="listbox">
110
+ {options.map((option) => (
111
+ <button
112
+ key={option.value}
113
+ type="button"
114
+ className={`ai-select-item ${option.value === value ? 'ai-select-item--selected' : ''}`}
115
+ onClick={() => handleSelect(option.value)}
116
+ role="option"
117
+ aria-selected={option.value === value}
118
+ >
119
+ {option.icon && <span className="ai-select-item-icon">{option.icon}</span>}
120
+ <div className="ai-select-item-content">
121
+ <span className="ai-select-item-label">{option.label}</span>
122
+ {option.description && (
123
+ <span className="ai-select-item-description">{option.description}</span>
124
+ )}
125
+ </div>
126
+ {option.value === value && (
127
+ <span className="ai-select-item-check">
128
+ <svg
129
+ width="12"
130
+ height="12"
131
+ viewBox="0 0 12 12"
132
+ fill="none"
133
+ xmlns="http://www.w3.org/2000/svg"
134
+ >
135
+ <path
136
+ d="M2.5 6L5 8.5L9.5 3.5"
137
+ stroke="currentColor"
138
+ strokeWidth="1.5"
139
+ strokeLinecap="round"
140
+ strokeLinejoin="round"
141
+ />
142
+ </svg>
143
+ </span>
144
+ )}
145
+ </button>
146
+ ))}
147
+ </div>
148
+ )}
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default Select;
154
+
155
+
156
+
@@ -0,0 +1,73 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+
3
+ export interface TooltipProps {
4
+ content: React.ReactNode;
5
+ children: React.ReactNode;
6
+ side?: 'top' | 'right' | 'bottom' | 'left';
7
+ delay?: number;
8
+ className?: string;
9
+ }
10
+
11
+ /**
12
+ * shadcn-inspired Tooltip component
13
+ */
14
+ export const Tooltip: React.FC<TooltipProps> = ({
15
+ content,
16
+ children,
17
+ side = 'right',
18
+ delay = 300,
19
+ className = '',
20
+ }) => {
21
+ const [isVisible, setIsVisible] = useState(false);
22
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
23
+ const triggerRef = useRef<HTMLDivElement>(null);
24
+
25
+ const showTooltip = () => {
26
+ timeoutRef.current = setTimeout(() => {
27
+ setIsVisible(true);
28
+ }, delay);
29
+ };
30
+
31
+ const hideTooltip = () => {
32
+ if (timeoutRef.current) {
33
+ clearTimeout(timeoutRef.current);
34
+ timeoutRef.current = null;
35
+ }
36
+ setIsVisible(false);
37
+ };
38
+
39
+ useEffect(() => {
40
+ return () => {
41
+ if (timeoutRef.current) {
42
+ clearTimeout(timeoutRef.current);
43
+ }
44
+ };
45
+ }, []);
46
+
47
+ return (
48
+ <div
49
+ ref={triggerRef}
50
+ className={`ai-tooltip-trigger ${className}`}
51
+ onMouseEnter={showTooltip}
52
+ onMouseLeave={hideTooltip}
53
+ onFocus={showTooltip}
54
+ onBlur={hideTooltip}
55
+ >
56
+ {children}
57
+ {isVisible && (
58
+ <div
59
+ className={`ai-tooltip ai-tooltip--${side}`}
60
+ role="tooltip"
61
+ >
62
+ <div className="ai-tooltip-content">{content}</div>
63
+ <div className="ai-tooltip-arrow" />
64
+ </div>
65
+ )}
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default Tooltip;
71
+
72
+
73
+
@@ -0,0 +1,20 @@
1
+ export { Button } from './Button';
2
+ export type { ButtonProps } from './Button';
3
+
4
+ export { Input } from './Input';
5
+ export type { InputProps } from './Input';
6
+
7
+ export { Select } from './Select';
8
+ export type { SelectProps, SelectOption } from './Select';
9
+
10
+ export { ScrollArea } from './ScrollArea';
11
+ export type { ScrollAreaProps } from './ScrollArea';
12
+
13
+ export { Tooltip } from './Tooltip';
14
+ export type { TooltipProps } from './Tooltip';
15
+
16
+ export { Dialog, DialogFooter } from './Dialog';
17
+ export type { DialogProps, DialogFooterProps } from './Dialog';
18
+
19
+
20
+