Skip to content

Commit 887aecd

Browse files
feat: various bug fixes and enhancements
This patch includes various bug fixes and enhancements: - The widget README now better reflects the updated Vite setup. - We now use `neutral` for `shadcn` components, instead of `slate`. - A type checking script was added to the widget web app. - Downloaded PDFs now include a timestamp to reduce file name conflicts. - When deep research is enabled, we now show deep research plan suggestions instead of the normal prompt suggestions. - Prompt suggestion items now show tagged datasets as mentions instead of rendering the markdown link string. - Various components were moved from the widget app to the UI package. - Research plans are now always opened by default. - We now import SVGs using default imports instead of named imports. - Charts are now rendered without `unsafe-eval` JavaScript. This allows for more strict content-security policies (CSP).
1 parent 6f760f6 commit 887aecd

File tree

82 files changed

+7417
-5859
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+7417
-5859
lines changed

apps/widget/README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,27 @@
22

33
## Development
44

5-
This is a `create-react-app`. You can build it like so:
5+
This is a React web application built with Vite.
6+
You can build it like so:
67

78
- Install dependencies
89

910
```bash
10-
npm install
11+
pnpm install
1112
```
1213

1314
- (Optional) For developing, start app on localhost
1415

1516
```bash
16-
npm run start
17+
pnpm dev
1718
```
1819

1920
- Run build command
2021

2122
```bash
22-
npm run build
23+
pnpm build
2324
```
2425

25-
- The build files should now be in `build` folder, ready to be deployed and served. This includes
26-
- `index.html` The entry point for the react app
27-
- `loader.js` The script that is used to load the react widget in the target website
28-
2926
## Documentation
3027

3128
Please visit [Numbers Station Widget API Documentation](https://docs.numbersstation.ai/api/guides/embed-widget/) for the embed widget implementation guide.

apps/widget/components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"tailwind": {
66
"config": "tailwind.config.js",
77
"css": "src/index.css",
8-
"baseColor": "slate",
8+
"baseColor": "neutral",
99
"cssVariables": true
1010
},
1111
"aliases": {

apps/widget/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ns/widget",
3-
"version": "188.21.2",
3+
"version": "192.5.0",
44
"private": true,
55
"type": "module",
66
"homepage": ".",
@@ -10,7 +10,8 @@
1010
"serve": "vite preview",
1111
"storybook": "storybook dev -p 6006",
1212
"build-storybook": "storybook build",
13-
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --max-warnings=100",
13+
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --max-warnings=70",
14+
"typecheck": "tsc --noEmit",
1415
"format": "prettier src --write"
1516
},
1617
"prettier": "@ns/prettier-config",
@@ -70,7 +71,7 @@
7071
"sonner": "^1.7.0",
7172
"tailwindcss-animate": "^1.0.7",
7273
"usehooks-ts": "^3.1.0",
73-
"vega-embed": "^6.26.0",
74+
"vega-embed": "^6.29.0",
7475
"web-vitals": "^4.2.4",
7576
"zod": "^3.23.8",
7677
"zustand": "^5.0.1"
@@ -129,4 +130,4 @@
129130
"node": ">=22"
130131
},
131132
"packageManager": "[email protected]"
132-
}
133+
}

apps/widget/src/components/DownloadPDFButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import download from 'downloadjs'
22
import { toast } from 'sonner'
33

44
import { getChatSummaryPdf } from '@ns/public-api'
5+
import { Button } from '@ns/ui/atoms/Button'
56
import { DEFAULT_CONVERSATION_NAME } from '@ns/ui/utils/constants'
67

7-
import { Button } from 'components/Button'
8-
import { ReactComponent as PDF } from 'lib/icons/pdf.svg?react'
8+
import PDF from 'lib/icons/pdf.svg?react'
99
import { useChatStore } from 'lib/stores/chat'
1010
import { getAccount } from 'lib/stores/user'
1111

@@ -18,7 +18,7 @@ export function DownloadPDFButton({ messageId }: { messageId: string }) {
1818
)
1919
download(
2020
data as Blob,
21-
`${currentChat?.name ?? DEFAULT_CONVERSATION_NAME} Summary.pdf`,
21+
`${currentChat?.name ?? DEFAULT_CONVERSATION_NAME} Summary ${new Date().toLocaleString()}.pdf`,
2222
'application/pdf',
2323
)
2424
}

apps/widget/src/components/ErrorAlert.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AlertCircle } from 'lucide-react'
22

3-
import { Alert, AlertDescription, AlertTitle } from 'components/Alert'
3+
import { Alert, AlertDescription, AlertTitle } from '@ns/ui/atoms/Alert'
44

55
interface Props {
66
message: string

apps/widget/src/components/ErrorBoundary.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import {
44
type FallbackProps,
55
} from 'react-error-boundary'
66

7+
import { Button } from '@ns/ui/atoms/Button'
78
import { ErrorBoundaryContent } from '@ns/ui/molecules/ErrorBoundaryContent'
89

9-
import { Button } from 'components/Button'
10-
1110
function FallbackComponent({ error, resetErrorBoundary }: FallbackProps) {
1211
return (
1312
<ErrorBoundaryContent error={error}>
@@ -23,7 +22,7 @@ export function ErrorBoundary({ children }: React.PropsWithChildren) {
2322
return (
2423
<Boundary
2524
FallbackComponent={FallbackComponent}
26-
onReset={() => navigate({ to: '/' })}
25+
onReset={() => navigate({ to: '/$' })}
2726
>
2827
{children}
2928
</Boundary>

apps/widget/src/components/ExportPDFButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { ExternalLink } from 'lucide-react'
33
import { toast } from 'sonner'
44

55
import { getDeepResearchSummaryPdf } from '@ns/public-api'
6+
import { Button } from '@ns/ui/atoms/Button'
67

7-
import { Button } from 'components/Button'
88
import { useChatStore } from 'lib/stores/chat'
99
import { getAccount } from 'lib/stores/user'
1010

@@ -17,7 +17,7 @@ export function ExportPDFButton({ messageId }: { messageId: string }) {
1717
)
1818
download(
1919
data as Blob,
20-
`${currentChat?.name ?? 'Deep Research'} Summary.pdf`,
20+
`${currentChat?.name ?? 'Deep Research'} Summary ${new Date().toLocaleString()}.pdf`,
2121
'application/pdf',
2222
)
2323
}

apps/widget/src/components/Suggestions.tsx

Lines changed: 124 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
import * as TabsPrimitive from '@radix-ui/react-tabs'
2-
import { Link } from '@tanstack/react-router'
3-
import { ArrowRight } from 'lucide-react'
2+
import { Link, useNavigate, useSearch } from '@tanstack/react-router'
3+
import { ArrowRight, Atom } from 'lucide-react'
44
import { nanoid } from 'nanoid'
55
import { forwardRef } from 'react'
6+
import { toast } from 'sonner'
67

78
import {
89
AgentName,
10+
type ChatApiResponse,
11+
type DeepResearchPlanInDbBase,
912
type GetSuggestionsForAccountQueryParams,
1013
type SuggestionApi,
14+
useCreateChatFromDeepResearchPlan,
15+
useGetDeepResearchPlans,
1116
useGetSuggestionsForAccount,
1217
} from '@ns/public-api'
18+
import { Alert, AlertDescription, AlertTitle } from '@ns/ui/atoms/Alert'
19+
import { Button } from '@ns/ui/atoms/Button'
20+
import { Spinner } from '@ns/ui/atoms/Spinner'
21+
import { Tooltip, TooltipContent, TooltipTrigger } from '@ns/ui/atoms/Tooltip'
22+
import {
23+
MessageRichTextInput,
24+
MessageRichTextInputContent,
25+
} from '@ns/ui/molecules/MessageRichTextInput'
1326
import { AGENT_DESCRIPTIONS, AGENT_DISPLAY_NAMES } from '@ns/ui/utils/agentName'
1427
import { cn } from '@ns/ui/utils/cn'
1528
import { ASSET_TRIGGER } from '@ns/ui/utils/mentions'
1629

17-
import { Alert, AlertDescription, AlertTitle } from 'components/Alert'
18-
import { Button } from 'components/Button'
19-
import { Spinner } from 'components/Spinner'
2030
import { useChatStore } from 'lib/stores/chat'
2131
import { useCustomizationStore } from 'lib/stores/customization'
2232
import { getAccount } from 'lib/stores/user'
2333

24-
import { Tooltip, TooltipContent, TooltipTrigger } from './Tooltip'
25-
2634
const suggestedAgentNames = [
2735
AgentName.sql_query_agent,
2836
AgentName.search_agent,
@@ -36,10 +44,14 @@ export function Suggestions({ className }: { className?: string }) {
3644
const {
3745
state: { showPromptLibrary },
3846
} = useCustomizationStore()
47+
const { deepResearch } = useSearch({ from: '/_widget/chats' })
3948
return (
4049
<TabsPrimitive.Root
4150
defaultValue={suggestedAgentNames[0]}
42-
className={cn('mx-auto flex max-w-3xl flex-col gap-2.5', className)}
51+
className={cn(
52+
'mx-auto flex w-full max-w-3xl flex-col gap-2.5',
53+
className,
54+
)}
4355
>
4456
<header className='flex flex-col gap-2 pt-4'>
4557
<h1 className='text-2xl font-semibold'>
@@ -49,7 +61,7 @@ export function Suggestions({ className }: { className?: string }) {
4961
<p className='text-base text-neutral-600'>
5062
Write your own prompt or choose a curated one
5163
</p>
52-
{showPromptLibrary && (
64+
{showPromptLibrary && !deepResearch && (
5365
<Button
5466
size='sm'
5567
variant='outline'
@@ -64,20 +76,16 @@ export function Suggestions({ className }: { className?: string }) {
6476
)}
6577
</div>
6678
</header>
67-
<SuggestionsGrid limit={4} />
79+
{deepResearch ? <DeepResearchGrid /> : <SuggestionsGrid limit={4} />}
6880
</TabsPrimitive.Root>
6981
)
7082
}
7183

72-
export function SuggestionsGrid(params: GetSuggestionsForAccountQueryParams) {
73-
const {
74-
data: suggestions,
75-
isPending,
76-
error,
77-
} = useGetSuggestionsForAccount({
78-
accountName: getAccount(),
79-
params,
80-
})
84+
function Grid({
85+
isPending,
86+
error,
87+
children,
88+
}: React.PropsWithChildren<{ isPending: boolean; error: unknown }>) {
8189
return isPending ? (
8290
<div className='flex items-center justify-center gap-2 p-6'>
8391
<Spinner size={0.4} />
@@ -89,11 +97,76 @@ export function SuggestionsGrid(params: GetSuggestionsForAccountQueryParams) {
8997
<AlertDescription>{(error as Error).message}</AlertDescription>
9098
</Alert>
9199
) : (
92-
<div className='grid grid-cols-1 gap-2 sm:grid-cols-2'>
93-
{suggestions.map((suggestion) => (
100+
<div className='grid grid-cols-1 gap-2 sm:grid-cols-2'>{children}</div>
101+
)
102+
}
103+
104+
function DeepResearchGrid() {
105+
const {
106+
data: deepResearchPlans,
107+
isPending,
108+
error,
109+
} = useGetDeepResearchPlans({ accountName: getAccount() })
110+
return (
111+
<Grid isPending={isPending} error={error}>
112+
{deepResearchPlans?.map((plan) => (
113+
<DeepResearchGridItem plan={plan} key={plan.id} />
114+
))}
115+
</Grid>
116+
)
117+
}
118+
119+
function DeepResearchGridItem({ plan }: { plan: DeepResearchPlanInDbBase }) {
120+
const { mutateAsync, isPending } = useCreateChatFromDeepResearchPlan()
121+
const { setCurrentChat } = useChatStore()
122+
const navigate = useNavigate()
123+
const onSuccess = (chat: ChatApiResponse) => {
124+
setCurrentChat(chat)
125+
navigate({ to: '/chats', search: true })
126+
}
127+
const onClick = () =>
128+
toast.promise(
129+
async () => {
130+
await mutateAsync(
131+
{ accountName: getAccount(), deepResearchPlanId: plan.id },
132+
{ onSuccess },
133+
)
134+
},
135+
{
136+
loading: 'Creating chat from research plan...',
137+
success: 'Chat created from research plan',
138+
error: 'Error creating chat from research plan',
139+
},
140+
)
141+
return (
142+
<GridItem
143+
onClick={onClick}
144+
suggestion={plan.question}
145+
className={cn(isPending && 'cursor-wait')}
146+
>
147+
<Mention className='bg-indigo-50 text-indigo-600'>
148+
<Atom className='h-4 w-4' />
149+
<span>Deep research</span>
150+
</Mention>
151+
</GridItem>
152+
)
153+
}
154+
155+
export function SuggestionsGrid(params: GetSuggestionsForAccountQueryParams) {
156+
const {
157+
data: suggestions,
158+
isPending,
159+
error,
160+
} = useGetSuggestionsForAccount({
161+
accountName: getAccount(),
162+
params,
163+
})
164+
return (
165+
<Grid isPending={isPending} error={error}>
166+
{suggestions?.map((suggestion) => (
94167
<SuggestionsGridItem suggestion={suggestion} key={nanoid()} />
95168
))}
96-
</div>
169+
</Grid>
97170
)
98171
}
99172

@@ -104,18 +177,39 @@ function SuggestionsGridItem({ suggestion }: { suggestion: SuggestionApi }) {
104177
const { sendMessage } = useChatStore()
105178
const assetTag = `[${suggestion.asset_name}](ds-${suggestion.short_id})`
106179
const message = `${suggestion.prompt} ${ASSET_TRIGGER}${assetTag}`
180+
return (
181+
<GridItem
182+
onClick={() => sendMessage(message)}
183+
suggestion={suggestion.prompt}
184+
>
185+
{showAgentTagging && <AgentMention name={suggestion.agent_name} />}
186+
<AssetMention name={suggestion.asset_name} />
187+
</GridItem>
188+
)
189+
}
190+
191+
function GridItem({
192+
className,
193+
suggestion,
194+
children,
195+
...props
196+
}: React.HTMLAttributes<HTMLButtonElement> & { suggestion: string }) {
107197
return (
108198
<button
109199
type='button'
110-
onClick={() => sendMessage(message)}
111-
className='flex flex-col items-start justify-between gap-6 rounded-lg border border-neutral-200 bg-white p-4 text-left transition hover:border-primary focus:border-primary'
200+
className={cn(
201+
'flex flex-col items-start justify-between gap-6 rounded-lg border border-neutral-200 bg-white p-4 text-left transition hover:border-primary focus:border-primary',
202+
className,
203+
)}
204+
{...props}
112205
>
113-
<span className='line-clamp-2 min-h-10 text-sm font-normal text-neutral-900'>
114-
{suggestion.prompt}
115-
</span>
206+
<div className='line-clamp-2 min-h-10 text-sm font-normal text-neutral-900'>
207+
<MessageRichTextInput readOnly value={suggestion}>
208+
<MessageRichTextInputContent />
209+
</MessageRichTextInput>
210+
</div>
116211
<div className='flex w-full items-center justify-between gap-4'>
117-
{showAgentTagging && <AgentMention name={suggestion.agent_name} />}
118-
<AssetMention name={suggestion.asset_name} />
212+
{children}
119213
</div>
120214
</button>
121215
)
@@ -151,7 +245,7 @@ const Mention = forwardRef<HTMLDivElement, MentionProps>(
151245
({ className, children, ...props }, ref) => (
152246
<div
153247
className={cn(
154-
'flex h-6 items-center rounded-md px-1.5 text-xs',
248+
'flex h-6 items-center gap-1 rounded-md px-1.5 text-xs',
155249
className,
156250
)}
157251
ref={ref}

apps/widget/src/components/chat/ChatListItem.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import { Ellipsis } from 'lucide-react'
33
import { useEffect, useRef, useState } from 'react'
44

55
import { type ChatApiResponse } from '@ns/public-api'
6-
import { cn } from '@ns/ui/utils/cn'
7-
86
import {
97
DropdownMenu,
108
DropdownMenuContent,
119
DropdownMenuItem,
1210
DropdownMenuSeparator,
1311
DropdownMenuTrigger,
14-
} from 'components/Dropdown'
12+
} from '@ns/ui/atoms/DropdownMenu'
13+
import { cn } from '@ns/ui/utils/cn'
14+
1515
import { useChatStore } from 'lib/stores/chat'
1616

1717
interface ChatListItemProps {

0 commit comments

Comments
 (0)