@chaaskit/client 0.1.1 → 0.1.2

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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/dist/lib/index.js +970 -80
  3. package/dist/lib/index.js.map +1 -1
  4. package/dist/lib/routes/AdminPromoCodesRoute.js +19 -0
  5. package/dist/lib/routes/AdminPromoCodesRoute.js.map +1 -0
  6. package/dist/lib/routes/AdminWaitlistRoute.js +19 -0
  7. package/dist/lib/routes/AdminWaitlistRoute.js.map +1 -0
  8. package/dist/lib/routes.js +47 -37
  9. package/dist/lib/routes.js.map +1 -1
  10. package/dist/lib/ssr-utils.js +36 -16
  11. package/dist/lib/ssr-utils.js.map +1 -1
  12. package/dist/lib/styles.css +37 -0
  13. package/dist/lib/useExtensions-B5nX_8XD.js.map +1 -1
  14. package/package.json +20 -12
  15. package/src/components/MessageItem.tsx +35 -4
  16. package/src/components/MessageList.tsx +51 -5
  17. package/src/components/OAuthAppsSection.tsx +1 -1
  18. package/src/components/Sidebar.tsx +1 -3
  19. package/src/components/ToolCallDisplay.tsx +102 -11
  20. package/src/components/tool-renderers/DocumentListRenderer.tsx +44 -0
  21. package/src/components/tool-renderers/DocumentReadRenderer.tsx +33 -0
  22. package/src/components/tool-renderers/DocumentSaveRenderer.tsx +32 -0
  23. package/src/components/tool-renderers/DocumentSearchRenderer.tsx +33 -0
  24. package/src/components/tool-renderers/index.ts +36 -0
  25. package/src/components/tool-renderers/utils.ts +7 -0
  26. package/src/contexts/AuthContext.tsx +16 -6
  27. package/src/contexts/ConfigContext.tsx +22 -28
  28. package/src/extensions/registry.ts +2 -1
  29. package/src/hooks/__tests__/basePath.test.ts +42 -0
  30. package/src/index.tsx +5 -2
  31. package/src/pages/AdminDashboardPage.tsx +15 -1
  32. package/src/pages/AdminPromoCodesPage.tsx +378 -0
  33. package/src/pages/AdminTeamPage.tsx +29 -1
  34. package/src/pages/AdminTeamsPage.tsx +15 -1
  35. package/src/pages/AdminUsersPage.tsx +15 -1
  36. package/src/pages/AdminWaitlistPage.tsx +156 -0
  37. package/src/pages/RegisterPage.tsx +91 -9
  38. package/src/routes/AdminPromoCodesRoute.tsx +24 -0
  39. package/src/routes/AdminWaitlistRoute.tsx +24 -0
  40. package/src/routes/index.ts +2 -0
  41. package/src/ssr-utils.tsx +32 -12
  42. package/src/stores/chatStore.ts +5 -0
  43. package/dist/favicon.svg +0 -11
  44. package/dist/index.html +0 -17
  45. package/dist/logo.svg +0 -12
@@ -0,0 +1,156 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Link } from 'react-router';
3
+ import { LayoutDashboard, Users, Building2, X, Mail, Tag } from 'lucide-react';
4
+ import { useConfig } from '../contexts/ConfigContext';
5
+ import { useAppPath } from '../hooks/useAppPath';
6
+ import { api } from '../utils/api';
7
+ import type { AdminWaitlistEntry, AdminWaitlistResponse } from '@chaaskit/shared';
8
+
9
+ export default function AdminWaitlistPage() {
10
+ const config = useConfig();
11
+ const appPath = useAppPath();
12
+ const [entries, setEntries] = useState<AdminWaitlistEntry[]>([]);
13
+ const [isLoading, setIsLoading] = useState(true);
14
+ const [error, setError] = useState('');
15
+ const [success, setSuccess] = useState('');
16
+
17
+ async function loadWaitlist() {
18
+ setIsLoading(true);
19
+ setError('');
20
+
21
+ try {
22
+ const response = await api.get<AdminWaitlistResponse>('/api/admin/waitlist');
23
+ setEntries(response.entries);
24
+ } catch (err) {
25
+ setError(err instanceof Error ? err.message : 'Failed to load waitlist');
26
+ } finally {
27
+ setIsLoading(false);
28
+ }
29
+ }
30
+
31
+ useEffect(() => {
32
+ loadWaitlist();
33
+ }, []);
34
+
35
+ async function handleInvite(entryId: string, email: string) {
36
+ setError('');
37
+ setSuccess('');
38
+
39
+ try {
40
+ await api.post(`/api/admin/waitlist/${entryId}/invite`, {});
41
+ setSuccess(`Invite sent to ${email}`);
42
+ loadWaitlist();
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err.message : 'Failed to send invite');
45
+ }
46
+ }
47
+
48
+ return (
49
+ <div className="min-h-screen bg-background p-4 sm:p-8">
50
+ <div className="mx-auto max-w-6xl">
51
+ <div className="flex items-center justify-between mb-4">
52
+ <h1 className="text-xl sm:text-2xl font-bold text-text-primary">Admin</h1>
53
+ <Link
54
+ to={appPath('/')}
55
+ className="flex items-center justify-center rounded-lg p-2 text-text-muted hover:text-text-primary hover:bg-background-secondary"
56
+ aria-label="Close"
57
+ >
58
+ <X size={20} />
59
+ </Link>
60
+ </div>
61
+
62
+ <div className="flex items-center gap-2 mb-6 sm:mb-8">
63
+ <Link
64
+ to={appPath('/admin')}
65
+ className="flex items-center gap-1.5 rounded-full bg-background-secondary px-4 py-2 text-sm font-medium text-text-secondary hover:bg-background-secondary/80"
66
+ >
67
+ <LayoutDashboard size={16} />
68
+ Overview
69
+ </Link>
70
+ <Link
71
+ to={appPath('/admin/users')}
72
+ className="flex items-center gap-1.5 rounded-full bg-background-secondary px-4 py-2 text-sm font-medium text-text-secondary hover:bg-background-secondary/80"
73
+ >
74
+ <Users size={16} />
75
+ Users
76
+ </Link>
77
+ {config.teams?.enabled && (
78
+ <Link
79
+ to={appPath('/admin/teams')}
80
+ className="flex items-center gap-1.5 rounded-full bg-background-secondary px-4 py-2 text-sm font-medium text-text-secondary hover:bg-background-secondary/80"
81
+ >
82
+ <Building2 size={16} />
83
+ Teams
84
+ </Link>
85
+ )}
86
+ <Link
87
+ to={appPath('/admin/promo-codes')}
88
+ className="flex items-center gap-1.5 rounded-full bg-background-secondary px-4 py-2 text-sm font-medium text-text-secondary hover:bg-background-secondary/80"
89
+ >
90
+ <Tag size={16} />
91
+ Promo Codes
92
+ </Link>
93
+ <span className="flex items-center gap-1.5 rounded-full bg-primary px-4 py-2 text-sm font-medium text-white">
94
+ <Mail size={16} />
95
+ Waitlist
96
+ </span>
97
+ </div>
98
+
99
+ {error && (
100
+ <div className="mb-6 rounded-lg bg-error/10 p-4 text-sm text-error">
101
+ {error}
102
+ </div>
103
+ )}
104
+
105
+ {success && (
106
+ <div className="mb-6 rounded-lg bg-success/10 p-4 text-sm text-success">
107
+ {success}
108
+ </div>
109
+ )}
110
+
111
+ <div className="rounded-lg bg-background-secondary overflow-hidden">
112
+ <div className="hidden md:block px-4 py-3 bg-background">
113
+ <div className="grid grid-cols-12 gap-4 text-sm font-medium text-text-muted">
114
+ <div className="col-span-4">Email</div>
115
+ <div className="col-span-2">Name</div>
116
+ <div className="col-span-2">Status</div>
117
+ <div className="col-span-2">Joined</div>
118
+ <div className="col-span-2">Action</div>
119
+ </div>
120
+ </div>
121
+
122
+ <div className="divide-y divide-background">
123
+ {isLoading ? (
124
+ <div className="px-4 py-8 text-center text-text-muted">Loading...</div>
125
+ ) : entries.length === 0 ? (
126
+ <div className="px-4 py-8 text-center text-text-muted">No waitlist entries</div>
127
+ ) : (
128
+ entries.map((entry) => (
129
+ <div key={entry.id} className="px-4 py-3 hover:bg-background/50">
130
+ <div className="grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-4 text-sm">
131
+ <div className="md:col-span-4 text-text-primary">{entry.email}</div>
132
+ <div className="md:col-span-2 text-text-secondary">{entry.name || '-'}</div>
133
+ <div className="md:col-span-2 capitalize text-text-secondary">{entry.status.replace('_', ' ')}</div>
134
+ <div className="md:col-span-2 text-text-muted">
135
+ {new Date(entry.createdAt).toLocaleDateString()}
136
+ </div>
137
+ <div className="md:col-span-2">
138
+ <button
139
+ type="button"
140
+ onClick={() => handleInvite(entry.id, entry.email)}
141
+ disabled={entry.status === 'invited'}
142
+ className="rounded-lg bg-primary px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-hover disabled:opacity-50"
143
+ >
144
+ {entry.status === 'invited' ? 'Invited' : 'Invite'}
145
+ </button>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ ))
150
+ )}
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ );
156
+ }
@@ -1,9 +1,10 @@
1
1
  import { useState } from 'react';
2
- import { Link, useNavigate } from 'react-router';
2
+ import { Link, useNavigate, useSearchParams } from 'react-router';
3
3
  import { useAuth } from '../contexts/AuthContext';
4
4
  import { useConfig } from '../contexts/ConfigContext';
5
5
  import { useTheme } from '../contexts/ThemeContext';
6
6
  import { useAppPath } from '../hooks/useAppPath';
7
+ import { api } from '../utils/api';
7
8
 
8
9
  export default function RegisterPage() {
9
10
  const navigate = useNavigate();
@@ -11,12 +12,22 @@ export default function RegisterPage() {
11
12
  const { register } = useAuth();
12
13
  const config = useConfig();
13
14
  const { theme } = useTheme();
15
+ const [searchParams] = useSearchParams();
16
+
17
+ const inviteToken = searchParams.get('invite') || undefined;
18
+ const referralCode = searchParams.get('ref') || undefined;
19
+ const gating = config.auth.gating;
20
+ const signupsRestricted = gating?.mode && gating.mode !== 'open' && !inviteToken;
21
+ const waitlistEnabled = gating?.waitlistEnabled ?? false;
22
+ const showWaitlist = !!signupsRestricted && waitlistEnabled;
23
+ const showRestrictedMessage = !!signupsRestricted && !waitlistEnabled;
14
24
 
15
25
  const [name, setName] = useState('');
16
26
  const [email, setEmail] = useState('');
17
27
  const [password, setPassword] = useState('');
18
28
  const [error, setError] = useState('');
19
29
  const [isLoading, setIsLoading] = useState(false);
30
+ const [waitlistStatus, setWaitlistStatus] = useState<'idle' | 'submitted'>('idle');
20
31
 
21
32
  async function handleSubmit(e: React.FormEvent) {
22
33
  e.preventDefault();
@@ -30,7 +41,10 @@ export default function RegisterPage() {
30
41
  setIsLoading(true);
31
42
 
32
43
  try {
33
- const { requiresVerification } = await register(email, password, name || undefined);
44
+ const { requiresVerification } = await register(email, password, name || undefined, {
45
+ inviteToken,
46
+ referralCode,
47
+ });
34
48
  if (requiresVerification) {
35
49
  navigate('/verify-email');
36
50
  } else {
@@ -43,6 +57,18 @@ export default function RegisterPage() {
43
57
  }
44
58
  }
45
59
 
60
+ async function handleWaitlistSubmit(e: React.FormEvent) {
61
+ e.preventDefault();
62
+ setError('');
63
+
64
+ try {
65
+ await api.post('/api/auth/waitlist', { email, name: name || undefined });
66
+ setWaitlistStatus('submitted');
67
+ } catch (err) {
68
+ setError(err instanceof Error ? err.message : 'Failed to join waitlist');
69
+ }
70
+ }
71
+
46
72
  return (
47
73
  <div className="flex min-h-screen items-center justify-center bg-background p-4">
48
74
  <div className="w-full max-w-md">
@@ -66,7 +92,60 @@ export default function RegisterPage() {
66
92
  </div>
67
93
  )}
68
94
 
69
- <form onSubmit={handleSubmit} className="space-y-4">
95
+ {showRestrictedMessage ? (
96
+ <div className="rounded-lg border border-border bg-background-secondary p-4 text-sm text-text-secondary">
97
+ Signups are currently closed. Please check back later.
98
+ </div>
99
+ ) : showWaitlist ? (
100
+ <div className="rounded-lg border border-border bg-background-secondary p-4">
101
+ <p className="text-sm text-text-secondary">
102
+ Signups are currently restricted. Join the waitlist to get an invite.
103
+ </p>
104
+
105
+ {waitlistStatus === 'submitted' ? (
106
+ <div className="mt-4 rounded-lg bg-success/10 p-3 text-sm text-success">
107
+ Thanks! You’re on the waitlist.
108
+ </div>
109
+ ) : (
110
+ <form onSubmit={handleWaitlistSubmit} className="mt-4 space-y-3">
111
+ <div>
112
+ <label htmlFor="waitlist-name" className="block text-sm font-medium text-text-primary">
113
+ Name (optional)
114
+ </label>
115
+ <input
116
+ type="text"
117
+ id="waitlist-name"
118
+ value={name}
119
+ onChange={(e) => setName(e.target.value)}
120
+ className="mt-1 w-full rounded-lg border border-input-border bg-input-background px-4 py-2 text-text-primary focus:border-primary focus:outline-none"
121
+ />
122
+ </div>
123
+
124
+ <div>
125
+ <label htmlFor="waitlist-email" className="block text-sm font-medium text-text-primary">
126
+ Email
127
+ </label>
128
+ <input
129
+ type="email"
130
+ id="waitlist-email"
131
+ value={email}
132
+ onChange={(e) => setEmail(e.target.value)}
133
+ required
134
+ className="mt-1 w-full rounded-lg border border-input-border bg-input-background px-4 py-2 text-text-primary focus:border-primary focus:outline-none"
135
+ />
136
+ </div>
137
+
138
+ <button
139
+ type="submit"
140
+ className="w-full rounded-lg bg-primary px-4 py-2 font-medium text-white hover:bg-primary-hover"
141
+ >
142
+ Join waitlist
143
+ </button>
144
+ </form>
145
+ )}
146
+ </div>
147
+ ) : (
148
+ <form onSubmit={handleSubmit} className="space-y-4">
70
149
  <div>
71
150
  <label
72
151
  htmlFor="name"
@@ -129,13 +208,16 @@ export default function RegisterPage() {
129
208
  {isLoading ? 'Creating account...' : 'Create account'}
130
209
  </button>
131
210
  </form>
211
+ )}
132
212
 
133
- <p className="mt-6 text-center text-sm text-text-secondary">
134
- Already have an account?{' '}
135
- <Link to="/login" className="text-primary hover:underline">
136
- Sign in
137
- </Link>
138
- </p>
213
+ {!signupsRestricted && (
214
+ <p className="mt-6 text-center text-sm text-text-secondary">
215
+ Already have an account?{' '}
216
+ <Link to="/login" className="text-primary hover:underline">
217
+ Sign in
218
+ </Link>
219
+ </p>
220
+ )}
139
221
 
140
222
  <p className="mt-4 text-center text-xs text-text-muted">
141
223
  By creating an account, you agree to our{' '}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Admin Promo Codes Route - Promo code management
3
+ */
4
+ import { ChatProviders } from '../index';
5
+ import AdminPromoCodesPage from '../pages/AdminPromoCodesPage';
6
+ import { SimpleLoadingSkeleton } from '../components/LoadingSkeletons';
7
+
8
+ export function meta() {
9
+ return [{ title: 'Admin - Promo Codes' }];
10
+ }
11
+
12
+ export function links() {
13
+ return [];
14
+ }
15
+
16
+ export default function AdminPromoCodesRoute() {
17
+ return (
18
+ <ChatProviders>
19
+ <AdminPromoCodesPage />
20
+ </ChatProviders>
21
+ );
22
+ }
23
+
24
+ export { SimpleLoadingSkeleton as LoadingSkeleton };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Admin Waitlist Route - Waitlist management
3
+ */
4
+ import { ChatProviders } from '../index';
5
+ import AdminWaitlistPage from '../pages/AdminWaitlistPage';
6
+ import { SimpleLoadingSkeleton } from '../components/LoadingSkeletons';
7
+
8
+ export function meta() {
9
+ return [{ title: 'Admin - Waitlist' }];
10
+ }
11
+
12
+ export function links() {
13
+ return [];
14
+ }
15
+
16
+ export default function AdminWaitlistRoute() {
17
+ return (
18
+ <ChatProviders>
19
+ <AdminWaitlistPage />
20
+ </ChatProviders>
21
+ );
22
+ }
23
+
24
+ export { SimpleLoadingSkeleton as LoadingSkeleton };
@@ -45,6 +45,8 @@ export { default as AdminDashboardRoute, meta as adminDashboardMeta, links as ad
45
45
  export { default as AdminUsersRoute, meta as adminUsersMeta, links as adminUsersLinks, LoadingSkeleton as AdminUsersLoadingSkeleton } from './AdminUsersRoute';
46
46
  export { default as AdminTeamsRoute, meta as adminTeamsMeta, links as adminTeamsLinks, LoadingSkeleton as AdminTeamsLoadingSkeleton } from './AdminTeamsRoute';
47
47
  export { default as AdminTeamRoute, meta as adminTeamMeta, links as adminTeamLinks, LoadingSkeleton as AdminTeamLoadingSkeleton } from './AdminTeamRoute';
48
+ export { default as AdminWaitlistRoute, meta as adminWaitlistMeta, links as adminWaitlistLinks, LoadingSkeleton as AdminWaitlistLoadingSkeleton } from './AdminWaitlistRoute';
49
+ export { default as AdminPromoCodesRoute, meta as adminPromoCodesMeta, links as adminPromoCodesLinks, LoadingSkeleton as AdminPromoCodesLoadingSkeleton } from './AdminPromoCodesRoute';
48
50
 
49
51
  // Auth-related routes
50
52
  export { default as VerifyEmailRoute, meta as verifyEmailMeta, links as verifyEmailLinks, LoadingSkeleton as VerifyEmailLoadingSkeleton } from './VerifyEmailRoute';
package/src/ssr-utils.tsx CHANGED
@@ -68,29 +68,49 @@ export function ConfigScript({ config }: ConfigScriptProps) {
68
68
  auth: {
69
69
  methods: config.auth?.methods,
70
70
  allowUnauthenticated: config.auth?.allowUnauthenticated,
71
+ magicLink: config.auth?.magicLink,
72
+ emailVerification: config.auth?.emailVerification,
73
+ gating: config.auth?.gating,
71
74
  },
72
75
  payments: {
73
76
  enabled: config.payments?.enabled,
74
- plans: config.payments?.plans?.map(plan => ({
75
- id: plan.id,
76
- name: plan.name,
77
- type: plan.type,
78
- })),
77
+ provider: config.payments?.provider,
79
78
  },
80
79
  legal: config.legal,
81
80
  sharing: config.sharing,
82
81
  teams: config.teams,
83
82
  projects: config.projects,
84
- documents: {
85
- enabled: config.documents?.enabled,
86
- },
83
+ documents: config.documents ? {
84
+ enabled: config.documents.enabled,
85
+ maxFileSizeMB: config.documents.maxFileSizeMB,
86
+ hybridThreshold: config.documents.hybridThreshold,
87
+ acceptedTypes: config.documents.acceptedTypes,
88
+ } : undefined,
87
89
  api: {
88
90
  enabled: config.api?.enabled,
89
91
  },
90
- promptTemplates: {
91
- enabled: config.promptTemplates?.enabled,
92
- allowUserTemplates: config.promptTemplates?.allowUserTemplates,
93
- },
92
+ promptTemplates: config.promptTemplates ? {
93
+ enabled: config.promptTemplates.enabled,
94
+ allowUserTemplates: config.promptTemplates.allowUserTemplates,
95
+ } : undefined,
96
+ scheduledPrompts: config.scheduledPrompts ? {
97
+ enabled: config.scheduledPrompts.enabled,
98
+ featureName: config.scheduledPrompts.featureName,
99
+ allowUserPrompts: config.scheduledPrompts.allowUserPrompts,
100
+ allowTeamPrompts: config.scheduledPrompts.allowTeamPrompts,
101
+ defaultTimezone: config.scheduledPrompts.defaultTimezone,
102
+ defaultMaxUserPrompts: config.scheduledPrompts.defaultMaxUserPrompts,
103
+ defaultMaxTeamPrompts: config.scheduledPrompts.defaultMaxTeamPrompts,
104
+ } : undefined,
105
+ credits: config.credits ? {
106
+ enabled: config.credits.enabled,
107
+ expiryEnabled: config.credits.expiryEnabled,
108
+ promoEnabled: config.credits.promoEnabled,
109
+ } : undefined,
110
+ metering: config.metering ? {
111
+ enabled: config.metering.enabled,
112
+ recordPromptCompletion: config.metering.recordPromptCompletion,
113
+ } : undefined,
94
114
  };
95
115
 
96
116
  return (
@@ -19,6 +19,7 @@ interface CompletedToolCall extends PendingToolCall {
19
19
  result: MCPContent[];
20
20
  isError?: boolean;
21
21
  uiResource?: UIResource;
22
+ structuredContent?: Record<string, unknown>;
22
23
  autoApproveReason?: AutoApproveReason;
23
24
  }
24
25
 
@@ -324,6 +325,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
324
325
  input?: Record<string, unknown>;
325
326
  isError?: boolean;
326
327
  uiResource?: UIResource;
328
+ structuredContent?: Record<string, unknown>;
327
329
  // Tool confirmation fields
328
330
  confirmationId?: string;
329
331
  toolName?: string;
@@ -397,6 +399,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
397
399
  result: (data.content as unknown as MCPContent[]) || [],
398
400
  isError: data.isError,
399
401
  uiResource: data.uiResource,
402
+ structuredContent: (data.structuredContent as Record<string, unknown> | undefined),
400
403
  }
401
404
  : {
402
405
  // Fallback: create from tool_result event data (server now includes name/serverId/input)
@@ -407,6 +410,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
407
410
  result: (data.content as unknown as MCPContent[]) || [],
408
411
  isError: data.isError,
409
412
  uiResource: data.uiResource,
413
+ structuredContent: (data.structuredContent as Record<string, unknown> | undefined),
410
414
  };
411
415
 
412
416
  return {
@@ -492,6 +496,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
492
496
  content: tc.result,
493
497
  isError: tc.isError,
494
498
  uiResource: tc.uiResource,
499
+ structuredContent: tc.structuredContent,
495
500
  }))
496
501
  : undefined,
497
502
  createdAt: new Date(),
package/dist/favicon.svg DELETED
@@ -1,11 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
2
- <defs>
3
- <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
4
- <stop offset="0%" style="stop-color:#6366f1"/>
5
- <stop offset="100%" style="stop-color:#8b5cf6"/>
6
- </linearGradient>
7
- </defs>
8
- <rect width="32" height="32" rx="6" fill="url(#grad)"/>
9
- <path d="M10 12 L16 9 L22 12 L22 18 L16 21 L10 18 Z" fill="none" stroke="white" stroke-width="1.5" stroke-linejoin="round"/>
10
- <circle cx="16" cy="15" r="2.5" fill="white"/>
11
- </svg>
package/dist/index.html DELETED
@@ -1,17 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>AI Chat</title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com" />
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
11
- <script type="module" crossorigin src="/assets/index-BYcLoar2.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-Ob8N4Y68.css">
13
- </head>
14
- <body>
15
- <div id="root"></div>
16
- </body>
17
- </html>
package/dist/logo.svg DELETED
@@ -1,12 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
2
- <defs>
3
- <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
- <stop offset="0%" style="stop-color:#6366f1"/>
5
- <stop offset="100%" style="stop-color:#8b5cf6"/>
6
- </linearGradient>
7
- </defs>
8
- <rect width="100" height="100" rx="20" fill="url(#gradient)"/>
9
- <path d="M30 35 L50 25 L70 35 L70 55 L50 65 L30 55 Z" fill="none" stroke="white" stroke-width="3" stroke-linejoin="round"/>
10
- <circle cx="50" cy="45" r="8" fill="white"/>
11
- <path d="M35 70 Q50 78 65 70" fill="none" stroke="white" stroke-width="3" stroke-linecap="round"/>
12
- </svg>