Jaspers Hub

jaspers / agents

pluginOfficialReviewed★ 0↓ 1

Research Agents: Lets you define research agents that chat and answer with citations from SEC filings, your documents, and the web, via Jaspers' research-store service.

3.6 KB
1import { useEffect, useState, type ReactElement, type ReactNode } from 'react'
2
3// The view's small buttons, icons named for screen readers and on hover. A delete takes a second
4// press, since a view's frame shows no dialog to confirm with.
5
6const CONFIRM_MS = 3000
7
8export function IconButton({
9 label,
10 onClick,
11 className,
12 children,
13}: {
14 label: string
15 onClick: () => void
16 className?: string
17 children: ReactNode
18}): ReactElement {
19 return (
20 <button
21 type="button"
22 className={className ? `ag-icon ${className}` : 'ag-icon'}
23 aria-label={label}
24 title={label}
25 onClick={onClick}
26 >
27 <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
28 {children}
29 </svg>
30 </button>
31 )
32}
33
34/** The + that starts a new one of what its list holds, at the end of its bar. */
35export function AddButton({ label, onClick }: { label: string; onClick: () => void }): ReactElement {
36 return (
37 <IconButton label={label} onClick={onClick} className="ag-add">
38 <path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" strokeWidth="1.8" />
39 </IconButton>
40 )
41}
42
43/** A trash can that turns red on a press: a second press within a few seconds deletes. */
44export function DeleteButton({ label, onDelete }: { label: string; onDelete: () => void }): ReactElement {
45 const [armed, setArmed] = useState(false)
46 useEffect(() => {
47 if (!armed) return
48 const timer = setTimeout(() => setArmed(false), CONFIRM_MS)
49 return () => clearTimeout(timer)
50 }, [armed])
51 return (
52 <IconButton
53 label={armed ? `${label}: press again` : label}
54 className={armed ? 'ag-danger' : undefined}
55 onClick={() => {
56 if (armed) {
57 setArmed(false)
58 onDelete()
59 } else {
60 setArmed(true)
61 }
62 }}
63 >
64 <path
65 d="M4.5 6.5h15M9.5 6.5V4.5h5v2M6.5 6.5l1 13h9l1-13M10.5 10v6M13.5 10v6"
66 fill="none"
67 stroke="currentColor"
68 strokeWidth="1.6"
69 />
70 </IconButton>
71 )
72}
73
74export function EditButton({ label, onClick }: { label: string; onClick: () => void }): ReactElement {
75 return (
76 <IconButton label={label} onClick={onClick}>
77 <path d="M15 5.5 18.5 9 9 18.5H5.5V15ZM12.5 8l3.5 3.5" fill="none" stroke="currentColor" strokeWidth="1.6" />
78 </IconButton>
79 )
80}
81
82export function RefreshButton({ label, onClick }: { label: string; onClick: () => void }): ReactElement {
83 return (
84 <IconButton label={label} onClick={onClick}>
85 <path d="M18.5 12a6.5 6.5 0 1 1-2-4.7M18.5 4.5v4h-4" fill="none" stroke="currentColor" strokeWidth="1.6" />
86 </IconButton>
87 )
88}
89
90export function StopButton({ label, onClick }: { label: string; onClick: () => void }): ReactElement {
91 return (
92 <IconButton label={label} onClick={onClick}>
93 <rect x="7" y="7" width="10" height="10" fill="currentColor" />
94 </IconButton>
95 )
96}
97
98/** Copies on a press, and shows a check for a moment once it has. */
99export function CopyButton({ label, onCopy }: { label: string; onCopy: () => Promise<void> }): ReactElement {
100 const [copied, setCopied] = useState(false)
101 useEffect(() => {
102 if (!copied) return
103 const timer = setTimeout(() => setCopied(false), CONFIRM_MS / 2)
104 return () => clearTimeout(timer)
105 }, [copied])
106 return (
107 <IconButton
108 label={copied ? 'Copied' : label}
109 onClick={() =>
110 void onCopy().then(
111 () => setCopied(true),
112 () => {},
113 )
114 }
115 >
116 {copied ? (
117 <path d="M5 12.5 10 17.5 19 7" fill="none" stroke="currentColor" strokeWidth="1.8" />
118 ) : (
119 <path d="M8.5 8.5h11v11h-11zM15.5 8.5v-4h-11v11h4" fill="none" stroke="currentColor" strokeWidth="1.6" />
120 )}
121 </IconButton>
122 )
123}