agent-hustle-demo 1.0.1
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 +429 -0
- package/dist/HustleChat-BC9wvWVA.d.ts +90 -0
- package/dist/HustleChat-BcrKkkyn.d.cts +90 -0
- package/dist/browser/hustle-react.js +14854 -0
- package/dist/browser/hustle-react.js.map +1 -0
- package/dist/components/index.cjs +3141 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +20 -0
- package/dist/components/index.d.ts +20 -0
- package/dist/components/index.js +3112 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.cjs +845 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +6 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +838 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hustle-Kj0X8qXC.d.cts +193 -0
- package/dist/hustle-Kj0X8qXC.d.ts +193 -0
- package/dist/index-ChUsRBwL.d.ts +152 -0
- package/dist/index-DE1N7C3W.d.cts +152 -0
- package/dist/index-DuPFrMZy.d.cts +214 -0
- package/dist/index-kFIdHjNw.d.ts +214 -0
- package/dist/index.cjs +3746 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +271 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +3697 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.cjs +844 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +5 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +838 -0
- package/dist/providers/index.js.map +1 -0
- package/package.json +80 -0
- package/src/components/AuthStatus.tsx +352 -0
- package/src/components/ConnectButton.tsx +421 -0
- package/src/components/HustleChat.tsx +1273 -0
- package/src/components/MarkdownContent.tsx +431 -0
- package/src/components/index.ts +15 -0
- package/src/hooks/index.ts +40 -0
- package/src/hooks/useEmblemAuth.ts +27 -0
- package/src/hooks/useHustle.ts +36 -0
- package/src/hooks/usePlugins.ts +135 -0
- package/src/index.ts +142 -0
- package/src/plugins/index.ts +48 -0
- package/src/plugins/migrateFun.ts +211 -0
- package/src/plugins/predictionMarket.ts +411 -0
- package/src/providers/EmblemAuthProvider.tsx +319 -0
- package/src/providers/HustleProvider.tsx +540 -0
- package/src/providers/index.ts +6 -0
- package/src/styles/index.ts +2 -0
- package/src/styles/tokens.ts +447 -0
- package/src/types/auth.ts +85 -0
- package/src/types/hustle.ts +217 -0
- package/src/types/index.ts +49 -0
- package/src/types/plugin.ts +180 -0
- package/src/utils/index.ts +122 -0
- package/src/utils/pluginRegistry.ts +375 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
|
4
|
+
import { marked } from 'marked';
|
|
5
|
+
import hljs from 'highlight.js/lib/core';
|
|
6
|
+
// Import only common languages for AI chat
|
|
7
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
8
|
+
import typescript from 'highlight.js/lib/languages/typescript';
|
|
9
|
+
import python from 'highlight.js/lib/languages/python';
|
|
10
|
+
import json from 'highlight.js/lib/languages/json';
|
|
11
|
+
import bash from 'highlight.js/lib/languages/bash';
|
|
12
|
+
import shell from 'highlight.js/lib/languages/shell';
|
|
13
|
+
import css from 'highlight.js/lib/languages/css';
|
|
14
|
+
import xml from 'highlight.js/lib/languages/xml';
|
|
15
|
+
import markdown from 'highlight.js/lib/languages/markdown';
|
|
16
|
+
import sql from 'highlight.js/lib/languages/sql';
|
|
17
|
+
import yaml from 'highlight.js/lib/languages/yaml';
|
|
18
|
+
import rust from 'highlight.js/lib/languages/rust';
|
|
19
|
+
import go from 'highlight.js/lib/languages/go';
|
|
20
|
+
import java from 'highlight.js/lib/languages/java';
|
|
21
|
+
import cpp from 'highlight.js/lib/languages/cpp';
|
|
22
|
+
import csharp from 'highlight.js/lib/languages/csharp';
|
|
23
|
+
import php from 'highlight.js/lib/languages/php';
|
|
24
|
+
import ruby from 'highlight.js/lib/languages/ruby';
|
|
25
|
+
import swift from 'highlight.js/lib/languages/swift';
|
|
26
|
+
import kotlin from 'highlight.js/lib/languages/kotlin';
|
|
27
|
+
import { tokens } from '../styles';
|
|
28
|
+
|
|
29
|
+
// Register languages
|
|
30
|
+
hljs.registerLanguage('javascript', javascript);
|
|
31
|
+
hljs.registerLanguage('js', javascript);
|
|
32
|
+
hljs.registerLanguage('typescript', typescript);
|
|
33
|
+
hljs.registerLanguage('ts', typescript);
|
|
34
|
+
hljs.registerLanguage('python', python);
|
|
35
|
+
hljs.registerLanguage('py', python);
|
|
36
|
+
hljs.registerLanguage('json', json);
|
|
37
|
+
hljs.registerLanguage('bash', bash);
|
|
38
|
+
hljs.registerLanguage('sh', bash);
|
|
39
|
+
hljs.registerLanguage('shell', shell);
|
|
40
|
+
hljs.registerLanguage('css', css);
|
|
41
|
+
hljs.registerLanguage('xml', xml);
|
|
42
|
+
hljs.registerLanguage('html', xml);
|
|
43
|
+
hljs.registerLanguage('markdown', markdown);
|
|
44
|
+
hljs.registerLanguage('md', markdown);
|
|
45
|
+
hljs.registerLanguage('sql', sql);
|
|
46
|
+
hljs.registerLanguage('yaml', yaml);
|
|
47
|
+
hljs.registerLanguage('yml', yaml);
|
|
48
|
+
hljs.registerLanguage('rust', rust);
|
|
49
|
+
hljs.registerLanguage('go', go);
|
|
50
|
+
hljs.registerLanguage('java', java);
|
|
51
|
+
hljs.registerLanguage('cpp', cpp);
|
|
52
|
+
hljs.registerLanguage('c', cpp);
|
|
53
|
+
hljs.registerLanguage('csharp', csharp);
|
|
54
|
+
hljs.registerLanguage('cs', csharp);
|
|
55
|
+
hljs.registerLanguage('php', php);
|
|
56
|
+
hljs.registerLanguage('ruby', ruby);
|
|
57
|
+
hljs.registerLanguage('rb', ruby);
|
|
58
|
+
hljs.registerLanguage('swift', swift);
|
|
59
|
+
hljs.registerLanguage('kotlin', kotlin);
|
|
60
|
+
hljs.registerLanguage('kt', kotlin);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* MarkdownContent - Renders markdown using marked + highlight.js
|
|
64
|
+
*
|
|
65
|
+
* Uses marked for markdown parsing (lightweight, reliable)
|
|
66
|
+
* Uses highlight.js for code block syntax highlighting
|
|
67
|
+
* Adds copy and "Open in Emblem AI" buttons to code blocks
|
|
68
|
+
*/
|
|
69
|
+
export interface MarkdownContentProps {
|
|
70
|
+
content: string;
|
|
71
|
+
className?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Container styles
|
|
76
|
+
*/
|
|
77
|
+
const containerStyle: React.CSSProperties = {
|
|
78
|
+
fontFamily: tokens.typography.fontFamily,
|
|
79
|
+
fontSize: tokens.typography.fontSizeMd,
|
|
80
|
+
lineHeight: 1.5,
|
|
81
|
+
color: 'inherit',
|
|
82
|
+
wordBreak: 'break-word',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Scoped CSS for markdown content + highlight.js dark theme
|
|
87
|
+
*/
|
|
88
|
+
const scopedStyles = `
|
|
89
|
+
.hljs-md p {
|
|
90
|
+
margin: 0 0 0.5em 0;
|
|
91
|
+
}
|
|
92
|
+
.hljs-md p:last-child {
|
|
93
|
+
margin-bottom: 0;
|
|
94
|
+
}
|
|
95
|
+
.hljs-md ul,
|
|
96
|
+
.hljs-md ol {
|
|
97
|
+
margin: 0.25em 0 0.5em 0;
|
|
98
|
+
padding-left: 1.5em;
|
|
99
|
+
}
|
|
100
|
+
.hljs-md li {
|
|
101
|
+
margin: 0.1em 0;
|
|
102
|
+
line-height: 1.4;
|
|
103
|
+
}
|
|
104
|
+
.hljs-md li > p {
|
|
105
|
+
margin: 0;
|
|
106
|
+
display: inline;
|
|
107
|
+
}
|
|
108
|
+
.hljs-md li > ul,
|
|
109
|
+
.hljs-md li > ol {
|
|
110
|
+
margin: 0.1em 0;
|
|
111
|
+
}
|
|
112
|
+
.hljs-md .code-block-wrapper {
|
|
113
|
+
position: relative;
|
|
114
|
+
margin: 0.5em 0;
|
|
115
|
+
}
|
|
116
|
+
.hljs-md .code-block-wrapper pre {
|
|
117
|
+
position: relative;
|
|
118
|
+
margin: 0;
|
|
119
|
+
padding: 0.75em 1em;
|
|
120
|
+
border-radius: 6px;
|
|
121
|
+
background: #1e1e1e;
|
|
122
|
+
overflow-x: auto;
|
|
123
|
+
}
|
|
124
|
+
.hljs-md .code-block-toolbar {
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: 6px;
|
|
127
|
+
right: 6px;
|
|
128
|
+
display: flex;
|
|
129
|
+
gap: 4px;
|
|
130
|
+
z-index: 10;
|
|
131
|
+
}
|
|
132
|
+
.hljs-md .code-block-btn {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
width: 26px;
|
|
137
|
+
height: 26px;
|
|
138
|
+
padding: 0;
|
|
139
|
+
background: #2d2d2d;
|
|
140
|
+
border: 1px solid #404040;
|
|
141
|
+
border-radius: 4px;
|
|
142
|
+
color: #888;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
transition: all 0.15s ease;
|
|
145
|
+
opacity: 0.7;
|
|
146
|
+
}
|
|
147
|
+
.hljs-md .code-block-wrapper:hover .code-block-btn {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
}
|
|
150
|
+
.hljs-md .code-block-btn:hover {
|
|
151
|
+
background: #3d3d3d;
|
|
152
|
+
border-color: #555;
|
|
153
|
+
color: #ccc;
|
|
154
|
+
}
|
|
155
|
+
.hljs-md .code-block-btn.copied {
|
|
156
|
+
color: ${tokens.colors.accentSuccess};
|
|
157
|
+
}
|
|
158
|
+
.hljs-md code {
|
|
159
|
+
font-family: ${tokens.typography.fontFamilyMono};
|
|
160
|
+
font-size: 0.9em;
|
|
161
|
+
}
|
|
162
|
+
.hljs-md :not(pre) > code {
|
|
163
|
+
padding: 0.15em 0.4em;
|
|
164
|
+
border-radius: 4px;
|
|
165
|
+
background: rgba(255, 255, 255, 0.1);
|
|
166
|
+
}
|
|
167
|
+
.hljs-md pre code {
|
|
168
|
+
padding: 0;
|
|
169
|
+
background: transparent;
|
|
170
|
+
font-size: 0.875em;
|
|
171
|
+
line-height: 1.5;
|
|
172
|
+
}
|
|
173
|
+
.hljs-md h1, .hljs-md h2, .hljs-md h3, .hljs-md h4 {
|
|
174
|
+
margin: 0.5em 0 0.25em 0;
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
line-height: 1.3;
|
|
177
|
+
}
|
|
178
|
+
.hljs-md h1:first-child, .hljs-md h2:first-child, .hljs-md h3:first-child {
|
|
179
|
+
margin-top: 0;
|
|
180
|
+
}
|
|
181
|
+
.hljs-md h1 { font-size: 1.5em; }
|
|
182
|
+
.hljs-md h2 { font-size: 1.25em; }
|
|
183
|
+
.hljs-md h3 { font-size: 1.1em; }
|
|
184
|
+
.hljs-md h4 { font-size: 1em; }
|
|
185
|
+
.hljs-md blockquote {
|
|
186
|
+
margin: 0.5em 0;
|
|
187
|
+
padding: 0.5em 0 0.5em 1em;
|
|
188
|
+
border-left: 3px solid ${tokens.colors.borderSecondary};
|
|
189
|
+
color: ${tokens.colors.textSecondary};
|
|
190
|
+
}
|
|
191
|
+
.hljs-md a {
|
|
192
|
+
color: ${tokens.colors.accentPrimary};
|
|
193
|
+
text-decoration: underline;
|
|
194
|
+
}
|
|
195
|
+
.hljs-md hr {
|
|
196
|
+
margin: 1em 0;
|
|
197
|
+
border: none;
|
|
198
|
+
border-top: 1px solid ${tokens.colors.borderSecondary};
|
|
199
|
+
}
|
|
200
|
+
.hljs-md table {
|
|
201
|
+
width: 100%;
|
|
202
|
+
margin: 0.5em 0;
|
|
203
|
+
border-collapse: collapse;
|
|
204
|
+
}
|
|
205
|
+
.hljs-md th, .hljs-md td {
|
|
206
|
+
padding: 0.5em;
|
|
207
|
+
border: 1px solid ${tokens.colors.borderPrimary};
|
|
208
|
+
text-align: left;
|
|
209
|
+
}
|
|
210
|
+
.hljs-md th {
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
background: rgba(255, 255, 255, 0.05);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* highlight.js dark theme (VS Code Dark+ inspired) */
|
|
216
|
+
.hljs {
|
|
217
|
+
color: #d4d4d4;
|
|
218
|
+
background: #1e1e1e;
|
|
219
|
+
}
|
|
220
|
+
.hljs-keyword,
|
|
221
|
+
.hljs-selector-tag,
|
|
222
|
+
.hljs-title,
|
|
223
|
+
.hljs-section,
|
|
224
|
+
.hljs-doctag,
|
|
225
|
+
.hljs-name,
|
|
226
|
+
.hljs-strong {
|
|
227
|
+
color: #569cd6;
|
|
228
|
+
font-weight: normal;
|
|
229
|
+
}
|
|
230
|
+
.hljs-built_in,
|
|
231
|
+
.hljs-literal,
|
|
232
|
+
.hljs-type,
|
|
233
|
+
.hljs-params,
|
|
234
|
+
.hljs-meta,
|
|
235
|
+
.hljs-link {
|
|
236
|
+
color: #4ec9b0;
|
|
237
|
+
}
|
|
238
|
+
.hljs-string,
|
|
239
|
+
.hljs-symbol,
|
|
240
|
+
.hljs-bullet,
|
|
241
|
+
.hljs-addition {
|
|
242
|
+
color: #ce9178;
|
|
243
|
+
}
|
|
244
|
+
.hljs-number {
|
|
245
|
+
color: #b5cea8;
|
|
246
|
+
}
|
|
247
|
+
.hljs-comment,
|
|
248
|
+
.hljs-quote,
|
|
249
|
+
.hljs-deletion {
|
|
250
|
+
color: #6a9955;
|
|
251
|
+
}
|
|
252
|
+
.hljs-variable,
|
|
253
|
+
.hljs-template-variable,
|
|
254
|
+
.hljs-attr {
|
|
255
|
+
color: #9cdcfe;
|
|
256
|
+
}
|
|
257
|
+
.hljs-regexp,
|
|
258
|
+
.hljs-selector-id,
|
|
259
|
+
.hljs-selector-class {
|
|
260
|
+
color: #d7ba7d;
|
|
261
|
+
}
|
|
262
|
+
.hljs-emphasis {
|
|
263
|
+
font-style: italic;
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
|
|
267
|
+
// Configure marked with highlight.js
|
|
268
|
+
marked.use({
|
|
269
|
+
gfm: true,
|
|
270
|
+
breaks: false,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Highlight code using highlight.js
|
|
275
|
+
*/
|
|
276
|
+
function highlightCode(code: string, lang: string): string {
|
|
277
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
278
|
+
try {
|
|
279
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
280
|
+
} catch {
|
|
281
|
+
// Fall through to auto-detect
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Auto-detect language
|
|
286
|
+
try {
|
|
287
|
+
return hljs.highlightAuto(code).value;
|
|
288
|
+
} catch {
|
|
289
|
+
// Fallback to escaped plain text
|
|
290
|
+
return code
|
|
291
|
+
.replace(/&/g, '&')
|
|
292
|
+
.replace(/</g, '<')
|
|
293
|
+
.replace(/>/g, '>');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Generate unique ID for code blocks
|
|
299
|
+
*/
|
|
300
|
+
let codeBlockCounter = 0;
|
|
301
|
+
function generateCodeBlockId(): string {
|
|
302
|
+
return `code-block-${++codeBlockCounter}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Process markdown and highlight code blocks
|
|
307
|
+
* Returns HTML with data attributes for code block content
|
|
308
|
+
*/
|
|
309
|
+
function renderMarkdown(content: string): string {
|
|
310
|
+
// Parse markdown to HTML
|
|
311
|
+
const html = marked.parse(content, { async: false }) as string;
|
|
312
|
+
|
|
313
|
+
// Find and highlight code blocks with language
|
|
314
|
+
const codeBlockRegex = /<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g;
|
|
315
|
+
let result = html.replace(codeBlockRegex, (_, lang, code) => {
|
|
316
|
+
const decoded = code
|
|
317
|
+
.replace(/</g, '<')
|
|
318
|
+
.replace(/>/g, '>')
|
|
319
|
+
.replace(/&/g, '&')
|
|
320
|
+
.replace(/"/g, '"');
|
|
321
|
+
const highlighted = highlightCode(decoded, lang);
|
|
322
|
+
const id = generateCodeBlockId();
|
|
323
|
+
const encodedCode = encodeURIComponent(decoded);
|
|
324
|
+
return `<div class="code-block-wrapper" data-code-id="${id}" data-code="${encodedCode}"><pre><code class="hljs language-${lang}">${highlighted}</code></pre><div class="code-block-toolbar"><button class="code-block-btn" data-action="copy" data-code-id="${id}" title="Copy code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button><button class="code-block-btn" data-action="emblem" data-code-id="${id}" title="Open in Emblem AI"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg></button></div></div>`;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Also handle code blocks without language
|
|
328
|
+
const plainCodeRegex = /<pre><code>([\s\S]*?)<\/code><\/pre>/g;
|
|
329
|
+
result = result.replace(plainCodeRegex, (_, code) => {
|
|
330
|
+
const decoded = code
|
|
331
|
+
.replace(/</g, '<')
|
|
332
|
+
.replace(/>/g, '>')
|
|
333
|
+
.replace(/&/g, '&')
|
|
334
|
+
.replace(/"/g, '"');
|
|
335
|
+
const highlighted = highlightCode(decoded, '');
|
|
336
|
+
const id = generateCodeBlockId();
|
|
337
|
+
const encodedCode = encodeURIComponent(decoded);
|
|
338
|
+
return `<div class="code-block-wrapper" data-code-id="${id}" data-code="${encodedCode}"><pre><code class="hljs">${highlighted}</code></pre><div class="code-block-toolbar"><button class="code-block-btn" data-action="copy" data-code-id="${id}" title="Copy code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button><button class="code-block-btn" data-action="emblem" data-code-id="${id}" title="Open in Emblem AI"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg></button></div></div>`;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|
345
|
+
const [rendered, setRendered] = useState<string>('');
|
|
346
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
347
|
+
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
try {
|
|
350
|
+
const html = renderMarkdown(content);
|
|
351
|
+
setRendered(html);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.error('[MarkdownContent] Render error:', err);
|
|
354
|
+
// Fallback to escaped plain text
|
|
355
|
+
setRendered(
|
|
356
|
+
content
|
|
357
|
+
.replace(/&/g, '&')
|
|
358
|
+
.replace(/</g, '<')
|
|
359
|
+
.replace(/>/g, '>')
|
|
360
|
+
.replace(/\n/g, '<br>')
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}, [content]);
|
|
364
|
+
|
|
365
|
+
// Attach event handlers after render
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
const container = containerRef.current;
|
|
368
|
+
if (!container) return;
|
|
369
|
+
|
|
370
|
+
const handleClick = async (e: MouseEvent) => {
|
|
371
|
+
const target = e.target as HTMLElement;
|
|
372
|
+
const button = target.closest('[data-action]') as HTMLElement;
|
|
373
|
+
if (!button) return;
|
|
374
|
+
|
|
375
|
+
const action = button.dataset.action;
|
|
376
|
+
const codeId = button.dataset.codeId;
|
|
377
|
+
if (!codeId) return;
|
|
378
|
+
|
|
379
|
+
const wrapper = container.querySelector(`[data-code-id="${codeId}"]`) as HTMLElement;
|
|
380
|
+
if (!wrapper) return;
|
|
381
|
+
|
|
382
|
+
const encodedCode = wrapper.dataset.code;
|
|
383
|
+
if (!encodedCode) return;
|
|
384
|
+
|
|
385
|
+
const code = decodeURIComponent(encodedCode);
|
|
386
|
+
|
|
387
|
+
if (action === 'copy') {
|
|
388
|
+
e.preventDefault();
|
|
389
|
+
try {
|
|
390
|
+
await navigator.clipboard.writeText(code);
|
|
391
|
+
button.classList.add('copied');
|
|
392
|
+
// Change icon to checkmark temporarily
|
|
393
|
+
button.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
394
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
395
|
+
</svg>`;
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
button.classList.remove('copied');
|
|
398
|
+
button.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
399
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
400
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
401
|
+
</svg>`;
|
|
402
|
+
}, 2000);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
console.error('Failed to copy:', err);
|
|
405
|
+
}
|
|
406
|
+
} else if (action === 'emblem') {
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
// Open Emblem AI with code in URL (no auto-submit)
|
|
409
|
+
const url = `https://build.emblemvault.ai/?q=${encodeURIComponent(code)}`;
|
|
410
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
container.addEventListener('click', handleClick);
|
|
415
|
+
return () => container.removeEventListener('click', handleClick);
|
|
416
|
+
}, [rendered]);
|
|
417
|
+
|
|
418
|
+
return (
|
|
419
|
+
<>
|
|
420
|
+
<style>{scopedStyles}</style>
|
|
421
|
+
<div
|
|
422
|
+
ref={containerRef}
|
|
423
|
+
style={containerStyle}
|
|
424
|
+
className={`hljs-md ${className || ''}`}
|
|
425
|
+
dangerouslySetInnerHTML={{ __html: rendered }}
|
|
426
|
+
/>
|
|
427
|
+
</>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export default MarkdownContent;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { ConnectButton } from './ConnectButton';
|
|
6
|
+
export type { ConnectButtonProps } from './ConnectButton';
|
|
7
|
+
|
|
8
|
+
export { AuthStatus } from './AuthStatus';
|
|
9
|
+
export type { AuthStatusProps } from './AuthStatus';
|
|
10
|
+
|
|
11
|
+
export { HustleChat } from './HustleChat';
|
|
12
|
+
export type { HustleChatProps } from './HustleChat';
|
|
13
|
+
|
|
14
|
+
export { MarkdownContent } from './MarkdownContent';
|
|
15
|
+
export type { MarkdownContentProps } from './MarkdownContent';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { useEmblemAuth, EmblemAuthProvider, resetAuthSDK } from './useEmblemAuth';
|
|
6
|
+
export { useHustle, HustleProvider } from './useHustle';
|
|
7
|
+
export { usePlugins } from './usePlugins';
|
|
8
|
+
|
|
9
|
+
// Re-export types for convenience
|
|
10
|
+
export type {
|
|
11
|
+
AuthSession,
|
|
12
|
+
AuthUser,
|
|
13
|
+
VaultInfo,
|
|
14
|
+
EmblemAuthConfig,
|
|
15
|
+
EmblemAuthContextValue,
|
|
16
|
+
EmblemAuthProviderProps,
|
|
17
|
+
ChatMessage,
|
|
18
|
+
ToolCall,
|
|
19
|
+
ToolResult,
|
|
20
|
+
Attachment,
|
|
21
|
+
Model,
|
|
22
|
+
ChatOptions,
|
|
23
|
+
StreamOptions,
|
|
24
|
+
StreamChunk,
|
|
25
|
+
ChatResponse,
|
|
26
|
+
ToolStartEvent,
|
|
27
|
+
ToolEndEvent,
|
|
28
|
+
StreamEndEvent,
|
|
29
|
+
HustleConfig,
|
|
30
|
+
HustleContextValue,
|
|
31
|
+
HustleProviderProps,
|
|
32
|
+
// Plugin types
|
|
33
|
+
HustlePlugin,
|
|
34
|
+
StoredPlugin,
|
|
35
|
+
HydratedPlugin,
|
|
36
|
+
ClientToolDefinition,
|
|
37
|
+
ToolExecutor,
|
|
38
|
+
} from '../types';
|
|
39
|
+
|
|
40
|
+
export type { UsePluginsReturn } from './usePlugins';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEmblemAuth hook
|
|
3
|
+
*
|
|
4
|
+
* Re-exported from EmblemAuthProvider for convenience.
|
|
5
|
+
* Can be imported from either location:
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useEmblemAuth } from '@/hooks/useEmblemAuth';
|
|
10
|
+
* // or
|
|
11
|
+
* import { useEmblemAuth } from '@/providers/EmblemAuthProvider';
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { useEmblemAuth } from '../providers/EmblemAuthProvider';
|
|
15
|
+
|
|
16
|
+
// Also export the provider and reset function for completeness
|
|
17
|
+
export { EmblemAuthProvider, resetAuthSDK } from '../providers/EmblemAuthProvider';
|
|
18
|
+
|
|
19
|
+
// Re-export auth types for convenience
|
|
20
|
+
export type {
|
|
21
|
+
AuthSession,
|
|
22
|
+
AuthUser,
|
|
23
|
+
VaultInfo,
|
|
24
|
+
EmblemAuthConfig,
|
|
25
|
+
EmblemAuthContextValue,
|
|
26
|
+
EmblemAuthProviderProps,
|
|
27
|
+
} from '../types';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useHustle hook
|
|
3
|
+
*
|
|
4
|
+
* Re-exported from HustleProvider for convenience.
|
|
5
|
+
* Can be imported from either location:
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useHustle } from '@/hooks/useHustle';
|
|
10
|
+
* // or
|
|
11
|
+
* import { useHustle } from '@/providers/HustleProvider';
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { useHustle } from '../providers/HustleProvider';
|
|
15
|
+
|
|
16
|
+
// Also export the provider for completeness
|
|
17
|
+
export { HustleProvider } from '../providers/HustleProvider';
|
|
18
|
+
|
|
19
|
+
// Re-export hustle types for convenience
|
|
20
|
+
export type {
|
|
21
|
+
ChatMessage,
|
|
22
|
+
ToolCall,
|
|
23
|
+
ToolResult,
|
|
24
|
+
Attachment,
|
|
25
|
+
Model,
|
|
26
|
+
ChatOptions,
|
|
27
|
+
StreamOptions,
|
|
28
|
+
StreamChunk,
|
|
29
|
+
ChatResponse,
|
|
30
|
+
ToolStartEvent,
|
|
31
|
+
ToolEndEvent,
|
|
32
|
+
StreamEndEvent,
|
|
33
|
+
HustleConfig,
|
|
34
|
+
HustleContextValue,
|
|
35
|
+
HustleProviderProps,
|
|
36
|
+
} from '../types';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* usePlugins Hook
|
|
5
|
+
*
|
|
6
|
+
* Manages plugin state with localStorage persistence and cross-tab sync.
|
|
7
|
+
* Supports instance-scoped storage for multiple HustleProviders.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
11
|
+
import { pluginRegistry, hydratePlugin } from '../utils/pluginRegistry';
|
|
12
|
+
import type { StoredPlugin, HustlePlugin, HydratedPlugin } from '../types';
|
|
13
|
+
|
|
14
|
+
// Re-export hydratePlugin for convenience
|
|
15
|
+
export { hydratePlugin };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the storage key for a given instance
|
|
19
|
+
*/
|
|
20
|
+
function getStorageKey(instanceId: string): string {
|
|
21
|
+
return `hustle-plugins-${instanceId}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return type for usePlugins hook
|
|
26
|
+
*/
|
|
27
|
+
export interface UsePluginsReturn {
|
|
28
|
+
/** All registered plugins (with enabled state) */
|
|
29
|
+
plugins: StoredPlugin[];
|
|
30
|
+
/** Only enabled plugins (hydrated with executors) */
|
|
31
|
+
enabledPlugins: HydratedPlugin[];
|
|
32
|
+
/** Register a new plugin */
|
|
33
|
+
registerPlugin: (plugin: HustlePlugin) => void;
|
|
34
|
+
/** Unregister a plugin by name */
|
|
35
|
+
unregisterPlugin: (name: string) => void;
|
|
36
|
+
/** Enable a plugin */
|
|
37
|
+
enablePlugin: (name: string) => void;
|
|
38
|
+
/** Disable a plugin */
|
|
39
|
+
disablePlugin: (name: string) => void;
|
|
40
|
+
/** Check if a plugin is registered */
|
|
41
|
+
isRegistered: (name: string) => boolean;
|
|
42
|
+
/** Check if a plugin is enabled */
|
|
43
|
+
isEnabled: (name: string) => boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hook for managing plugins
|
|
48
|
+
*
|
|
49
|
+
* @param instanceId - Optional instance ID for scoping plugin storage (defaults to 'default')
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* const { plugins, registerPlugin, enabledPlugins } = usePlugins();
|
|
54
|
+
*
|
|
55
|
+
* // Install a plugin
|
|
56
|
+
* registerPlugin(myPlugin);
|
|
57
|
+
*
|
|
58
|
+
* // Check enabled plugins
|
|
59
|
+
* console.log('Active tools:', enabledPlugins.flatMap(p => p.tools));
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function usePlugins(instanceId: string = 'default'): UsePluginsReturn {
|
|
63
|
+
const [plugins, setPlugins] = useState<StoredPlugin[]>([]);
|
|
64
|
+
|
|
65
|
+
// Load initial plugins and subscribe to changes
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
// Load initial state
|
|
68
|
+
setPlugins(pluginRegistry.loadFromStorage(instanceId));
|
|
69
|
+
|
|
70
|
+
// Subscribe to registry changes for this instance
|
|
71
|
+
const unsubscribe = pluginRegistry.onChange(setPlugins, instanceId);
|
|
72
|
+
|
|
73
|
+
// Listen to storage events for cross-tab sync
|
|
74
|
+
const storageKey = getStorageKey(instanceId);
|
|
75
|
+
const handleStorage = (e: StorageEvent) => {
|
|
76
|
+
if (e.key === storageKey) {
|
|
77
|
+
setPlugins(pluginRegistry.loadFromStorage(instanceId));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
window.addEventListener('storage', handleStorage);
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
unsubscribe();
|
|
84
|
+
window.removeEventListener('storage', handleStorage);
|
|
85
|
+
};
|
|
86
|
+
}, [instanceId]);
|
|
87
|
+
|
|
88
|
+
// Register a new plugin
|
|
89
|
+
const registerPlugin = useCallback((plugin: HustlePlugin) => {
|
|
90
|
+
pluginRegistry.register(plugin, true, instanceId);
|
|
91
|
+
}, [instanceId]);
|
|
92
|
+
|
|
93
|
+
// Unregister a plugin
|
|
94
|
+
const unregisterPlugin = useCallback((name: string) => {
|
|
95
|
+
pluginRegistry.unregister(name, instanceId);
|
|
96
|
+
}, [instanceId]);
|
|
97
|
+
|
|
98
|
+
// Enable a plugin
|
|
99
|
+
const enablePlugin = useCallback((name: string) => {
|
|
100
|
+
pluginRegistry.setEnabled(name, true, instanceId);
|
|
101
|
+
}, [instanceId]);
|
|
102
|
+
|
|
103
|
+
// Disable a plugin
|
|
104
|
+
const disablePlugin = useCallback((name: string) => {
|
|
105
|
+
pluginRegistry.setEnabled(name, false, instanceId);
|
|
106
|
+
}, [instanceId]);
|
|
107
|
+
|
|
108
|
+
// Check if plugin is registered
|
|
109
|
+
const isRegistered = useCallback(
|
|
110
|
+
(name: string) => plugins.some(p => p.name === name),
|
|
111
|
+
[plugins]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Check if plugin is enabled
|
|
115
|
+
const isEnabled = useCallback(
|
|
116
|
+
(name: string) => plugins.some(p => p.name === name && p.enabled),
|
|
117
|
+
[plugins]
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Get enabled plugins with hydrated executors
|
|
121
|
+
const enabledPlugins = plugins.filter(p => p.enabled).map(hydratePlugin);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
plugins,
|
|
125
|
+
enabledPlugins,
|
|
126
|
+
registerPlugin,
|
|
127
|
+
unregisterPlugin,
|
|
128
|
+
enablePlugin,
|
|
129
|
+
disablePlugin,
|
|
130
|
+
isRegistered,
|
|
131
|
+
isEnabled,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default usePlugins;
|