Add files via upload
This commit is contained in:
parent
fbdfe9f11c
commit
69a7f180e4
83
app/api/chat/route.ts
Normal file
83
app/api/chat/route.ts
Normal file
@ -0,0 +1,83 @@
|
||||
|
||||
import { streamText, UIMessage, convertToModelMessages, stepCountIs, createUIMessageStream, createUIMessageStreamResponse } from 'ai';
|
||||
import { MODEL } from '@/config';
|
||||
import { SYSTEM_PROMPT } from '@/prompts';
|
||||
import { isContentFlagged } from '@/lib/moderation';
|
||||
import { webSearch } from './tools/web-search';
|
||||
import { vectorDatabaseSearch } from './tools/search-vector-database';
|
||||
|
||||
export const maxDuration = 30;
|
||||
export async function POST(req: Request) {
|
||||
const { messages }: { messages: UIMessage[] } = await req.json();
|
||||
|
||||
const latestUserMessage = messages
|
||||
.filter(msg => msg.role === 'user')
|
||||
.pop();
|
||||
|
||||
if (latestUserMessage) {
|
||||
const textParts = latestUserMessage.parts
|
||||
.filter(part => part.type === 'text')
|
||||
.map(part => 'text' in part ? part.text : '')
|
||||
.join('');
|
||||
|
||||
if (textParts) {
|
||||
const moderationResult = await isContentFlagged(textParts);
|
||||
|
||||
if (moderationResult.flagged) {
|
||||
const stream = createUIMessageStream({
|
||||
execute({ writer }) {
|
||||
const textId = 'moderation-denial-text';
|
||||
|
||||
writer.write({
|
||||
type: 'start',
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'text-start',
|
||||
id: textId,
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'text-delta',
|
||||
id: textId,
|
||||
delta: moderationResult.denialMessage || "Your message violates our guidelines. I can't answer that.",
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'text-end',
|
||||
id: textId,
|
||||
});
|
||||
|
||||
writer.write({
|
||||
type: 'finish',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return createUIMessageStreamResponse({ stream });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = streamText({
|
||||
model: MODEL,
|
||||
system: SYSTEM_PROMPT,
|
||||
messages: convertToModelMessages(messages),
|
||||
tools: {
|
||||
webSearch,
|
||||
vectorDatabaseSearch,
|
||||
},
|
||||
stopWhen: stepCountIs(10),
|
||||
providerOptions: {
|
||||
openai: {
|
||||
reasoningSummary: 'auto',
|
||||
reasoningEffort: 'low',
|
||||
parallelToolCalls: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
sendReasoning: true,
|
||||
});
|
||||
}
|
||||
14
app/api/chat/tools/search-vector-database.ts
Normal file
14
app/api/chat/tools/search-vector-database.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
import { searchPinecone } from "@/lib/pinecone";
|
||||
|
||||
export const vectorDatabaseSearch = tool({
|
||||
description: 'Search the vector database for information',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('The query to search the vector database for. Optimally is a hypothetical answer for similarity search.'),
|
||||
}),
|
||||
execute: async ({ query }) => {
|
||||
return await searchPinecone(query);
|
||||
},
|
||||
});
|
||||
|
||||
32
app/api/chat/tools/web-search.ts
Normal file
32
app/api/chat/tools/web-search.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import Exa from 'exa-js';
|
||||
|
||||
const exa = new Exa(process.env.EXA_API_KEY);
|
||||
|
||||
export const webSearch = tool({
|
||||
description: 'Search the web for up-to-date information',
|
||||
inputSchema: z.object({
|
||||
query: z.string().min(1).describe('The search query'),
|
||||
}),
|
||||
execute: async ({ query }) => {
|
||||
try {
|
||||
const { results } = await exa.search(query, {
|
||||
contents: {
|
||||
text: true,
|
||||
},
|
||||
numResults: 3,
|
||||
});
|
||||
|
||||
return results.map(result => ({
|
||||
title: result.title,
|
||||
url: result.url,
|
||||
content: result.text?.slice(0, 1000) || '',
|
||||
publishedDate: result.publishedDate,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error searching the web:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
126
app/globals.css
Normal file
126
app/globals.css
Normal file
@ -0,0 +1,126 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-inter);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(0.995 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.1 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
.single-char-link {
|
||||
@apply bg-card text-card-foreground px-2 py-1 rounded-full hover:underline hover:scale-105 transition-all duration-100 !text-foreground !no-underline border-input border;
|
||||
}
|
||||
}
|
||||
34
app/layout.tsx
Normal file
34
app/layout.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "MyAI3",
|
||||
description: "MyAI3",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${inter.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
255
app/page.tsx
Normal file
255
app/page.tsx
Normal file
@ -0,0 +1,255 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import * as z from "zod";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Field,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
} from "@/components/ui/field";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { ArrowUp, Eraser, Loader2, Plus, PlusIcon, Square } from "lucide-react";
|
||||
import { MessageWall } from "@/components/messages/message-wall";
|
||||
import { ChatHeader } from "@/app/parts/chat-header";
|
||||
import { ChatHeaderBlock } from "@/app/parts/chat-header";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { UIMessage } from "ai";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { AI_NAME, CLEAR_CHAT_TEXT, OWNER_NAME, WELCOME_MESSAGE } from "@/config";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
const formSchema = z.object({
|
||||
message: z
|
||||
.string()
|
||||
.min(1, "Message cannot be empty.")
|
||||
.max(2000, "Message must be at most 2000 characters."),
|
||||
});
|
||||
|
||||
const STORAGE_KEY = 'chat-messages';
|
||||
|
||||
type StorageData = {
|
||||
messages: UIMessage[];
|
||||
durations: Record<string, number>;
|
||||
};
|
||||
|
||||
const loadMessagesFromStorage = (): { messages: UIMessage[]; durations: Record<string, number> } => {
|
||||
if (typeof window === 'undefined') return { messages: [], durations: {} };
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (!stored) return { messages: [], durations: {} };
|
||||
|
||||
const parsed = JSON.parse(stored);
|
||||
return {
|
||||
messages: parsed.messages || [],
|
||||
durations: parsed.durations || {},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to load messages from localStorage:', error);
|
||||
return { messages: [], durations: {} };
|
||||
}
|
||||
};
|
||||
|
||||
const saveMessagesToStorage = (messages: UIMessage[], durations: Record<string, number>) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
const data: StorageData = { messages, durations };
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.error('Failed to save messages to localStorage:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export default function Chat() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [durations, setDurations] = useState<Record<string, number>>({});
|
||||
const welcomeMessageShownRef = useRef<boolean>(false);
|
||||
|
||||
const stored = typeof window !== 'undefined' ? loadMessagesFromStorage() : { messages: [], durations: {} };
|
||||
const [initialMessages] = useState<UIMessage[]>(stored.messages);
|
||||
|
||||
const { messages, sendMessage, status, stop, setMessages } = useChat({
|
||||
messages: initialMessages,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
setDurations(stored.durations);
|
||||
setMessages(stored.messages);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isClient) {
|
||||
saveMessagesToStorage(messages, durations);
|
||||
}
|
||||
}, [durations, messages, isClient]);
|
||||
|
||||
const handleDurationChange = (key: string, duration: number) => {
|
||||
setDurations((prevDurations) => {
|
||||
const newDurations = { ...prevDurations };
|
||||
newDurations[key] = duration;
|
||||
return newDurations;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isClient && initialMessages.length === 0 && !welcomeMessageShownRef.current) {
|
||||
const welcomeMessage: UIMessage = {
|
||||
id: `welcome-${Date.now()}`,
|
||||
role: "assistant",
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
text: WELCOME_MESSAGE,
|
||||
},
|
||||
],
|
||||
};
|
||||
setMessages([welcomeMessage]);
|
||||
saveMessagesToStorage([welcomeMessage], {});
|
||||
welcomeMessageShownRef.current = true;
|
||||
}
|
||||
}, [isClient, initialMessages.length, setMessages]);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
message: "",
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof formSchema>) {
|
||||
sendMessage({ text: data.message });
|
||||
form.reset();
|
||||
}
|
||||
|
||||
function clearChat() {
|
||||
const newMessages: UIMessage[] = [];
|
||||
const newDurations = {};
|
||||
setMessages(newMessages);
|
||||
setDurations(newDurations);
|
||||
saveMessagesToStorage(newMessages, newDurations);
|
||||
toast.success("Chat cleared");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center font-sans dark:bg-black">
|
||||
<main className="w-full dark:bg-black h-screen relative">
|
||||
<div className="fixed top-0 left-0 right-0 z-50 bg-linear-to-b from-background via-background/50 to-transparent dark:bg-black overflow-visible pb-16">
|
||||
<div className="relative overflow-visible">
|
||||
<ChatHeader>
|
||||
<ChatHeaderBlock />
|
||||
<ChatHeaderBlock className="justify-center items-center">
|
||||
<Avatar
|
||||
className="size-8 ring-1 ring-primary"
|
||||
>
|
||||
<AvatarImage src="/logo.png" />
|
||||
<AvatarFallback>
|
||||
<Image src="/logo.png" alt="Logo" width={36} height={36} />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<p className="tracking-tight">Chat with {AI_NAME}</p>
|
||||
</ChatHeaderBlock>
|
||||
<ChatHeaderBlock className="justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="cursor-pointer"
|
||||
onClick={clearChat}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
{CLEAR_CHAT_TEXT}
|
||||
</Button>
|
||||
</ChatHeaderBlock>
|
||||
</ChatHeader>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-screen overflow-y-auto px-5 py-4 w-full pt-[88px] pb-[150px]">
|
||||
<div className="flex flex-col items-center justify-end min-h-full">
|
||||
{isClient ? (
|
||||
<>
|
||||
<MessageWall messages={messages} status={status} durations={durations} onDurationChange={handleDurationChange} />
|
||||
{status === "submitted" && (
|
||||
<div className="flex justify-start max-w-3xl w-full">
|
||||
<Loader2 className="size-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex justify-center max-w-2xl w-full">
|
||||
<Loader2 className="size-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-linear-to-t from-background via-background/50 to-transparent dark:bg-black overflow-visible pt-13">
|
||||
<div className="w-full px-5 pt-5 pb-1 items-center flex justify-center relative overflow-visible">
|
||||
<div className="message-fade-overlay" />
|
||||
<div className="max-w-3xl w-full">
|
||||
<form id="chat-form" onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<FieldGroup>
|
||||
<Controller
|
||||
name="message"
|
||||
control={form.control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<FieldLabel htmlFor="chat-form-message" className="sr-only">
|
||||
Message
|
||||
</FieldLabel>
|
||||
<div className="relative h-13">
|
||||
<Input
|
||||
{...field}
|
||||
id="chat-form-message"
|
||||
className="h-15 pr-15 pl-5 bg-card rounded-[20px]"
|
||||
placeholder="Type your message here..."
|
||||
disabled={status === "streaming"}
|
||||
aria-invalid={fieldState.invalid}
|
||||
autoComplete="off"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
form.handleSubmit(onSubmit)();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{(status == "ready" || status == "error") && (
|
||||
<Button
|
||||
className="absolute right-3 top-3 rounded-full"
|
||||
type="submit"
|
||||
disabled={!field.value.trim()}
|
||||
size="icon"
|
||||
>
|
||||
<ArrowUp className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
{(status == "streaming" || status == "submitted") && (
|
||||
<Button
|
||||
className="absolute right-2 top-2 rounded-full"
|
||||
size="icon"
|
||||
onClick={() => {
|
||||
stop();
|
||||
}}
|
||||
>
|
||||
<Square className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full px-5 py-3 items-center flex justify-center text-xs text-muted-foreground">
|
||||
© {new Date().getFullYear()} {OWNER_NAME} <Link href="/terms" className="underline">Terms of Use</Link> Powered by <Link href="https://ringel.ai/" className="underline">Ringel.AI</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
17
app/parts/chat-header.tsx
Normal file
17
app/parts/chat-header.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function ChatHeaderBlock({ children, className }: { children?: React.ReactNode, className?: string }) {
|
||||
return (
|
||||
<div className={cn("gap-2 flex flex-1", className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ChatHeader({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="w-full flex py-5 px-5 bg-linear-to-b from-background to-transparent">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
207
app/terms/page.tsx
Normal file
207
app/terms/page.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { OWNER_NAME } from "@/config";
|
||||
|
||||
export default function Terms() {
|
||||
return (
|
||||
<div className="w-full flex justify-center p-10">
|
||||
<div className="w-full max-w-screen-md space-y-6">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 text-gray-500 hover:text-gray-700 underline"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4" />
|
||||
Back to Chatbot
|
||||
</Link>
|
||||
<h1 className="text-3xl font-bold">MyAI3</h1>
|
||||
<h2 className="text-2xl font-semibold">Terms of Use / Disclaimer</h2>
|
||||
|
||||
<p className="text-gray-700">
|
||||
The following terms of use govern access to and use of the MyAI3
|
||||
Assistant ("AI Chatbot"), an artificial intelligence tool provided by
|
||||
{OWNER_NAME} ("I", "me", or "myself"). By engaging with the AI
|
||||
Chatbot, you agree to these terms. If you do not agree, you may not
|
||||
use the AI Chatbot.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">General Information</h3>
|
||||
<ol className="list-decimal list-inside space-y-3">
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Provider and Purpose:</span> The
|
||||
AI Chatbot is a tool developed and maintained by {OWNER_NAME}. It
|
||||
is intended solely to assist users with questions and coursework
|
||||
related to courses taught by {OWNER_NAME}. The AI Chatbot is not
|
||||
affiliated with, endorsed by, or operated by the course provider.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Third-Party Involvement:</span>{" "}
|
||||
The AI Chatbot utilizes multiple third-party platforms and
|
||||
vendors, some of which operate outside the United States. Your
|
||||
inputs may be transmitted, processed, and stored by these
|
||||
third-party systems. As such, confidentiality, security, and privacy
|
||||
cannot be guaranteed, and data transmission may be inherently
|
||||
insecure and subject to interception.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">No Guarantee of Accuracy:</span>{" "}
|
||||
The AI Chatbot is designed to provide helpful and relevant
|
||||
responses but may deliver inaccurate, incomplete, or outdated
|
||||
information. Users are strongly encouraged to independently verify
|
||||
any information before relying on it for decisions or actions.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">Liability</h3>
|
||||
<ol className="list-decimal list-inside space-y-3">
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Use at Your Own Risk:</span> The
|
||||
AI Chatbot is provided on an "as-is" and "as-available" basis. To
|
||||
the fullest extent permitted by law:
|
||||
<ul className="list-disc list-inside ml-6 mt-2 space-y-2">
|
||||
<li>
|
||||
{OWNER_NAME} disclaims all warranties, express or implied,
|
||||
including but not limited to warranties of merchantability,
|
||||
fitness for a particular purpose, and non-infringement.
|
||||
</li>
|
||||
<li>
|
||||
{OWNER_NAME} is not liable for any errors, inaccuracies, or
|
||||
omissions in the information provided by the AI Chatbot.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">
|
||||
No Responsibility for Damages:
|
||||
</span>{" "}
|
||||
Under no circumstances shall {OWNER_NAME}, his collaborators,
|
||||
partners, affiliated entities, or representatives be liable for
|
||||
any direct, indirect, incidental, consequential, special, or
|
||||
punitive damages arising out of or in connection with the use of
|
||||
the AI Chatbot.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">
|
||||
Modification or Discontinuation:
|
||||
</span>{" "}
|
||||
I reserve the right to modify, suspend, or discontinue the AI
|
||||
Chatbot's functionalities at any time without notice.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Future Fees:</span> While the AI
|
||||
Chatbot is currently provided free of charge, I reserve the right
|
||||
to implement a fee for its use at any time.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">User Responsibilities</h3>
|
||||
<ol className="list-decimal list-inside space-y-3">
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Eligibility:</span> Use of the AI
|
||||
Chatbot is restricted to individuals aged 18 or older.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Prohibited Conduct:</span> By
|
||||
using the AI Chatbot, you agree not to:
|
||||
<ul className="list-disc list-inside ml-6 mt-2 space-y-2">
|
||||
<li>Post or transmit content that is defamatory, offensive, intimidating, illegal, racist, discriminatory, obscene, or otherwise inappropriate.</li>
|
||||
<li>Use the AI Chatbot to engage in unlawful or unethical activities.</li>
|
||||
<li>Attempt to compromise the security or functionality of the AI Chatbot</li>
|
||||
<li>Copy, distribute, modify, reverse engineer, decompile, or extract the source code of the AI Chatbot without explicit written consent.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">Data Privacy and Security</h3>
|
||||
<ol className="list-decimal list-inside space-y-3">
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">No Privacy Guarantee:</span> The
|
||||
AI Chatbot does not guarantee privacy, confidentiality, or
|
||||
security of the information you provide. Conversations may be
|
||||
reviewed by {OWNER_NAME}, collaborators, partners, or affiliated
|
||||
entities for purposes such as improving the AI Chatbot, developing
|
||||
course materials, and conducting research.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Public Information:</span> Any
|
||||
information you provide through the AI Chatbot is treated as
|
||||
public.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Data Transmission:</span> Inputs
|
||||
may be transmitted to and processed by third-party services.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">Ownership of Content and Commercial Use</h3>
|
||||
<ol className="list-decimal list-inside space-y-3">
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Surrender of Rights:</span> By
|
||||
using the AI Chatbot, you irrevocably assign and surrender all rights,
|
||||
title, interest, and intellectual property rights in any content, inputs
|
||||
you provide, and outputs generated by the AI Chatbot to {OWNER_NAME}.
|
||||
This includes, but is not limited to, text, questions, and conversations.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">Commercial and Research Use:</span>{" "}
|
||||
{OWNER_NAME} reserves the right to use any input provided by users and
|
||||
any output generated by the AI Chatbot for commercial purposes, research,
|
||||
or other activities without compensation or notification to users.
|
||||
</li>
|
||||
<li className="text-gray-700">
|
||||
<span className="font-semibold">No Claim to Gains or Profits:</span>{" "}
|
||||
Users agree that they have no rights, claims, or entitlement to
|
||||
any gains, profits, or benefits derived from the use or
|
||||
exploitation of the content provided to the AI Chatbot.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">Indemnification</h3>
|
||||
<p className="text-gray-700">
|
||||
By using the AI Chatbot, you agree to indemnify and hold harmless
|
||||
{OWNER_NAME}, his collaborators, partners, affiliated entities, and
|
||||
representatives from any claims, damages, losses, or liabilities
|
||||
arising out of your use of the AI Chatbot or violation of these
|
||||
terms.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">Governing Law and Jurisdiction</h3>
|
||||
<p className="text-gray-700">
|
||||
These terms are governed by the laws of the State of North Carolina,
|
||||
United States. Additional jurisdictions may apply for users outside
|
||||
the United States, subject to applicable local laws. In case of
|
||||
conflicts, the laws of North Carolina shall prevail to the extent
|
||||
permissible. Any disputes arising under or in connection with these
|
||||
terms shall be subject to the exclusive jurisdiction of the courts
|
||||
located in North Carolina.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold">Acceptance of Terms</h3>
|
||||
<p className="text-gray-700">
|
||||
By using the AI Chatbot, you confirm that you have read, understood,
|
||||
and agreed to these Terms of Use and Disclaimer. If you do not
|
||||
agree with any part of these terms, you may not use the AI Chatbot.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-sm text-gray-600">
|
||||
<p>Last Updated: November 17, 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user