@mars-stack/cli 3.0.1 → 4.0.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/dist/index.js +350 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template/.cursor/skills/mars-upgrade-scaffold/SKILL.md +70 -0
- package/template/AGENTS.md +5 -1
- package/template/scripts/ensure-db.mjs +15 -9
- package/template/src/app/(auth)/verify/page.tsx +9 -8
- package/template/src/app/(protected)/dashboard/page.tsx +228 -11
- package/template/src/app/(protected)/files/page.tsx +30 -0
- package/template/src/app/(protected)/layout.tsx +14 -1
- package/template/src/app/(protected)/settings/billing/page.tsx +262 -0
- package/template/src/app/api/auth/signup/route.test.ts +118 -0
- package/template/src/app/api/auth/signup/route.ts +29 -5
- package/template/src/app/api/protected/billing/checkout/route.ts +2 -2
- package/template/src/app/api/protected/billing/portal/route.ts +1 -1
- package/template/src/app/api/protected/billing/subscription/route.ts +13 -0
- package/template/src/app/api/protected/files/list/route.ts +22 -0
- package/template/src/app/pricing/page.tsx +276 -0
- package/template/src/config/routes.ts +3 -0
- package/template/src/features/uploads/components/FileList.tsx +202 -0
- package/template/src/features/uploads/components/FileUploader.tsx +225 -0
- package/template/src/features/uploads/components/index.ts +2 -0
- package/template/src/features/uploads/index.ts +2 -0
- package/template/src/proxy.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -2808,6 +2808,8 @@ async function generateOnboarding(projectRoot) {
|
|
|
2808
2808
|
}
|
|
2809
2809
|
await addUserRelation(projectRoot, "onboardingProgress OnboardingProgress?", ctx);
|
|
2810
2810
|
await registerRoute(projectRoot, "onboarding", "/onboarding", ctx);
|
|
2811
|
+
await patchDashboardWithOnboardingRedirect(projectRoot, ctx);
|
|
2812
|
+
await patchProxyWithOnboardingRoute(projectRoot, ctx);
|
|
2811
2813
|
await setConfigFlag6(projectRoot, ctx);
|
|
2812
2814
|
await ctx.commit();
|
|
2813
2815
|
log.success(`Generated onboarding feature with ${count} files`);
|
|
@@ -2816,10 +2818,11 @@ async function generateOnboarding(projectRoot) {
|
|
|
2816
2818
|
log.step("src/app/(protected)/onboarding/ \u2014 onboarding page");
|
|
2817
2819
|
log.step("src/app/api/protected/onboarding/ \u2014 progress and step API routes");
|
|
2818
2820
|
log.step("prisma/schema/onboarding.prisma \u2014 OnboardingProgress model");
|
|
2821
|
+
log.step("src/app/(protected)/dashboard/page.tsx \u2014 onboarding redirect for new users");
|
|
2822
|
+
log.step("src/proxy.ts \u2014 /onboarding added to protected routes");
|
|
2819
2823
|
log.blank();
|
|
2820
2824
|
log.warn("Next steps:");
|
|
2821
2825
|
log.step("Run `yarn db:push` to sync the Prisma schema");
|
|
2822
|
-
log.step("Add onboarding redirect check to your protected layout");
|
|
2823
2826
|
log.step("Customize steps in `src/features/onboarding/config.ts`");
|
|
2824
2827
|
log.blank();
|
|
2825
2828
|
} catch (error) {
|
|
@@ -2827,6 +2830,66 @@ async function generateOnboarding(projectRoot) {
|
|
|
2827
2830
|
throw error;
|
|
2828
2831
|
}
|
|
2829
2832
|
}
|
|
2833
|
+
async function patchDashboardWithOnboardingRedirect(projectRoot, ctx) {
|
|
2834
|
+
const dashboardPath = path13.join(
|
|
2835
|
+
projectRoot,
|
|
2836
|
+
"src",
|
|
2837
|
+
"app",
|
|
2838
|
+
"(protected)",
|
|
2839
|
+
"dashboard",
|
|
2840
|
+
"page.tsx"
|
|
2841
|
+
);
|
|
2842
|
+
if (!await fs13.pathExists(dashboardPath)) return;
|
|
2843
|
+
await ctx.trackModifiedFile(dashboardPath);
|
|
2844
|
+
let content = await fs13.readFile(dashboardPath, "utf-8");
|
|
2845
|
+
if (content.includes("isOnboardingComplete")) return;
|
|
2846
|
+
const redirectImport = `import { redirect } from 'next/navigation';
|
|
2847
|
+
`;
|
|
2848
|
+
const onboardingImport = `import { isOnboardingComplete } from '@/features/onboarding/server';
|
|
2849
|
+
`;
|
|
2850
|
+
if (!content.includes("from 'next/navigation'")) {
|
|
2851
|
+
const firstImport = content.indexOf("import ");
|
|
2852
|
+
if (firstImport >= 0) {
|
|
2853
|
+
content = content.slice(0, firstImport) + redirectImport + content.slice(firstImport);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
const configImportIdx = content.indexOf("from '@/config/app.config'");
|
|
2857
|
+
if (configImportIdx >= 0) {
|
|
2858
|
+
const lineEnd = content.indexOf("\n", configImportIdx);
|
|
2859
|
+
content = content.slice(0, lineEnd + 1) + onboardingImport + content.slice(lineEnd + 1);
|
|
2860
|
+
} else {
|
|
2861
|
+
const firstImport = content.indexOf("import ");
|
|
2862
|
+
if (firstImport >= 0) {
|
|
2863
|
+
content = content.slice(0, firstImport) + onboardingImport + content.slice(firstImport);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
const sessionLine = content.match(/const session = await verifySession\(\);?\n/);
|
|
2867
|
+
if (sessionLine && sessionLine.index !== void 0) {
|
|
2868
|
+
const insertAt = sessionLine.index + sessionLine[0].length;
|
|
2869
|
+
const redirectBlock = `
|
|
2870
|
+
if (appConfig.features.onboarding) {
|
|
2871
|
+
const complete = await isOnboardingComplete(session.userId);
|
|
2872
|
+
if (!complete) {
|
|
2873
|
+
redirect('/onboarding');
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
`;
|
|
2877
|
+
content = content.slice(0, insertAt) + redirectBlock + content.slice(insertAt);
|
|
2878
|
+
}
|
|
2879
|
+
await fs13.writeFile(dashboardPath, content);
|
|
2880
|
+
}
|
|
2881
|
+
async function patchProxyWithOnboardingRoute(projectRoot, ctx) {
|
|
2882
|
+
const proxyPath = path13.join(projectRoot, "src", "proxy.ts");
|
|
2883
|
+
if (!await fs13.pathExists(proxyPath)) return;
|
|
2884
|
+
await ctx.trackModifiedFile(proxyPath);
|
|
2885
|
+
let content = await fs13.readFile(proxyPath, "utf-8");
|
|
2886
|
+
if (content.includes("routes.onboarding")) return;
|
|
2887
|
+
content = content.replace(
|
|
2888
|
+
/const protectedRoutes = \[([^\]]*)\]/,
|
|
2889
|
+
"const protectedRoutes = [$1, routes.onboarding]"
|
|
2890
|
+
);
|
|
2891
|
+
await fs13.writeFile(proxyPath, content);
|
|
2892
|
+
}
|
|
2830
2893
|
async function setConfigFlag6(projectRoot, ctx) {
|
|
2831
2894
|
const configPath = path13.join(projectRoot, "src", "config", "app.config.ts");
|
|
2832
2895
|
if (!await fs13.pathExists(configPath)) return;
|
|
@@ -4171,8 +4234,13 @@ async function generateAI(projectRoot) {
|
|
|
4171
4234
|
"src/features/ai/server/anthropic-provider.ts": anthropicProvider(),
|
|
4172
4235
|
"src/features/ai/validation/schemas.ts": schemas5(),
|
|
4173
4236
|
"src/features/ai/hooks/use-chat.ts": useChatHook(),
|
|
4237
|
+
"src/features/ai/components/ChatMessages.tsx": chatMessagesComponent(),
|
|
4238
|
+
"src/features/ai/components/ChatInput.tsx": chatInputComponent(),
|
|
4239
|
+
"src/features/ai/components/Chat.tsx": chatComponent(),
|
|
4240
|
+
"src/features/ai/components/index.ts": componentBarrel(),
|
|
4174
4241
|
"src/features/ai/index.ts": barrelExports2(),
|
|
4175
|
-
"src/app/api/protected/ai/chat/route.ts": chatRoute()
|
|
4242
|
+
"src/app/api/protected/ai/chat/route.ts": chatRoute(),
|
|
4243
|
+
"src/app/(protected)/ai/page.tsx": aiPage()
|
|
4176
4244
|
};
|
|
4177
4245
|
let count = 0;
|
|
4178
4246
|
for (const [filePath, content] of Object.entries(files)) {
|
|
@@ -4186,18 +4254,20 @@ async function generateAI(projectRoot) {
|
|
|
4186
4254
|
openai: "^4.0.0",
|
|
4187
4255
|
"@anthropic-ai/sdk": "^0.30.0"
|
|
4188
4256
|
});
|
|
4257
|
+
await registerRoute(projectRoot, "ai", "/ai", ctx);
|
|
4258
|
+
await wireProtectedNav4(projectRoot, ctx);
|
|
4189
4259
|
await setConfigFlag9(projectRoot, ctx);
|
|
4190
4260
|
await ctx.commit();
|
|
4191
4261
|
log.success(`Generated AI feature with ${count} files`);
|
|
4192
4262
|
log.blank();
|
|
4193
|
-
log.step("src/features/ai/ \u2014 types, provider factory, hooks, validation");
|
|
4263
|
+
log.step("src/features/ai/ \u2014 types, provider factory, hooks, validation, chat UI");
|
|
4264
|
+
log.step("src/features/ai/components/ \u2014 Chat, ChatMessages, ChatInput");
|
|
4265
|
+
log.step("src/app/(protected)/ai/ \u2014 AI chat page");
|
|
4194
4266
|
log.step("src/app/api/protected/ai/chat/ \u2014 streaming chat endpoint");
|
|
4195
4267
|
log.blank();
|
|
4196
4268
|
log.warn("Next steps:");
|
|
4197
4269
|
log.step('Set the provider in appConfig.services.ai.provider ("openai" or "anthropic")');
|
|
4198
4270
|
log.step("Set OPENAI_API_KEY or ANTHROPIC_API_KEY in .env");
|
|
4199
|
-
log.step("Use getAIProvider() from server code for chat/streaming");
|
|
4200
|
-
log.step("Use useChat() hook for client-side chat interfaces");
|
|
4201
4271
|
log.blank();
|
|
4202
4272
|
} catch (error) {
|
|
4203
4273
|
await ctx.rollback();
|
|
@@ -4588,11 +4658,281 @@ export function useChat(options: UseChatOptions = {}): UseChatReturn {
|
|
|
4588
4658
|
}
|
|
4589
4659
|
`;
|
|
4590
4660
|
}
|
|
4661
|
+
function chatMessagesComponent() {
|
|
4662
|
+
return `${STAMP9}
|
|
4663
|
+
'use client';
|
|
4664
|
+
|
|
4665
|
+
import { useEffect, useRef } from 'react';
|
|
4666
|
+
import type { ChatMessage } from '../types';
|
|
4667
|
+
import { Spinner } from '@mars-stack/ui';
|
|
4668
|
+
|
|
4669
|
+
interface ChatMessagesProps {
|
|
4670
|
+
messages: ChatMessage[];
|
|
4671
|
+
isLoading: boolean;
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
export function ChatMessages({ messages, isLoading }: ChatMessagesProps) {
|
|
4675
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
4676
|
+
|
|
4677
|
+
useEffect(() => {
|
|
4678
|
+
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
4679
|
+
}, [messages]);
|
|
4680
|
+
|
|
4681
|
+
if (messages.length === 0) {
|
|
4682
|
+
return (
|
|
4683
|
+
<div className="flex flex-1 items-center justify-center">
|
|
4684
|
+
<div className="text-center">
|
|
4685
|
+
<svg
|
|
4686
|
+
className="mx-auto h-10 w-10 text-text-muted"
|
|
4687
|
+
fill="none"
|
|
4688
|
+
viewBox="0 0 24 24"
|
|
4689
|
+
strokeWidth={1}
|
|
4690
|
+
stroke="currentColor"
|
|
4691
|
+
>
|
|
4692
|
+
<path
|
|
4693
|
+
strokeLinecap="round"
|
|
4694
|
+
strokeLinejoin="round"
|
|
4695
|
+
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"
|
|
4696
|
+
/>
|
|
4697
|
+
</svg>
|
|
4698
|
+
<p className="mt-3 text-sm text-text-secondary">Start a conversation</p>
|
|
4699
|
+
<p className="mt-1 text-xs text-text-muted">Send a message to begin chatting with the AI assistant.</p>
|
|
4700
|
+
</div>
|
|
4701
|
+
</div>
|
|
4702
|
+
);
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
return (
|
|
4706
|
+
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-4">
|
|
4707
|
+
{messages.map((message, index) => (
|
|
4708
|
+
<div
|
|
4709
|
+
key={index}
|
|
4710
|
+
className={\`flex \${message.role === 'user' ? 'justify-end' : 'justify-start'}\`}
|
|
4711
|
+
>
|
|
4712
|
+
<div
|
|
4713
|
+
className={\`max-w-[80%] rounded-2xl px-4 py-2.5 \${
|
|
4714
|
+
message.role === 'user'
|
|
4715
|
+
? 'bg-brand-primary text-text-on-brand'
|
|
4716
|
+
: 'bg-surface-card border border-border-default text-text-primary'
|
|
4717
|
+
}\`}
|
|
4718
|
+
>
|
|
4719
|
+
{message.content ? (
|
|
4720
|
+
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
|
|
4721
|
+
) : isLoading && index === messages.length - 1 ? (
|
|
4722
|
+
<div className="flex items-center gap-1.5 py-1">
|
|
4723
|
+
<Spinner size="sm" />
|
|
4724
|
+
<span className="text-xs text-text-muted">Thinking...</span>
|
|
4725
|
+
</div>
|
|
4726
|
+
) : null}
|
|
4727
|
+
</div>
|
|
4728
|
+
</div>
|
|
4729
|
+
))}
|
|
4730
|
+
<div ref={bottomRef} />
|
|
4731
|
+
</div>
|
|
4732
|
+
);
|
|
4733
|
+
}
|
|
4734
|
+
`;
|
|
4735
|
+
}
|
|
4736
|
+
function chatInputComponent() {
|
|
4737
|
+
return `${STAMP9}
|
|
4738
|
+
'use client';
|
|
4739
|
+
|
|
4740
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
4741
|
+
import { Button } from '@mars-stack/ui';
|
|
4742
|
+
|
|
4743
|
+
interface ChatInputProps {
|
|
4744
|
+
onSend: (content: string) => void;
|
|
4745
|
+
disabled?: boolean;
|
|
4746
|
+
placeholder?: string;
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
function SendIcon() {
|
|
4750
|
+
return (
|
|
4751
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
|
4752
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
|
|
4753
|
+
</svg>
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
export function ChatInput({ onSend, disabled = false, placeholder = 'Type a message...' }: ChatInputProps) {
|
|
4758
|
+
const [input, setInput] = useState('');
|
|
4759
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
4760
|
+
|
|
4761
|
+
const handleSubmit = useCallback(() => {
|
|
4762
|
+
const trimmed = input.trim();
|
|
4763
|
+
if (!trimmed || disabled) return;
|
|
4764
|
+
|
|
4765
|
+
onSend(trimmed);
|
|
4766
|
+
setInput('');
|
|
4767
|
+
|
|
4768
|
+
requestAnimationFrame(() => {
|
|
4769
|
+
if (textareaRef.current) {
|
|
4770
|
+
textareaRef.current.style.height = 'auto';
|
|
4771
|
+
}
|
|
4772
|
+
});
|
|
4773
|
+
}, [input, disabled, onSend]);
|
|
4774
|
+
|
|
4775
|
+
const handleKeyDown = useCallback(
|
|
4776
|
+
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
4777
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
4778
|
+
event.preventDefault();
|
|
4779
|
+
handleSubmit();
|
|
4780
|
+
}
|
|
4781
|
+
},
|
|
4782
|
+
[handleSubmit],
|
|
4783
|
+
);
|
|
4784
|
+
|
|
4785
|
+
useEffect(() => {
|
|
4786
|
+
const textarea = textareaRef.current;
|
|
4787
|
+
if (!textarea) return;
|
|
4788
|
+
|
|
4789
|
+
textarea.style.height = 'auto';
|
|
4790
|
+
textarea.style.height = \`\${Math.min(textarea.scrollHeight, 160)}px\`;
|
|
4791
|
+
}, [input]);
|
|
4792
|
+
|
|
4793
|
+
return (
|
|
4794
|
+
<div className="border-t border-border-default bg-surface-card p-4">
|
|
4795
|
+
<div className="flex items-end gap-2">
|
|
4796
|
+
<textarea
|
|
4797
|
+
ref={textareaRef}
|
|
4798
|
+
value={input}
|
|
4799
|
+
onChange={(e) => setInput(e.target.value)}
|
|
4800
|
+
onKeyDown={handleKeyDown}
|
|
4801
|
+
placeholder={placeholder}
|
|
4802
|
+
disabled={disabled}
|
|
4803
|
+
rows={1}
|
|
4804
|
+
className="flex-1 resize-none rounded-xl border border-border-input bg-surface-input px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted focus:border-border-focus focus:outline-none focus:ring-2 focus:ring-ring-focus disabled:opacity-50"
|
|
4805
|
+
/>
|
|
4806
|
+
<Button
|
|
4807
|
+
variant="primary"
|
|
4808
|
+
size="md"
|
|
4809
|
+
onClick={handleSubmit}
|
|
4810
|
+
disabled={disabled || !input.trim()}
|
|
4811
|
+
aria-label="Send message"
|
|
4812
|
+
>
|
|
4813
|
+
<SendIcon />
|
|
4814
|
+
</Button>
|
|
4815
|
+
</div>
|
|
4816
|
+
<p className="mt-1.5 text-xs text-text-muted">
|
|
4817
|
+
Press Enter to send, Shift+Enter for a new line.
|
|
4818
|
+
</p>
|
|
4819
|
+
</div>
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
`;
|
|
4823
|
+
}
|
|
4824
|
+
function chatComponent() {
|
|
4825
|
+
return `${STAMP9}
|
|
4826
|
+
'use client';
|
|
4827
|
+
|
|
4828
|
+
import { useChat } from '../hooks/use-chat';
|
|
4829
|
+
import { ChatMessages } from './ChatMessages';
|
|
4830
|
+
import { ChatInput } from './ChatInput';
|
|
4831
|
+
|
|
4832
|
+
interface ChatProps {
|
|
4833
|
+
systemPrompt?: string;
|
|
4834
|
+
placeholder?: string;
|
|
4835
|
+
className?: string;
|
|
4836
|
+
}
|
|
4837
|
+
|
|
4838
|
+
export function Chat({ placeholder, className }: ChatProps) {
|
|
4839
|
+
const { messages, isLoading, error, send, reset } = useChat();
|
|
4840
|
+
|
|
4841
|
+
return (
|
|
4842
|
+
<div className={\`flex flex-col h-full \${className ?? ''}\`}>
|
|
4843
|
+
{error && (
|
|
4844
|
+
<div className="mx-4 mt-4 rounded-lg border border-border-default bg-error-muted p-3">
|
|
4845
|
+
<div className="flex items-center justify-between gap-2">
|
|
4846
|
+
<p className="text-sm text-text-error">{error}</p>
|
|
4847
|
+
<button
|
|
4848
|
+
type="button"
|
|
4849
|
+
onClick={reset}
|
|
4850
|
+
className="text-xs font-medium text-text-link hover:text-text-link-hover"
|
|
4851
|
+
>
|
|
4852
|
+
Reset
|
|
4853
|
+
</button>
|
|
4854
|
+
</div>
|
|
4855
|
+
</div>
|
|
4856
|
+
)}
|
|
4857
|
+
|
|
4858
|
+
<ChatMessages messages={messages} isLoading={isLoading} />
|
|
4859
|
+
|
|
4860
|
+
<ChatInput
|
|
4861
|
+
onSend={send}
|
|
4862
|
+
disabled={isLoading}
|
|
4863
|
+
placeholder={placeholder}
|
|
4864
|
+
/>
|
|
4865
|
+
</div>
|
|
4866
|
+
);
|
|
4867
|
+
}
|
|
4868
|
+
`;
|
|
4869
|
+
}
|
|
4870
|
+
function componentBarrel() {
|
|
4871
|
+
return `${STAMP9}
|
|
4872
|
+
export { Chat } from './Chat';
|
|
4873
|
+
export { ChatMessages } from './ChatMessages';
|
|
4874
|
+
export { ChatInput } from './ChatInput';
|
|
4875
|
+
`;
|
|
4876
|
+
}
|
|
4877
|
+
async function wireProtectedNav4(projectRoot, ctx) {
|
|
4878
|
+
const layoutPath = path16.join(
|
|
4879
|
+
projectRoot,
|
|
4880
|
+
"src",
|
|
4881
|
+
"app",
|
|
4882
|
+
"(protected)",
|
|
4883
|
+
"layout.tsx"
|
|
4884
|
+
);
|
|
4885
|
+
if (!await fs16.pathExists(layoutPath)) return;
|
|
4886
|
+
let content = await fs16.readFile(layoutPath, "utf-8");
|
|
4887
|
+
if (content.includes("routes.ai") || content.includes("label: 'AI'")) return;
|
|
4888
|
+
await ctx.trackModifiedFile(layoutPath);
|
|
4889
|
+
const insertion = ` if (appConfig.features.ai) {
|
|
4890
|
+
items.push({ label: 'AI', href: routes.ai });
|
|
4891
|
+
}
|
|
4892
|
+
`;
|
|
4893
|
+
if (content.includes("function buildNavItems()") && content.includes("return items;")) {
|
|
4894
|
+
const returnMarker = " return items;";
|
|
4895
|
+
const insertPos = content.lastIndexOf(returnMarker);
|
|
4896
|
+
if (insertPos !== -1) {
|
|
4897
|
+
content = content.slice(0, insertPos) + insertion + content.slice(insertPos);
|
|
4898
|
+
}
|
|
4899
|
+
} else if (content.includes("NAV_ITEMS")) {
|
|
4900
|
+
content = insertImportAfterDirectives(
|
|
4901
|
+
content,
|
|
4902
|
+
"// AI nav link added by mars generate ai\n"
|
|
4903
|
+
);
|
|
4904
|
+
}
|
|
4905
|
+
await fs16.writeFile(layoutPath, content);
|
|
4906
|
+
}
|
|
4907
|
+
function aiPage() {
|
|
4908
|
+
return `${STAMP9}
|
|
4909
|
+
'use client';
|
|
4910
|
+
|
|
4911
|
+
import { Chat } from '@/features/ai/components';
|
|
4912
|
+
|
|
4913
|
+
export default function AIPage() {
|
|
4914
|
+
return (
|
|
4915
|
+
<div className="flex h-[calc(100vh-8rem)] flex-col">
|
|
4916
|
+
<div className="mb-4">
|
|
4917
|
+
<h1 className="text-2xl font-bold text-text-primary">AI Assistant</h1>
|
|
4918
|
+
<p className="mt-1 text-text-secondary">Chat with the AI assistant.</p>
|
|
4919
|
+
</div>
|
|
4920
|
+
|
|
4921
|
+
<div className="flex-1 overflow-hidden rounded-xl border border-border-default bg-surface-card">
|
|
4922
|
+
<Chat placeholder="Ask me anything..." />
|
|
4923
|
+
</div>
|
|
4924
|
+
</div>
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
`;
|
|
4928
|
+
}
|
|
4591
4929
|
function barrelExports2() {
|
|
4592
4930
|
return `${STAMP9}
|
|
4593
4931
|
export type { ChatMessage, ChatParams, ChatResponse, AIProvider } from './types';
|
|
4594
|
-
export { chatSchema
|
|
4932
|
+
export { chatSchema } from './validation/schemas';
|
|
4933
|
+
export type { ChatInput as ChatFormInput } from './validation/schemas';
|
|
4595
4934
|
export { useChat } from './hooks/use-chat';
|
|
4935
|
+
export { Chat, ChatMessages, ChatInput } from './components';
|
|
4596
4936
|
`;
|
|
4597
4937
|
}
|
|
4598
4938
|
function chatRoute() {
|
|
@@ -4636,9 +4976,11 @@ var GENERATOR_VERSION9, STAMP9;
|
|
|
4636
4976
|
var init_ai = __esm({
|
|
4637
4977
|
"src/generators/features/ai.ts"() {
|
|
4638
4978
|
"use strict";
|
|
4979
|
+
init_client_file_patch();
|
|
4639
4980
|
init_logger();
|
|
4640
4981
|
init_rollback();
|
|
4641
4982
|
init_dependencies();
|
|
4983
|
+
init_routes();
|
|
4642
4984
|
GENERATOR_VERSION9 = "0.1.0";
|
|
4643
4985
|
STAMP9 = `// @mars-generated ai@${GENERATOR_VERSION9}`;
|
|
4644
4986
|
}
|
|
@@ -6609,10 +6951,12 @@ var FEATURE_DIRECTORY_MAP = {
|
|
|
6609
6951
|
"src/features/billing",
|
|
6610
6952
|
"src/app/api/protected/billing",
|
|
6611
6953
|
"src/app/api/webhooks/stripe",
|
|
6954
|
+
"src/app/(protected)/settings/billing",
|
|
6612
6955
|
"prisma/schema/subscription.prisma"
|
|
6613
6956
|
],
|
|
6614
6957
|
fileUpload: [
|
|
6615
6958
|
"src/features/uploads",
|
|
6959
|
+
"src/app/(protected)/files",
|
|
6616
6960
|
"src/app/api/protected/files",
|
|
6617
6961
|
"prisma/schema/file.prisma"
|
|
6618
6962
|
]
|