@robosystems/core 0.3.0 → 0.4.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
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# @robosystems/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@robosystems/core)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Shared React core for the RoboSystems ecosystem apps — authentication, platform contexts, task monitoring, and the common UI component set used by robosystems-app, roboledger-app, and roboinvestor-app. Built for the Next.js App Router (client/server components and Server Actions) and consumed as a versioned npm package.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Features
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
|
|
15
|
-
It is Next.js-specific by design (App Router client/server components, Server Actions, `next/headers` cookie helpers) and is intended to be consumed by Next.js applications only. The compiled output targets bundler resolution — it is not loadable by Node's native ESM resolver.
|
|
10
|
+
- **Authentication** — sign-in/sign-up forms, `AuthProvider`/`AuthGuard`, JWT and SSO token handling
|
|
11
|
+
- **Contexts** — graph, entity, organization, service-offerings, and sidebar state
|
|
12
|
+
- **Task monitoring** — SSE-based operation monitoring with a polling fallback for long-running jobs
|
|
13
|
+
- **UI components** — layout, chat, forms, settings, API keys, and the shared Flowbite/Tailwind theme
|
|
14
|
+
- **Hooks & utilities** — user, limits, toast, and media-query hooks plus cookie persistence helpers
|
|
16
15
|
|
|
17
16
|
## Installation
|
|
18
17
|
|
|
@@ -20,233 +19,33 @@ It is Next.js-specific by design (App Router client/server components, Server Ac
|
|
|
20
19
|
npm install @robosystems/core
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
The consuming app provides:
|
|
26
|
-
|
|
27
|
-
| Peer | Range |
|
|
28
|
-
| --------------------- | ---------- |
|
|
29
|
-
| `react` / `react-dom` | >=18 <20 |
|
|
30
|
-
| `next` | >=15 <17 |
|
|
31
|
-
| `flowbite-react` | ^0.12.5 |
|
|
32
|
-
| `react-icons` | >=4 <6 |
|
|
33
|
-
| `@robosystems/client` | >=0.3.2 <1 |
|
|
34
|
-
|
|
35
|
-
### App wiring
|
|
36
|
-
|
|
37
|
-
Two integration points beyond the install:
|
|
38
|
-
|
|
39
|
-
1. **Tailwind content scan** — the package ships Tailwind utility classes in its compiled JS; add it to the app's `tailwind.config.ts`:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
content: [
|
|
43
|
-
// ...existing globs
|
|
44
|
-
'node_modules/@robosystems/core/**/*.js',
|
|
45
|
-
]
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
2. **Vitest** (if the app tests components that render core) — the package is compiled ESM with directory imports and must be processed by vite, in `vitest.config.ts`:
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
test: {
|
|
52
|
-
server: { deps: { inline: [/@robosystems\/core/] } },
|
|
53
|
-
}
|
|
54
|
-
```
|
|
22
|
+
`react`, `react-dom`, `next`, `flowbite-react`, `react-icons`, and `@robosystems/client` are peer dependencies provided by the consuming app. See [CLAUDE.md](CLAUDE.md) for app wiring (Tailwind content scan, vitest inlining), packaging notes, and the release flow.
|
|
55
23
|
|
|
56
24
|
## Usage
|
|
57
25
|
|
|
58
|
-
|
|
26
|
+
Import from the root barrel or from subpaths mirroring the folder structure:
|
|
59
27
|
|
|
60
|
-
```
|
|
61
|
-
import {
|
|
62
|
-
AuthProvider,
|
|
63
|
-
useAuth,
|
|
64
|
-
useGraphContext,
|
|
65
|
-
customTheme,
|
|
66
|
-
} from '@robosystems/core'
|
|
28
|
+
```tsx
|
|
29
|
+
import { AuthProvider, useGraphContext, customTheme } from '@robosystems/core'
|
|
67
30
|
import { PageHeader, Spinner } from '@robosystems/core/ui-components'
|
|
68
31
|
import { useToast } from '@robosystems/core/hooks/use-toast'
|
|
69
32
|
```
|
|
70
33
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
## Structure
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
├── actions/ # Next.js Server Actions
|
|
77
|
-
│ ├── entity-actions.ts # Entity creation and management
|
|
78
|
-
│ └── graph-actions.ts # Graph creation and lifecycle
|
|
79
|
-
├── auth-components/ # Authentication UI components
|
|
80
|
-
│ ├── AppSwitcher.tsx # Cross-app navigation switcher
|
|
81
|
-
│ ├── AuthGuard.tsx # Route protection wrapper
|
|
82
|
-
│ ├── AuthProvider.tsx # Authentication context provider
|
|
83
|
-
│ ├── SessionWarningDialog.tsx # Session expiry warning
|
|
84
|
-
│ ├── SignInForm.tsx # Login form
|
|
85
|
-
│ ├── SignUpForm.tsx # Registration form
|
|
86
|
-
│ └── TurnstileWidget.tsx # Cloudflare Turnstile CAPTCHA
|
|
87
|
-
├── auth-core/ # Authentication logic and types
|
|
88
|
-
│ ├── cleanup.ts # Session cleanup utilities
|
|
89
|
-
│ ├── client.ts # Authentication client
|
|
90
|
-
│ ├── config.ts # Auth configuration
|
|
91
|
-
│ ├── hooks.ts # useAuth and related hooks
|
|
92
|
-
│ ├── sso.ts # SSO/OAuth support
|
|
93
|
-
│ ├── token-storage.ts # JWT storage utilities
|
|
94
|
-
│ └── types.ts # Auth TypeScript types
|
|
95
|
-
├── components/ # Shared UI components
|
|
96
|
-
│ ├── console/ # Terminal-style output display
|
|
97
|
-
│ ├── repositories/ # Repository browsing and subscriptions
|
|
98
|
-
│ ├── search/ # Search UI
|
|
99
|
-
│ ├── EntitySelector.tsx # Entity picker component
|
|
100
|
-
│ ├── GraphSelectorCore.tsx # Graph picker component
|
|
101
|
-
│ ├── PageLayout.tsx # Standard page layout
|
|
102
|
-
│ └── RepositoryGuard.tsx # Repository access protection
|
|
103
|
-
├── contexts/ # React contexts
|
|
104
|
-
│ ├── entity-context.tsx # Active entity state
|
|
105
|
-
│ ├── graph-context.tsx # Active graph state
|
|
106
|
-
│ ├── org-context.tsx # Organization state
|
|
107
|
-
│ ├── service-offerings-context.tsx # Available plans and tiers
|
|
108
|
-
│ └── sidebar-context.tsx # Sidebar collapsed/expanded state
|
|
109
|
-
├── hooks/ # Custom React hooks
|
|
110
|
-
│ ├── use-api-error.ts # API error normalization
|
|
111
|
-
│ ├── use-media-query.ts # Responsive breakpoint detection
|
|
112
|
-
│ ├── use-streaming-query.ts # SSE streaming data hook
|
|
113
|
-
│ ├── use-toast.tsx # Toast notification hook
|
|
114
|
-
│ ├── use-user-limits.ts # User quota and limit checks
|
|
115
|
-
│ └── use-user.ts # Current user data hook
|
|
116
|
-
├── lib/ # Utility libraries
|
|
117
|
-
│ ├── entity-cookie.ts # Entity selection persistence
|
|
118
|
-
│ ├── graph-cookie.ts # Graph selection persistence
|
|
119
|
-
│ ├── graph-tiers.ts # Tier display helpers
|
|
120
|
-
│ └── sidebar-cookie.ts # Sidebar state persistence
|
|
121
|
-
├── library/ # XBRL taxonomy browser
|
|
122
|
-
├── research/ # Research coverage components
|
|
123
|
-
├── task-monitoring/ # Background job and operation tracking
|
|
124
|
-
│ ├── hooks.ts # useTaskMonitoring, useEntityCreationTask
|
|
125
|
-
│ ├── operationErrors.ts # Operation error types
|
|
126
|
-
│ ├── operationHooks.ts # useOperationMonitoring, useGraphCreation
|
|
127
|
-
│ ├── operationMonitor.ts # SSE-based operation monitor
|
|
128
|
-
│ ├── operationTypes.ts # Shared operation types
|
|
129
|
-
│ ├── taskMonitor.ts # Polling-based task monitor (fallback)
|
|
130
|
-
│ └── types.ts # Task and operation TypeScript types
|
|
131
|
-
├── theme/ # UI theming
|
|
132
|
-
│ └── flowbite-theme.ts # Flowbite React custom theme
|
|
133
|
-
├── types/ # Shared TypeScript definitions
|
|
134
|
-
│ ├── entity.d.ts # Entity type definitions
|
|
135
|
-
│ └── user.d.ts # User type definitions
|
|
136
|
-
├── ui-components/ # Reusable UI components
|
|
137
|
-
│ ├── api-keys/ # API key management
|
|
138
|
-
│ ├── chat/ # Chat UI (header, input, messages, deep research toggle)
|
|
139
|
-
│ ├── forms/ # Form components and validation
|
|
140
|
-
│ ├── layout/ # Navbar, sidebar, page containers
|
|
141
|
-
│ ├── settings/ # Settings page components
|
|
142
|
-
│ ├── support/ # Support modal
|
|
143
|
-
│ ├── Logo.tsx # RoboSystems logo component
|
|
144
|
-
│ └── Spinner.tsx # Loading spinner
|
|
145
|
-
└── utils/ # Utility functions
|
|
146
|
-
└── turnstile-config.ts # Cloudflare Turnstile configuration
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Technology Stack
|
|
150
|
-
|
|
151
|
-
- **React 18/19** with modern hooks and patterns
|
|
152
|
-
- **TypeScript** for type safety
|
|
153
|
-
- **Flowbite React** for UI components
|
|
154
|
-
- **Tailwind CSS** for styling (classes compiled by the consuming app)
|
|
155
|
-
- **Next.js 15/16** App Router
|
|
156
|
-
- **Auto-generated SDK** (`@robosystems/client`) from OpenAPI specifications
|
|
157
|
-
|
|
158
|
-
## Key Patterns
|
|
159
|
-
|
|
160
|
-
### Task Monitoring
|
|
161
|
-
|
|
162
|
-
Two monitors handle async operations:
|
|
163
|
-
|
|
164
|
-
- **`operationMonitor`** — SSE-based, used for graph lifecycle ops (create, materialize, etc.) that return `OperationEnvelope` with an `operationId`
|
|
165
|
-
- **`taskMonitor`** — Polling-based fallback for older task-style operations
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
import { useOperationMonitoring, useGraphCreation } from '@robosystems/core/task-monitoring'
|
|
169
|
-
|
|
170
|
-
// Monitor a graph operation via SSE
|
|
171
|
-
const { startMonitoring, progress, result } = useOperationMonitoring()
|
|
172
|
-
await startMonitoring(operationId)
|
|
173
|
-
|
|
174
|
-
// Full graph creation with monitoring
|
|
175
|
-
const { createGraph, isCreating } = useGraphCreation()
|
|
176
|
-
await createGraph({ graph_type: 'entity', graph_name: 'Acme Corp', ... })
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Contexts
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
import { useGraphContext, useOrgContext } from '@robosystems/core/contexts'
|
|
183
|
-
|
|
184
|
-
function MyComponent() {
|
|
185
|
-
const { currentGraphId, setCurrentGraphId } = useGraphContext()
|
|
186
|
-
const { org } = useOrgContext()
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Authentication
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
import { useAuth, AuthProvider, AuthGuard } from '@robosystems/core/auth-core'
|
|
194
|
-
import { SignInForm, SignUpForm } from '@robosystems/core/auth-components'
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Development
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
npm install # Also wires .githooks via the prepare script
|
|
201
|
-
npm run test # Vitest suite (jsdom, app-equivalent mocks in test/__mocks__)
|
|
202
|
-
npm run test:all # format:check + lint + typecheck + test + build
|
|
203
|
-
npm run build # tsc → dist/ + prepare-package.mjs (publishable package root)
|
|
204
|
-
npm run pack:local # Build + npm pack ./dist → tarball for local app testing
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
To test changes in an app before releasing:
|
|
34
|
+
## Resources
|
|
208
35
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Packaging notes
|
|
215
|
-
|
|
216
|
-
- The package is published **from `dist/`** so compiled files sit at the package root — the apps' directory-barrel and direct-file deep imports both resolve without an `exports` map.
|
|
217
|
-
- The build is **per-file tsc** (not a bundler) so `'use client'` / `'use server'` directives survive at the top of each emitted file.
|
|
218
|
-
- **No CommonJS**: the output is ESM — `require()` is not available at runtime. Use static or dynamic `import`.
|
|
219
|
-
|
|
220
|
-
### Adding New Components
|
|
221
|
-
|
|
222
|
-
1. Create component in the appropriate directory
|
|
223
|
-
2. Add TypeScript types in `types/` if needed
|
|
224
|
-
3. Export from the directory's `index.ts` (and the root `index.ts` if broadly useful)
|
|
225
|
-
4. Add tests in the adjacent `__tests__/` directory
|
|
226
|
-
5. Validate in an app with a `pack:local` tarball before releasing
|
|
36
|
+
- [RoboSystems Platform](https://robosystems.ai)
|
|
37
|
+
- [GitHub Repository](https://github.com/RoboFinSystems/robosystems-core)
|
|
38
|
+
- [API Documentation](https://api.robosystems.ai/docs)
|
|
227
39
|
|
|
228
|
-
|
|
40
|
+
## Support
|
|
229
41
|
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
## Releasing
|
|
236
|
-
|
|
237
|
-
Releases run through GitHub Actions (same pipeline as `@robosystems/report-components`):
|
|
238
|
-
|
|
239
|
-
1. Run the **Create Release & Publish** workflow (`gh workflow run create-release.yml --field version_type=patch|minor|major`, or `npm run release:create`)
|
|
240
|
-
2. It bumps the version on `main`, cuts a `release/<version>` branch, tags `v<version>`, and creates a GitHub Release with an AI-generated changelog
|
|
241
|
-
3. The `release/**` push triggers `publish.yml`, which builds and runs `npm publish ./dist --provenance` via npm OIDC Trusted Publishing
|
|
242
|
-
|
|
243
|
-
## Security
|
|
244
|
-
|
|
245
|
-
- Never commit secrets or API keys
|
|
246
|
-
- Use environment variables for configuration
|
|
247
|
-
- Follow authentication best practices
|
|
248
|
-
- Validate all inputs and API responses
|
|
42
|
+
- [Issues](https://github.com/RoboFinSystems/robosystems-core/issues)
|
|
43
|
+
- [Wiki](https://github.com/RoboFinSystems/robosystems/wiki)
|
|
44
|
+
- [Projects](https://github.com/orgs/RoboFinSystems/projects)
|
|
45
|
+
- [Discussions](https://github.com/orgs/RoboFinSystems/discussions)
|
|
249
46
|
|
|
250
47
|
## License
|
|
251
48
|
|
|
252
|
-
MIT
|
|
49
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
50
|
+
|
|
51
|
+
MIT © 2026 RFS LLC
|
|
@@ -87,6 +87,9 @@ export function ConsoleContent({ config }) {
|
|
|
87
87
|
.join('\n');
|
|
88
88
|
const builtInCommands = ` /query - Execute a Cypher query\n` +
|
|
89
89
|
` /search - Search documents\n` +
|
|
90
|
+
(config.enableRecall
|
|
91
|
+
? ` /recall - Recall semantic memories\n`
|
|
92
|
+
: '') +
|
|
90
93
|
` /mcp - Show MCP connection setup\n` +
|
|
91
94
|
` /help - Show this help message\n` +
|
|
92
95
|
` /clear - Clear console history\n` +
|
|
@@ -451,7 +454,7 @@ export function ConsoleContent({ config }) {
|
|
|
451
454
|
}
|
|
452
455
|
};
|
|
453
456
|
const handleCommand = async (command) => {
|
|
454
|
-
var _a;
|
|
457
|
+
var _a, _b;
|
|
455
458
|
if (!command.trim())
|
|
456
459
|
return;
|
|
457
460
|
addUserMessage(command);
|
|
@@ -515,16 +518,70 @@ export function ConsoleContent({ config }) {
|
|
|
515
518
|
addErrorMessage('Search failed. Please try again.');
|
|
516
519
|
}
|
|
517
520
|
}
|
|
518
|
-
catch (
|
|
521
|
+
catch (_c) {
|
|
519
522
|
addErrorMessage('An error occurred while searching.');
|
|
520
523
|
}
|
|
521
524
|
return;
|
|
522
525
|
}
|
|
526
|
+
// Handle /recall command — built-in only when the target supports
|
|
527
|
+
// semantic memory (user graphs; shared repositories reject it). When
|
|
528
|
+
// disabled it falls through to the unknown-command branch below.
|
|
529
|
+
if (config.enableRecall && command.toLowerCase().startsWith('/recall')) {
|
|
530
|
+
const recallQuery = command.slice(7).trim();
|
|
531
|
+
if (!recallQuery) {
|
|
532
|
+
addErrorMessage('Usage: /recall <query>\n\nExamples:\n /recall payment terms for Acme\n /recall quarter close checklist');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!graphId) {
|
|
536
|
+
addErrorMessage(config.noSelectionError);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
addSystemMessage(`Recalling memories for "${recallQuery}"...`);
|
|
540
|
+
try {
|
|
541
|
+
const res = await SDK.recallMemory({
|
|
542
|
+
path: { graph_id: graphId },
|
|
543
|
+
body: { query: recallQuery, k: 10 },
|
|
544
|
+
});
|
|
545
|
+
if (res.data) {
|
|
546
|
+
const data = res.data;
|
|
547
|
+
if (data.hits.length === 0) {
|
|
548
|
+
addSystemMessage(`No memories found for "${recallQuery}".`);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
const lines = data.hits.map((hit, idx) => {
|
|
552
|
+
var _a;
|
|
553
|
+
const tags = ((_a = hit.tags) === null || _a === void 0 ? void 0 : _a.length) ? ` [${hit.tags.join(', ')}]` : '';
|
|
554
|
+
const text = hit.snippet
|
|
555
|
+
? `\n ${hit.snippet.slice(0, 150)}${hit.snippet.length > 150 ? '...' : ''}`
|
|
556
|
+
: '';
|
|
557
|
+
return ` ${idx + 1}. [${hit.score.toFixed(2)}]${tags}${text}`;
|
|
558
|
+
});
|
|
559
|
+
addSystemMessage(`Recalled ${data.total} memor${data.total === 1 ? 'y' : 'ies'} for "${recallQuery}" (showing ${data.hits.length}):\n\n${lines.join('\n\n')}`, true);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
const status = (_a = res.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
564
|
+
if (status === 403) {
|
|
565
|
+
addErrorMessage('Memory recall is not available for shared repositories.');
|
|
566
|
+
}
|
|
567
|
+
else if (status === 404 || status === 503) {
|
|
568
|
+
addErrorMessage('Semantic memory is not enabled in this environment.');
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
addErrorMessage('Recall failed. Please try again.');
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch (_d) {
|
|
576
|
+
addErrorMessage('An error occurred while recalling memories.');
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
523
580
|
// Handle slash commands
|
|
524
581
|
if (command.startsWith('/')) {
|
|
525
582
|
const cmd = command.toLowerCase().split(' ')[0];
|
|
526
583
|
// Check extra commands first
|
|
527
|
-
const extra = (
|
|
584
|
+
const extra = (_b = config.extraCommands) === null || _b === void 0 ? void 0 : _b.find((ec) => ec.command.toLowerCase() === cmd);
|
|
528
585
|
if (extra) {
|
|
529
586
|
await extra.handler({ addSystemMessage, addErrorMessage, graphId });
|
|
530
587
|
return;
|
|
@@ -390,6 +390,8 @@ export function buildGraphAwareConsoleConfig(graph, branding) {
|
|
|
390
390
|
sampleQueries: set.sampleQueries,
|
|
391
391
|
examplesLabel: (_b = branding.examplesLabel) !== null && _b !== void 0 ? _b : 'Example Cypher Queries:',
|
|
392
392
|
noSelectionError: (_c = branding.noSelectionError) !== null && _c !== void 0 ? _c : 'No graph selected. Please select a graph first.',
|
|
393
|
+
// Semantic memory is per-user-graph; shared repositories reject it.
|
|
394
|
+
enableRecall: !isRepository,
|
|
393
395
|
};
|
|
394
396
|
}
|
|
395
397
|
/**
|
|
@@ -56,6 +56,10 @@ export interface ConsoleConfig {
|
|
|
56
56
|
examplesLabel: string;
|
|
57
57
|
/** Error message when no graph/portfolio is selected */
|
|
58
58
|
noSelectionError: string;
|
|
59
|
+
/** Enable the built-in /recall semantic-memory command. Off by default —
|
|
60
|
+
* shared repositories don't support memory, so the graph-aware builder
|
|
61
|
+
* enables it only for user graphs. */
|
|
62
|
+
enableRecall?: boolean;
|
|
59
63
|
/** Extra slash commands beyond the built-in set */
|
|
60
64
|
extraCommands?: ConsoleExtraCommand[];
|
|
61
65
|
}
|
package/package.json
CHANGED
|
@@ -4,10 +4,11 @@ import type { CoverageItem } from './types';
|
|
|
4
4
|
* "Listen" card right under the video), then the brief rendered from markdown (its own
|
|
5
5
|
* leading H1 is stripped — we render the title above it), and the continuing-coverage
|
|
6
6
|
* history. Works in a server component (SSR'd for SEO) or a client one.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Theme-aware: readable in both light and dark. The prose body sets an explicit
|
|
8
|
+
* light + `dark:` color for every element it renders (headings, p, strong, em, links,
|
|
9
|
+
* lists, code, blockquote, hr, tables) rather than relying on `prose-invert` /
|
|
10
|
+
* `dark:prose-invert` — the latter doesn't resolve from a `.dark` class in the
|
|
11
|
+
* Tailwind v4 + typography-plugin setup these apps use.
|
|
11
12
|
*/
|
|
12
13
|
export declare function ResearchArticle({ item, briefMarkdown, }: {
|
|
13
14
|
item: CoverageItem;
|
|
@@ -8,10 +8,11 @@ import { CoverageHistory } from './CoverageHistory';
|
|
|
8
8
|
* "Listen" card right under the video), then the brief rendered from markdown (its own
|
|
9
9
|
* leading H1 is stripped — we render the title above it), and the continuing-coverage
|
|
10
10
|
* history. Works in a server component (SSR'd for SEO) or a client one.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Theme-aware: readable in both light and dark. The prose body sets an explicit
|
|
12
|
+
* light + `dark:` color for every element it renders (headings, p, strong, em, links,
|
|
13
|
+
* lists, code, blockquote, hr, tables) rather than relying on `prose-invert` /
|
|
14
|
+
* `dark:prose-invert` — the latter doesn't resolve from a `.dark` class in the
|
|
15
|
+
* Tailwind v4 + typography-plugin setup these apps use.
|
|
15
16
|
*/
|
|
16
17
|
export function ResearchArticle({ item, briefMarkdown, }) {
|
|
17
18
|
var _a;
|
|
@@ -20,7 +21,7 @@ export function ResearchArticle({ item, briefMarkdown, }) {
|
|
|
20
21
|
const podcastYtId = youtubeId(item.podcast_youtube_url);
|
|
21
22
|
return (_jsxs("article", { className: "mx-auto max-w-3xl", children: [_jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400", children: [_jsxs("span", { className: "rounded bg-cyan-500/10 px-2 py-0.5 font-semibold text-cyan-600 dark:text-cyan-400", children: [item.company, " \u00B7 ", item.ticker] }), item.coverage_label && _jsx("span", { children: item.coverage_label }), _jsx("span", { children: (_a = item.date) === null || _a === void 0 ? void 0 : _a.slice(0, 10) })] }), _jsx("h1", { className: "mb-6 text-3xl font-bold text-gray-900 dark:text-white", children: item.title }), ytId ? (_jsx("div", { className: "mb-8 aspect-video w-full overflow-hidden rounded-xl bg-black", children: _jsx("iframe", { className: "h-full w-full", src: `https://www.youtube.com/embed/${ytId}`, title: item.title, loading: "lazy", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true }) })) : (item.assets.video && (
|
|
22
23
|
// eslint-disable-next-line jsx-a11y/media-has-caption
|
|
23
|
-
_jsx("video", { controls: true, poster: item.assets.thumbnail, src: item.assets.video, className: "mb-8 aspect-video w-full rounded-xl bg-black" }))), (item.assets.podcast_mp3 || podcastYtId) && (_jsx("section", { className: "mb-8", children: item.assets.podcast_mp3 ? (_jsxs("div", { className: "rounded-xl border border-cyan-500/30 bg-
|
|
24
|
+
_jsx("video", { controls: true, poster: item.assets.thumbnail, src: item.assets.video, className: "mb-8 aspect-video w-full rounded-xl bg-black" }))), (item.assets.podcast_mp3 || podcastYtId) && (_jsx("section", { className: "mb-8", children: item.assets.podcast_mp3 ? (_jsxs("div", { className: "rounded-xl border border-cyan-500/30 bg-cyan-50/60 p-4 dark:bg-gray-900/50", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-3", children: [_jsx("p", { className: "text-sm font-semibold text-cyan-600 dark:text-cyan-400", children: "\uD83C\uDF99 Listen \u2014 Q&A podcast" }), podcastYtId && (_jsx("a", { href: item.podcast_youtube_url, target: "_blank", rel: "noopener noreferrer", className: "shrink-0 text-xs text-cyan-600 hover:underline dark:text-cyan-400", children: "Watch on YouTube \u2197" }))] }), _jsx("audio", { controls: true, preload: "none", src: item.assets.podcast_mp3, className: "w-full" })] })) : (
|
|
24
25
|
// no MP3 yet — fall back to the YouTube video player
|
|
25
|
-
_jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-3 text-xl font-bold text-gray-900 dark:text-white", children: "Listen \u2014 Q&A podcast" }), _jsx("div", { className: "aspect-video w-full overflow-hidden rounded-xl bg-black", children: _jsx("iframe", { className: "h-full w-full", src: `https://www.youtube.com/embed/${podcastYtId}`, title: `${item.title} — Q&A podcast`, loading: "lazy", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true }) })] })) })), body && (_jsx("div", { className: "prose prose-lg prose-
|
|
26
|
+
_jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-3 text-xl font-bold text-gray-900 dark:text-white", children: "Listen \u2014 Q&A podcast" }), _jsx("div", { className: "aspect-video w-full overflow-hidden rounded-xl bg-black", children: _jsx("iframe", { className: "h-full w-full", src: `https://www.youtube.com/embed/${podcastYtId}`, title: `${item.title} — Q&A podcast`, loading: "lazy", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true }) })] })) })), body && (_jsx("div", { className: "prose prose-lg prose-headings:font-heading prose-headings:font-bold prose-headings:text-gray-900 dark:prose-headings:text-white prose-p:text-gray-700 dark:prose-p:text-gray-300 prose-p:leading-relaxed prose-a:text-cyan-600 dark:prose-a:text-cyan-400 prose-a:no-underline hover:prose-a:text-cyan-500 dark:hover:prose-a:text-cyan-300 prose-strong:text-gray-900 dark:prose-strong:text-white prose-strong:font-semibold prose-em:text-gray-700 dark:prose-em:text-gray-300 prose-code:text-cyan-700 dark:prose-code:text-cyan-400 prose-code:bg-gray-100 dark:prose-code:bg-gray-800 prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-gray-900 prose-pre:border prose-pre:border-gray-800 prose-blockquote:border-l-cyan-500 prose-blockquote:text-gray-600 dark:prose-blockquote:text-gray-400 prose-blockquote:italic prose-ul:text-gray-700 dark:prose-ul:text-gray-300 prose-ol:text-gray-700 dark:prose-ol:text-gray-300 prose-li:marker:text-cyan-500 prose-hr:border-gray-200 dark:prose-hr:border-gray-800 prose-table:border-gray-300 dark:prose-table:border-gray-700 prose-th:bg-gray-100 dark:prose-th:bg-gray-900 prose-th:text-gray-900 dark:prose-th:text-white prose-td:text-gray-700 dark:prose-td:text-gray-300 max-w-none", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: body }) })), _jsx(CoverageHistory, { history: item.history }), _jsxs("footer", { className: "mt-12 border-t border-gray-200 pt-6 dark:border-gray-800", children: [_jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Audio & music produced with ElevenLabs." }), _jsxs("a", { href: "https://elevenlabs.io/startup-grants", target: "_blank", rel: "noopener noreferrer", "aria-label": "Backed by the ElevenLabs Grants program", className: "mt-3 inline-block", children: [_jsx("img", { src: "/images/logos/elevenlabs-grants.webp", alt: "Backed by the ElevenLabs Grants program", width: 200, className: "h-auto dark:hidden" }), _jsx("img", { src: "/images/logos/elevenlabs-grants-white.webp", alt: "Backed by the ElevenLabs Grants program", width: 200, className: "hidden h-auto dark:block" })] }), _jsxs("p", { className: "mt-2 text-xs text-gray-400 dark:text-gray-500", children: ["Using ElevenLabs yourself? Our", ' ', _jsx("a", { href: "https://try.elevenlabs.io/v9z3wzm97gk3", target: "_blank", rel: "noopener noreferrer", className: "hover:underline", children: "referral link" }), ' ', "costs you nothing extra and supports this research."] })] })] }));
|
|
26
27
|
}
|