├── .env.local.example ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── app ├── auth │ ├── callback │ │ └── route.ts │ ├── sign-in │ │ └── route.ts │ ├── sign-out │ │ └── route.ts │ └── sign-up │ │ └── route.ts ├── chat │ ├── layout.tsx │ └── page.tsx ├── favicon.ico ├── files │ ├── layout.tsx │ └── page.tsx ├── globals.css ├── layout.tsx ├── login │ ├── layout.tsx │ ├── messages.tsx │ └── page.tsx ├── opengraph-image.png └── page.tsx ├── assets ├── hero.png ├── instructions.png └── step-2-er-diagram.png ├── components.json ├── components ├── LogoutButton.tsx ├── NextJsLogo.tsx ├── SupabaseLogo.tsx └── ui │ ├── button.tsx │ ├── input.tsx │ ├── toast.tsx │ ├── toaster.tsx │ └── use-toast.ts ├── lib ├── hooks │ └── use-pipeline.ts ├── providers.tsx ├── utils.ts └── workers │ └── pipeline.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── sample-files ├── roman-empire-1.md ├── roman-empire-2.md └── roman-empire-3.md ├── supabase ├── .gitignore ├── config.toml ├── functions │ ├── _lib │ │ ├── database.ts │ │ └── markdown-parser.ts │ ├── chat │ │ └── index.ts │ ├── embed │ │ └── index.ts │ ├── import_map.json │ └── process │ │ └── index.ts ├── migrations │ ├── 20231006192603_files.sql │ ├── 20231006212813_documents.sql │ ├── 20231007002735_embed.sql │ ├── 20231007040908_match.sql │ └── 20240223144537_cascade.sql └── seed.sql ├── tailwind.config.js └── tsconfig.json /.env.local.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | # https://app.supabase.com/project/_/settings/api 3 | NEXT_PUBLIC_SUPABASE_URL=your-project-url 4 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # env files 28 | .env* 29 | !.env*.example 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "denoland.vscode-deno", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "bradlc.vscode-tailwindcss", 7 | ], 8 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": false, 5 | "deno.enablePaths": [ 6 | "supabase" 7 | ], 8 | "deno.importMap": "./supabase/functions/import_map.json" 9 | } -------------------------------------------------------------------------------- /app/auth/callback/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { NextResponse } from 'next/server'; 4 | 5 | export const dynamic = 'force-dynamic'; 6 | 7 | export async function GET(request: Request) { 8 | // The `/auth/callback` route is required for the server-side auth flow implemented 9 | // by the Auth Helpers package. It exchanges an auth code for the user's session. 10 | // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange 11 | const requestUrl = new URL(request.url); 12 | const code = requestUrl.searchParams.get('code'); 13 | 14 | if (code) { 15 | const supabase = createRouteHandlerClient({ cookies }); 16 | await supabase.auth.exchangeCodeForSession(code); 17 | } 18 | 19 | // URL to redirect to after sign in process completes 20 | return NextResponse.redirect(requestUrl.origin); 21 | } 22 | -------------------------------------------------------------------------------- /app/auth/sign-in/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { NextResponse } from 'next/server'; 4 | 5 | export const dynamic = 'force-dynamic'; 6 | 7 | export async function POST(request: Request) { 8 | const requestUrl = new URL(request.url); 9 | const formData = await request.formData(); 10 | const email = String(formData.get('email')); 11 | const password = String(formData.get('password')); 12 | const supabase = createRouteHandlerClient({ cookies }); 13 | 14 | const { error } = await supabase.auth.signInWithPassword({ 15 | email, 16 | password, 17 | }); 18 | 19 | if (error) { 20 | return NextResponse.redirect( 21 | `${requestUrl.origin}/login?error=Could not authenticate user`, 22 | { 23 | // a 301 status is required to redirect from a POST to a GET route 24 | status: 301, 25 | } 26 | ); 27 | } 28 | 29 | return NextResponse.redirect(requestUrl.origin, { 30 | // a 301 status is required to redirect from a POST to a GET route 31 | status: 301, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /app/auth/sign-out/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { NextResponse } from 'next/server'; 4 | 5 | export const dynamic = 'force-dynamic'; 6 | 7 | export async function POST(request: Request) { 8 | const requestUrl = new URL(request.url); 9 | const supabase = createRouteHandlerClient({ cookies }); 10 | 11 | await supabase.auth.signOut(); 12 | 13 | return NextResponse.redirect(`${requestUrl.origin}/login`, { 14 | // a 301 status is required to redirect from a POST to a GET route 15 | status: 301, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /app/auth/sign-up/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { NextResponse } from 'next/server'; 4 | 5 | export const dynamic = 'force-dynamic'; 6 | 7 | export async function POST(request: Request) { 8 | const requestUrl = new URL(request.url); 9 | const formData = await request.formData(); 10 | const email = String(formData.get('email')); 11 | const password = String(formData.get('password')); 12 | const supabase = createRouteHandlerClient({ cookies }); 13 | 14 | const { error } = await supabase.auth.signUp({ 15 | email, 16 | password, 17 | options: { 18 | emailRedirectTo: `${requestUrl.origin}/auth/callback`, 19 | }, 20 | }); 21 | 22 | if (error) { 23 | return NextResponse.redirect( 24 | `${requestUrl.origin}/login?error=Could not authenticate user`, 25 | { 26 | // a 301 status is required to redirect from a POST to a GET route 27 | status: 301, 28 | } 29 | ); 30 | } 31 | 32 | return NextResponse.redirect( 33 | `${requestUrl.origin}/login?message=Check email to continue sign in process`, 34 | { 35 | // a 301 status is required to redirect from a POST to a GET route 36 | status: 301, 37 | } 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /app/chat/layout.tsx: -------------------------------------------------------------------------------- 1 | import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { redirect } from 'next/navigation'; 4 | import { PropsWithChildren } from 'react'; 5 | 6 | export default async function ChatLayout({ children }: PropsWithChildren) { 7 | // Keep cookies in the JS execution context for Next.js build 8 | const cookieStore = cookies(); 9 | 10 | const supabase = createServerComponentClient({ cookies: () => cookieStore }); 11 | 12 | const { 13 | data: { user }, 14 | } = await supabase.auth.getUser(); 15 | 16 | if (!user) { 17 | return redirect('/login'); 18 | } 19 | 20 | return <>{children}; 21 | } 22 | -------------------------------------------------------------------------------- /app/chat/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Button } from '@/components/ui/button'; 4 | import { Input } from '@/components/ui/input'; 5 | import { usePipeline } from '@/lib/hooks/use-pipeline'; 6 | import { cn } from '@/lib/utils'; 7 | import { Database } from '@/supabase/functions/_lib/database'; 8 | import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'; 9 | import { useChat } from 'ai/react'; 10 | 11 | export default function ChatPage() { 12 | const supabase = createClientComponentClient(); 13 | 14 | const generateEmbedding = usePipeline( 15 | 'feature-extraction', 16 | 'Supabase/gte-small' 17 | ); 18 | 19 | const { messages, input, handleInputChange, handleSubmit, isLoading } = 20 | useChat({ 21 | api: `${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`, 22 | }); 23 | 24 | const isReady = !!generateEmbedding; 25 | 26 | return ( 27 |
28 |
29 |
30 | {messages.map(({ id, role, content }) => ( 31 |
38 | {content} 39 |
40 | ))} 41 | {isLoading && ( 42 |
43 | )} 44 | {messages.length === 0 && ( 45 |
46 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | )} 61 |
62 |
{ 65 | e.preventDefault(); 66 | if (!generateEmbedding) { 67 | throw new Error('Unable to generate embeddings'); 68 | } 69 | 70 | const output = await generateEmbedding(input, { 71 | pooling: 'mean', 72 | normalize: true, 73 | }); 74 | 75 | const embedding = JSON.stringify(Array.from(output.data)); 76 | 77 | const { 78 | data: { session }, 79 | } = await supabase.auth.getSession(); 80 | 81 | if (!session) { 82 | return; 83 | } 84 | 85 | handleSubmit(e, { 86 | options: { 87 | headers: { 88 | authorization: `Bearer ${session.access_token}`, 89 | }, 90 | body: { 91 | embedding, 92 | }, 93 | }, 94 | }); 95 | }} 96 | > 97 | 104 | 107 |
108 |
109 |
110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/chatgpt-your-files/39f1403ed1c5aad57a7527c63638930e7c131e20/app/favicon.ico -------------------------------------------------------------------------------- /app/files/layout.tsx: -------------------------------------------------------------------------------- 1 | import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { redirect } from 'next/navigation'; 4 | import { PropsWithChildren } from 'react'; 5 | 6 | export default async function FilesLayout({ children }: PropsWithChildren) { 7 | // Keep cookies in the JS execution context for Next.js build 8 | const cookieStore = cookies(); 9 | 10 | const supabase = createServerComponentClient({ cookies: () => cookieStore }); 11 | 12 | const { 13 | data: { user }, 14 | } = await supabase.auth.getUser(); 15 | 16 | if (!user) { 17 | return redirect('/login'); 18 | } 19 | 20 | return <>{children}; 21 | } 22 | -------------------------------------------------------------------------------- /app/files/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Input } from '@/components/ui/input'; 4 | import { toast } from '@/components/ui/use-toast'; 5 | import { Database } from '@/supabase/functions/_lib/database'; 6 | import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'; 7 | import { useQuery } from '@tanstack/react-query'; 8 | import { useRouter } from 'next/navigation'; 9 | 10 | export default function FilesPage() { 11 | const supabase = createClientComponentClient(); 12 | const router = useRouter(); 13 | 14 | const { data: documents } = useQuery(['files'], async () => { 15 | const { data, error } = await supabase 16 | .from('documents_with_storage_path') 17 | .select(); 18 | 19 | if (error) { 20 | toast({ 21 | variant: 'destructive', 22 | description: 'Failed to fetch documents', 23 | }); 24 | throw error; 25 | } 26 | 27 | return data; 28 | }); 29 | 30 | return ( 31 |
32 |
33 | { 38 | const selectedFile = e.target.files?.[0]; 39 | 40 | if (selectedFile) { 41 | const { error } = await supabase.storage 42 | .from('files') 43 | .upload( 44 | `${crypto.randomUUID()}/${selectedFile.name}`, 45 | selectedFile 46 | ); 47 | 48 | if (error) { 49 | toast({ 50 | variant: 'destructive', 51 | description: 52 | 'There was an error uploading the file. Please try again.', 53 | }); 54 | return; 55 | } 56 | 57 | router.push('/chat'); 58 | } 59 | }} 60 | /> 61 |
62 | {documents && ( 63 |
64 | {documents.map((document) => ( 65 |
{ 68 | if (!document.storage_object_path) { 69 | toast({ 70 | variant: 'destructive', 71 | description: 'Failed to download file, please try again.', 72 | }); 73 | return; 74 | } 75 | 76 | const { data, error } = await supabase.storage 77 | .from('files') 78 | .createSignedUrl(document.storage_object_path, 60); 79 | 80 | if (error) { 81 | toast({ 82 | variant: 'destructive', 83 | description: 'Failed to download file. Please try again.', 84 | }); 85 | return; 86 | } 87 | 88 | window.location.href = data.signedUrl; 89 | }} 90 | > 91 | 98 | 99 | 100 | 101 | {document.name} 102 |
103 | ))} 104 |
105 | )} 106 |
107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import LogoutButton from '@/components/LogoutButton'; 2 | import { Toaster } from '@/components/ui/toaster'; 3 | import Providers from '@/lib/providers'; 4 | import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; 5 | import { cookies } from 'next/headers'; 6 | import Link from 'next/link'; 7 | import { PropsWithChildren } from 'react'; 8 | import 'three-dots/dist/three-dots.css'; 9 | import './globals.css'; 10 | 11 | export const metadata = { 12 | title: 'Create Next App', 13 | description: 'Generated by create next app', 14 | }; 15 | 16 | export default async function RootLayout({ children }: PropsWithChildren) { 17 | // Keep cookies in the JS execution context for Next.js build 18 | const cookieStore = cookies(); 19 | 20 | const supabase = createServerComponentClient({ cookies: () => cookieStore }); 21 | 22 | const { 23 | data: { user }, 24 | } = await supabase.auth.getUser(); 25 | 26 | return ( 27 | 28 | 29 | 30 |
31 | 88 |
89 | {children} 90 |
91 | 92 |
93 |
94 | 95 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /app/login/layout.tsx: -------------------------------------------------------------------------------- 1 | import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import { redirect } from 'next/navigation'; 4 | import { PropsWithChildren } from 'react'; 5 | 6 | export default async function LoginLayout({ children }: PropsWithChildren) { 7 | // Keep cookies in the JS execution context for Next.js build 8 | const cookieStore = cookies(); 9 | 10 | const supabase = createServerComponentClient({ cookies: () => cookieStore }); 11 | 12 | const { 13 | data: { user }, 14 | } = await supabase.auth.getUser(); 15 | 16 | if (user) { 17 | return redirect('/'); 18 | } 19 | 20 | return <>{children}; 21 | } 22 | -------------------------------------------------------------------------------- /app/login/messages.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useSearchParams } from 'next/navigation'; 4 | 5 | export default function Messages() { 6 | const searchParams = useSearchParams(); 7 | const error = searchParams.get('error'); 8 | const message = searchParams.get('message'); 9 | return ( 10 | <> 11 | {error && ( 12 |

13 | {error} 14 |

15 | )} 16 | {message && ( 17 |

18 | {message} 19 |

20 | )} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import Messages from './messages'; 2 | 3 | export default function Login() { 4 | return ( 5 |
6 |
11 | 14 | 20 | 23 | 30 | 33 | 39 | 40 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/chatgpt-your-files/39f1403ed1c5aad57a7527c63638930e7c131e20/app/opengraph-image.png -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; 2 | import { cookies } from 'next/headers'; 3 | import Link from 'next/link'; 4 | 5 | export default async function Index() { 6 | const cookeStore = cookies(); 7 | const supabase = createServerComponentClient({ cookies: () => cookeStore }); 8 | 9 | const { 10 | data: { user }, 11 | } = await supabase.auth.getUser(); 12 | 13 | return ( 14 |
15 |
16 |
17 |

Supabase and Next.js Starter Template

18 |

19 | Chat with your files using Supabase and{' '} 20 | Next.js 21 |

22 | {user ? ( 23 |
24 | 28 | Upload 29 | 30 | 34 | Chat 35 | 36 |
37 | ) : ( 38 |
39 | 43 | Login 44 | 45 |
46 | )} 47 |
48 |
49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /assets/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/chatgpt-your-files/39f1403ed1c5aad57a7527c63638930e7c131e20/assets/hero.png -------------------------------------------------------------------------------- /assets/instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/chatgpt-your-files/39f1403ed1c5aad57a7527c63638930e7c131e20/assets/instructions.png -------------------------------------------------------------------------------- /assets/step-2-er-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase-community/chatgpt-your-files/39f1403ed1c5aad57a7527c63638930e7c131e20/assets/step-2-er-diagram.png -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/LogoutButton.tsx: -------------------------------------------------------------------------------- 1 | export default function LogoutButton() { 2 | return ( 3 |
4 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /components/NextJsLogo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function NextJsLogo() { 4 | return ( 5 | 12 | 16 | 20 | 24 | 28 | 34 | 38 | 42 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /components/SupabaseLogo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function SupabaseLogo() { 4 | return ( 5 | 13 | 14 | 15 | 19 | 24 | 28 | 29 | 33 | 37 | 41 | 45 | 49 | 53 | 57 | 61 | 62 | 63 | 71 | 72 | 73 | 74 | 82 | 83 | 84 | 85 | 86 | 92 | 93 | 94 | 100 | 101 | 102 | 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ToastPrimitives from "@radix-ui/react-toast" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | import { X } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ToastProvider = ToastPrimitives.Provider 9 | 10 | const ToastViewport = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 24 | 25 | const toastVariants = cva( 26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 27 | { 28 | variants: { 29 | variant: { 30 | default: "border bg-background text-foreground", 31 | destructive: 32 | "destructive group border-destructive bg-destructive text-destructive-foreground", 33 | }, 34 | }, 35 | defaultVariants: { 36 | variant: "default", 37 | }, 38 | } 39 | ) 40 | 41 | const Toast = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef & 44 | VariantProps 45 | >(({ className, variant, ...props }, ref) => { 46 | return ( 47 | 52 | ) 53 | }) 54 | Toast.displayName = ToastPrimitives.Root.displayName 55 | 56 | const ToastAction = React.forwardRef< 57 | React.ElementRef, 58 | React.ComponentPropsWithoutRef 59 | >(({ className, ...props }, ref) => ( 60 | 68 | )) 69 | ToastAction.displayName = ToastPrimitives.Action.displayName 70 | 71 | const ToastClose = React.forwardRef< 72 | React.ElementRef, 73 | React.ComponentPropsWithoutRef 74 | >(({ className, ...props }, ref) => ( 75 | 84 | 85 | 86 | )) 87 | ToastClose.displayName = ToastPrimitives.Close.displayName 88 | 89 | const ToastTitle = React.forwardRef< 90 | React.ElementRef, 91 | React.ComponentPropsWithoutRef 92 | >(({ className, ...props }, ref) => ( 93 | 98 | )) 99 | ToastTitle.displayName = ToastPrimitives.Title.displayName 100 | 101 | const ToastDescription = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | ToastDescription.displayName = ToastPrimitives.Description.displayName 112 | 113 | type ToastProps = React.ComponentPropsWithoutRef 114 | 115 | type ToastActionElement = React.ReactElement 116 | 117 | export { 118 | type ToastProps, 119 | type ToastActionElement, 120 | ToastProvider, 121 | ToastViewport, 122 | Toast, 123 | ToastTitle, 124 | ToastDescription, 125 | ToastClose, 126 | ToastAction, 127 | } 128 | -------------------------------------------------------------------------------- /components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/components/ui/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /components/ui/use-toast.ts: -------------------------------------------------------------------------------- 1 | // Inspired by react-hot-toast library 2 | import * as React from "react" 3 | 4 | import type { 5 | ToastActionElement, 6 | ToastProps, 7 | } from "@/components/ui/toast" 8 | 9 | const TOAST_LIMIT = 1 10 | const TOAST_REMOVE_DELAY = 1000000 11 | 12 | type ToasterToast = ToastProps & { 13 | id: string 14 | title?: React.ReactNode 15 | description?: React.ReactNode 16 | action?: ToastActionElement 17 | } 18 | 19 | const actionTypes = { 20 | ADD_TOAST: "ADD_TOAST", 21 | UPDATE_TOAST: "UPDATE_TOAST", 22 | DISMISS_TOAST: "DISMISS_TOAST", 23 | REMOVE_TOAST: "REMOVE_TOAST", 24 | } as const 25 | 26 | let count = 0 27 | 28 | function genId() { 29 | count = (count + 1) % Number.MAX_VALUE 30 | return count.toString() 31 | } 32 | 33 | type ActionType = typeof actionTypes 34 | 35 | type Action = 36 | | { 37 | type: ActionType["ADD_TOAST"] 38 | toast: ToasterToast 39 | } 40 | | { 41 | type: ActionType["UPDATE_TOAST"] 42 | toast: Partial 43 | } 44 | | { 45 | type: ActionType["DISMISS_TOAST"] 46 | toastId?: ToasterToast["id"] 47 | } 48 | | { 49 | type: ActionType["REMOVE_TOAST"] 50 | toastId?: ToasterToast["id"] 51 | } 52 | 53 | interface State { 54 | toasts: ToasterToast[] 55 | } 56 | 57 | const toastTimeouts = new Map>() 58 | 59 | const addToRemoveQueue = (toastId: string) => { 60 | if (toastTimeouts.has(toastId)) { 61 | return 62 | } 63 | 64 | const timeout = setTimeout(() => { 65 | toastTimeouts.delete(toastId) 66 | dispatch({ 67 | type: "REMOVE_TOAST", 68 | toastId: toastId, 69 | }) 70 | }, TOAST_REMOVE_DELAY) 71 | 72 | toastTimeouts.set(toastId, timeout) 73 | } 74 | 75 | export const reducer = (state: State, action: Action): State => { 76 | switch (action.type) { 77 | case "ADD_TOAST": 78 | return { 79 | ...state, 80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 81 | } 82 | 83 | case "UPDATE_TOAST": 84 | return { 85 | ...state, 86 | toasts: state.toasts.map((t) => 87 | t.id === action.toast.id ? { ...t, ...action.toast } : t 88 | ), 89 | } 90 | 91 | case "DISMISS_TOAST": { 92 | const { toastId } = action 93 | 94 | // ! Side effects ! - This could be extracted into a dismissToast() action, 95 | // but I'll keep it here for simplicity 96 | if (toastId) { 97 | addToRemoveQueue(toastId) 98 | } else { 99 | state.toasts.forEach((toast) => { 100 | addToRemoveQueue(toast.id) 101 | }) 102 | } 103 | 104 | return { 105 | ...state, 106 | toasts: state.toasts.map((t) => 107 | t.id === toastId || toastId === undefined 108 | ? { 109 | ...t, 110 | open: false, 111 | } 112 | : t 113 | ), 114 | } 115 | } 116 | case "REMOVE_TOAST": 117 | if (action.toastId === undefined) { 118 | return { 119 | ...state, 120 | toasts: [], 121 | } 122 | } 123 | return { 124 | ...state, 125 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 126 | } 127 | } 128 | } 129 | 130 | const listeners: Array<(state: State) => void> = [] 131 | 132 | let memoryState: State = { toasts: [] } 133 | 134 | function dispatch(action: Action) { 135 | memoryState = reducer(memoryState, action) 136 | listeners.forEach((listener) => { 137 | listener(memoryState) 138 | }) 139 | } 140 | 141 | type Toast = Omit 142 | 143 | function toast({ ...props }: Toast) { 144 | const id = genId() 145 | 146 | const update = (props: ToasterToast) => 147 | dispatch({ 148 | type: "UPDATE_TOAST", 149 | toast: { ...props, id }, 150 | }) 151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) 152 | 153 | dispatch({ 154 | type: "ADD_TOAST", 155 | toast: { 156 | ...props, 157 | id, 158 | open: true, 159 | onOpenChange: (open) => { 160 | if (!open) dismiss() 161 | }, 162 | }, 163 | }) 164 | 165 | return { 166 | id: id, 167 | dismiss, 168 | update, 169 | } 170 | } 171 | 172 | function useToast() { 173 | const [state, setState] = React.useState(memoryState) 174 | 175 | React.useEffect(() => { 176 | listeners.push(setState) 177 | return () => { 178 | const index = listeners.indexOf(setState) 179 | if (index > -1) { 180 | listeners.splice(index, 1) 181 | } 182 | } 183 | }, [state]) 184 | 185 | return { 186 | ...state, 187 | toast, 188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 189 | } 190 | } 191 | 192 | export { useToast, toast } 193 | -------------------------------------------------------------------------------- /lib/hooks/use-pipeline.ts: -------------------------------------------------------------------------------- 1 | import { Pipeline, PretrainedOptions, Tensor } from '@xenova/transformers'; 2 | import { useEffect, useState } from 'react'; 3 | import { 4 | InitEventData, 5 | OutgoingEventData, 6 | RunEventData, 7 | } from '../workers/pipeline'; 8 | 9 | export type PipeParameters = Parameters; 10 | export type PipeReturnType = Awaited>; 11 | export type PipeFunction = (...args: PipeParameters) => Promise; 12 | 13 | /** 14 | * Hook to build a Transformers.js pipeline function. 15 | * 16 | * Similar to `pipeline()`, but runs inference in a separate 17 | * Web Worker thread and asynchronous logic is 18 | * abstracted for you. 19 | * 20 | * *Important:* `options` must be memoized (if passed), 21 | * otherwise the hook will continuously rebuild the pipeline. 22 | */ 23 | export function usePipeline( 24 | task: string, 25 | model?: string, 26 | options?: PretrainedOptions 27 | ) { 28 | const [worker, setWorker] = useState(); 29 | const [pipe, setPipe] = useState(); 30 | 31 | // Using `useEffect` + `useState` over `useMemo` because we need a 32 | // cleanup function and asynchronous initialization 33 | useEffect(() => { 34 | const { progress_callback, ...transferableOptions } = options ?? {}; 35 | 36 | const worker = new Worker( 37 | new URL('../workers/pipeline.ts', import.meta.url), 38 | { 39 | type: 'module', 40 | } 41 | ); 42 | 43 | const onMessageReceived = (e: MessageEvent) => { 44 | const { type } = e.data; 45 | 46 | switch (type) { 47 | case 'progress': { 48 | const { data } = e.data; 49 | progress_callback?.(data); 50 | break; 51 | } 52 | case 'ready': { 53 | setWorker(worker); 54 | break; 55 | } 56 | } 57 | }; 58 | 59 | worker.addEventListener('message', onMessageReceived); 60 | 61 | worker.postMessage({ 62 | type: 'init', 63 | args: [task, model, transferableOptions], 64 | } satisfies InitEventData); 65 | 66 | return () => { 67 | worker.removeEventListener('message', onMessageReceived); 68 | worker.terminate(); 69 | 70 | setWorker(undefined); 71 | }; 72 | }, [task, model, options]); 73 | 74 | // Using `useEffect` + `useState` over `useMemo` because we need a 75 | // cleanup function 76 | useEffect(() => { 77 | if (!worker) { 78 | return; 79 | } 80 | 81 | // ID to sync return values between multiple ongoing pipe executions 82 | let currentId = 0; 83 | 84 | const callbacks = new Map void>(); 85 | 86 | const onMessageReceived = (e: MessageEvent) => { 87 | switch (e.data.type) { 88 | case 'result': 89 | const { id, data: serializedData } = e.data; 90 | const { type, data, dims } = serializedData; 91 | const output = new Tensor(type, data, dims); 92 | const callback = callbacks.get(id); 93 | 94 | if (!callback) { 95 | throw new Error(`Missing callback for pipe execution id: ${id}`); 96 | } 97 | 98 | callback(output); 99 | break; 100 | } 101 | }; 102 | 103 | worker.addEventListener('message', onMessageReceived); 104 | 105 | const pipe: PipeFunction = (...args) => { 106 | if (!worker) { 107 | throw new Error('Worker unavailable'); 108 | } 109 | 110 | const id = currentId++; 111 | 112 | return new Promise((resolve) => { 113 | callbacks.set(id, resolve); 114 | worker.postMessage({ type: 'run', id, args } satisfies RunEventData); 115 | }); 116 | }; 117 | 118 | setPipe(() => pipe); 119 | 120 | return () => { 121 | worker?.removeEventListener('message', onMessageReceived); 122 | setPipe(undefined); 123 | }; 124 | }, [worker]); 125 | 126 | return pipe; 127 | } 128 | -------------------------------------------------------------------------------- /lib/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 4 | import { PropsWithChildren, useMemo } from 'react'; 5 | 6 | export default function Providers({ children }: PropsWithChildren<{}>) { 7 | const queryClient = useMemo(() => new QueryClient(), []); 8 | return ( 9 | {children} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /lib/workers/pipeline.ts: -------------------------------------------------------------------------------- 1 | import { Pipeline, pipeline } from '@xenova/transformers'; 2 | import { PipeParameters, PipeReturnType } from '../hooks/use-pipeline'; 3 | 4 | export type InitEventData = { 5 | type: 'init'; 6 | args: Parameters; 7 | }; 8 | 9 | export type RunEventData = { 10 | type: 'run'; 11 | id: number; 12 | args: PipeParameters; 13 | }; 14 | 15 | export type IncomingEventData = InitEventData | RunEventData; 16 | 17 | type BaseProgressUpdate = { 18 | name: string; 19 | file: string; 20 | }; 21 | 22 | export type InitiateProgressUpdate = BaseProgressUpdate & { 23 | status: 'initiate'; 24 | }; 25 | 26 | export type DownloadProgressUpdate = BaseProgressUpdate & { 27 | status: 'download'; 28 | }; 29 | 30 | export type ProgressProgressUpdate = BaseProgressUpdate & { 31 | status: 'progress'; 32 | progress: number; 33 | loaded: number; 34 | total: number; 35 | }; 36 | 37 | export type DoneProgressUpdate = BaseProgressUpdate & { 38 | status: 'done'; 39 | }; 40 | 41 | export type ProgressUpdate = 42 | | InitiateProgressUpdate 43 | | DownloadProgressUpdate 44 | | ProgressProgressUpdate 45 | | DoneProgressUpdate; 46 | 47 | export type ProgressEventData = { 48 | type: 'progress'; 49 | data: ProgressUpdate; 50 | }; 51 | 52 | export type ReadyEventData = { 53 | type: 'ready'; 54 | }; 55 | 56 | export type ResultEventData = { 57 | type: 'result'; 58 | id: number; 59 | data: PipeReturnType; 60 | }; 61 | 62 | export type OutgoingEventData = 63 | | ProgressEventData 64 | | ReadyEventData 65 | | ResultEventData; 66 | 67 | class PipelineSingleton { 68 | static instance?: Pipeline; 69 | 70 | static async init(...args: Parameters) { 71 | this.instance = await pipeline(...args); 72 | } 73 | } 74 | 75 | // Listen for messages from the main thread 76 | self.addEventListener( 77 | 'message', 78 | async (event: MessageEvent) => { 79 | const { type, args } = event.data; 80 | 81 | switch (type) { 82 | case 'init': { 83 | const progress_callback = (data: ProgressUpdate) => { 84 | self.postMessage({ 85 | type: 'progress', 86 | data, 87 | } satisfies ProgressEventData); 88 | }; 89 | 90 | const [task, model, options] = args; 91 | 92 | await PipelineSingleton.init(task, model, { 93 | ...options, 94 | progress_callback, 95 | }); 96 | 97 | self.postMessage({ 98 | type: 'ready', 99 | } satisfies ReadyEventData); 100 | 101 | break; 102 | } 103 | case 'run': { 104 | if (!PipelineSingleton.instance) { 105 | throw new Error('Pipeline not initialized'); 106 | } 107 | 108 | const { id } = event.data; 109 | 110 | const output = await PipelineSingleton.instance(...args); 111 | 112 | // Classes (ie. `Tensor`) cannot be transferred to the main thread, 113 | // so we spread its properties into a plain object 114 | const data = { ...output }; 115 | 116 | self.postMessage({ 117 | type: 'result', 118 | id, 119 | data, 120 | } satisfies ResultEventData); 121 | 122 | break; 123 | } 124 | } 125 | } 126 | ); 127 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' 2 | import { NextResponse } from 'next/server' 3 | 4 | import type { NextRequest } from 'next/server' 5 | 6 | export async function middleware(req: NextRequest) { 7 | const res = NextResponse.next() 8 | 9 | // Create a Supabase client configured to use cookies 10 | const supabase = createMiddlewareClient({ req, res }) 11 | 12 | // Refresh session if expired - required for Server Components 13 | // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware 14 | await supabase.auth.getSession() 15 | 16 | return res 17 | } 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | webpack: (config) => { 7 | config.resolve.alias = { 8 | ...config.resolve.alias, 9 | sharp$: false, 10 | 'onnxruntime-node$': false, 11 | }; 12 | return config; 13 | }, 14 | }; 15 | 16 | module.exports = nextConfig; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start", 7 | "gen:types": "supabase gen types typescript --local > supabase/functions/_lib/database.ts" 8 | }, 9 | "dependencies": { 10 | "@radix-ui/react-slot": "^1.0.2", 11 | "@radix-ui/react-toast": "^1.1.5", 12 | "@supabase/auth-helpers-nextjs": "latest", 13 | "@supabase/supabase-js": "latest", 14 | "@tanstack/react-query": "^4.35.7", 15 | "@xenova/transformers": "^2.6.2", 16 | "ai": "^2.2.14", 17 | "autoprefixer": "10.4.15", 18 | "class-variance-authority": "^0.7.0", 19 | "clsx": "^2.0.0", 20 | "lucide-react": "^0.284.0", 21 | "next": "latest", 22 | "postcss": "8.4.29", 23 | "react": "18.2.0", 24 | "react-dom": "18.2.0", 25 | "tailwind-merge": "^1.14.0", 26 | "tailwindcss": "3.3.3", 27 | "tailwindcss-animate": "^1.0.7", 28 | "three-dots": "^0.3.2", 29 | "typescript": "5.1.3" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "20.3.1", 33 | "@types/react": "18.2.12", 34 | "@types/react-dom": "18.2.5", 35 | "encoding": "^0.1.13", 36 | "supabase": "^1.163.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /sample-files/roman-empire-2.md: -------------------------------------------------------------------------------- 1 | ### Military 2 | 3 | Main articles: [Imperial Roman army](/wiki/Imperial_Roman_army 'Imperial Roman army') and [Structural history of the Roman military](/wiki/Structural_history_of_the_Roman_military 'Structural history of the Roman military') 4 | 5 | After the [Punic Wars](/wiki/Punic_Wars 'Punic Wars'), the Roman army comprised professional soldiers who volunteered for 20 years of active duty and five as reserves. The transition to a professional military began during the late Republic and was one of the many profound shifts away from republicanism, under which an army of [conscript citizens](/wiki/Conscripts 'Conscripts') defended the homeland against a specific threat. The Romans expanded their war machine by \"organizing the communities that they conquered in Italy into a system that generated huge reservoirs of manpower for their army.\" By Imperial times, military service was a full-time career. The pervasiveness of military garrisons throughout the Empire was a major influence in the process of [Romanization]( 'Romanization (cultural)'). 6 | 7 | The primary mission of the military of the early empire was to preserve the [Pax Romana](/wiki/Pax_Romana 'Pax Romana'). The three major divisions of the military were: 8 | 9 | - the garrison at Rome, comprising the [Praetorian Guard](/wiki/Praetorian_Guard 'Praetorian Guard'), the _[cohortes urbanae](/wiki/Cohortes_urbanae 'Cohortes urbanae')_ and the _[vigiles](/wiki/Vigiles 'Vigiles')_, who functioned as police and firefighters; 10 | - the provincial army, comprising the [Roman legions](/wiki/Roman_legions 'Roman legions') and the auxiliaries provided by the provinces _([auxilia](/wiki/Auxilia 'Auxilia')_); 11 | - the [navy](/wiki/Roman_navy 'Roman navy'). 12 | 13 | Through his military reforms, which included consolidating or disbanding units of questionable loyalty, Augustus regularized the legion. A legion was organized into ten [cohorts]( 'Cohort (military unit)'), each of which comprised six [centuries](/wiki/Centuria 'Centuria'), with a century further made up of ten squads _([contubernia](/wiki/Contubernium 'Contubernium'))_; the exact size of the Imperial legion, which was likely determined by [logistics](/wiki/Military_logistics 'Military logistics'), has been estimated to range from 4,800 to 5,280. After Germanic tribes wiped out three legions in the [Battle of the Teutoburg Forest](/wiki/Battle_of_the_Teutoburg_Forest 'Battle of the Teutoburg Forest') in 9 AD, the number of legions was increased from 25 to around 30. The army had about 300,000 soldiers in the 1st century, and under 400,000 in the 2nd, \"significantly smaller\" than the collective armed forces of the conquered territories. No more than 2% of adult males living in the Empire served in the Imperial army. Augustus also created the [Praetorian Guard](/wiki/Praetorian_Guard 'Praetorian Guard'): nine cohorts, ostensibly to maintain the public peace, which were garrisoned in Italy. Better paid than the legionaries, the Praetorians served only sixteen years. 14 | 15 | The _auxilia_ were recruited from among the non-citizens. Organized in smaller units of roughly cohort strength, they were paid less than the legionaries, and after 25 years of service were rewarded with [Roman citizenship](/wiki/Roman_citizenship 'Roman citizenship'), also extended to their sons. According to [Tacitus](/wiki/Tacitus 'Tacitus') there were roughly as many auxiliaries as there were legionaries---thus, around 125,000 men, implying approximately 250 auxiliary regiments. The [Roman cavalry](/wiki/Roman_cavalry 'Roman cavalry') of the earliest Empire were primarily from Celtic, Hispanic or Germanic areas. Several aspects of training and equipment derived from the Celts. 16 | 17 | The [Roman navy](/wiki/Roman_navy 'Roman navy') not only aided in the supply and transport of the legions but also in the protection of the [frontiers]( 'Limes (Roman Empire)') along the rivers [Rhine](/wiki/Rhine 'Rhine') and [Danube](/wiki/Danube 'Danube'). Another duty was protecting maritime trade against pirates. It patrolled the Mediterranean, parts of the [North Atlantic](/wiki/Atlantic 'Atlantic') coasts, and the [Black Sea](/wiki/Black_Sea 'Black Sea'). Nevertheless, the army was considered the senior and more prestigious branch. 18 | 19 | ### Provincial government 20 | 21 | An annexed territory became a [Roman province](/wiki/Roman_province 'Roman province') in three steps: making a register of cities, taking a census, and surveying the land. Further government recordkeeping included births and deaths, real estate transactions, taxes, and juridical proceedings. In the 1st and 2nd centuries, the central government sent out around 160 officials annually to govern outside Italy. Among these officials were the [Roman governors](/wiki/Roman_governor 'Roman governor'): [magistrates elected at Rome](/wiki/Executive_magistrates_of_the_Roman_Empire 'Executive magistrates of the Roman Empire') who in the name of the [Roman people](/wiki/SPQR 'SPQR') governed [senatorial provinces](/wiki/Senatorial_province 'Senatorial province'); or governors, usually of equestrian rank, who held their _imperium_ on behalf of the emperor in [imperial provinces](/wiki/Imperial_province 'Imperial province'), most notably [Roman Egypt](/wiki/Roman_Egypt 'Roman Egypt'). A governor had to make himself accessible to the people he governed, but he could delegate various duties. His staff, however, was minimal: his official attendants _([apparitores](/wiki/Apparitor 'Apparitor'))_, including [lictors](/wiki/Lictor 'Lictor'), heralds, messengers, [scribes]( 'Scriba (ancient Rome)'), and bodyguards; [legates](/wiki/Legatus 'Legatus'), both civil and military, usually of equestrian rank; and friends who accompanied him unofficially. 22 | 23 | Other officials were appointed as supervisors of government finances. Separating fiscal responsibility from justice and administration was a reform of the Imperial era, to avoid provincial governors and [tax farmers]( 'Farm (revenue leasing)') exploiting local populations for personal gain. Equestrian [procurators]( 'Procurator (Roman)'), whose authority was originally \"extra-judicial and extra-constitutional,\" managed both state-owned property and the personal property of the emperor _([res privata](/wiki/Privatus 'Privatus'))_. Because Roman government officials were few, a provincial who needed help with a legal dispute or criminal case might seek out any Roman perceived to have some official capacity. 24 | 25 | ### Law 26 | 27 | Main article: [Roman law](/wiki/Roman_law 'Roman law') 28 | 29 | Roman courts held [original jurisdiction](/wiki/Original_jurisdiction 'Original jurisdiction') over cases involving Roman citizens throughout the empire, but there were too few judicial functionaries to impose Roman law uniformly in the provinces. Most parts of the Eastern Empire already had well-established law codes and juridical procedures. Generally, it was Roman policy to respect the _mos regionis_ (\"regional tradition\" or \"law of the land\") and to regard local laws as a source of legal precedent and social stability. The compatibility of Roman and local law was thought to reflect an underlying _[ius gentium](/wiki/Ius_gentium 'Ius gentium')_, the \"law of nations\" or [international law](/wiki/International_law 'International law') regarded as common and customary. If provincial law conflicted with Roman law or custom, Roman courts heard [appeals](/wiki/Appellate_court 'Appellate court'), and the emperor held final decision-making authority. 30 | 31 | In the West, law had been administered on a highly localized or tribal basis, and [private property rights](/wiki/Private_property_rights 'Private property rights') may have been a novelty of the Roman era, particularly among [Celts](/wiki/Celts 'Celts'). Roman law facilitated the acquisition of wealth by a pro-Roman elite. The extension of universal citizenship to all free inhabitants of the Empire in 212 required the uniform application of Roman law, replacing local law codes that had applied to non-citizens. Diocletian\'s efforts to stabilize the Empire after the [Crisis of the Third Century](/wiki/Crisis_of_the_Third_Century 'Crisis of the Third Century') included two major compilations of law in four years, the _[Codex Gregorianus](/wiki/Codex_Gregorianus 'Codex Gregorianus')_ and the _[Codex Hermogenianus](/wiki/Codex_Hermogenianus 'Codex Hermogenianus')_, to guide provincial administrators in setting consistent legal standards. 32 | 33 | The pervasiveness of Roman law throughout Western Europe enormously influenced the Western legal tradition, reflected by continued use of [Latin legal terminology](/wiki/List_of_legal_Latin_terms 'List of legal Latin terms') in modern law. 34 | 35 | ### Taxation 36 | 37 | Further information: [Taxation in ancient Rome](/wiki/Taxation_in_ancient_Rome 'Taxation in ancient Rome') 38 | 39 | Taxation under the Empire amounted to about 5% of its [gross product](/wiki/Roman_gross_domestic_product 'Roman gross domestic product'). The typical tax rate for individuals ranged from 2 to 5%. The tax code was \"bewildering\" in its complicated system of [direct](/wiki/Direct_taxation 'Direct taxation') and [indirect taxes](/wiki/Indirect_taxes 'Indirect taxes'), some paid in cash and some [in kind](/wiki/Barter 'Barter'). Taxes might be specific to a province, or kinds of properties such as [fisheries](/wiki/Fishery 'Fishery'); they might be temporary. Tax collection was justified by the need to maintain the military, and taxpayers sometimes got a refund if the army captured a surplus of booty. In-kind taxes were accepted from less-[monetized](/wiki/Monetization 'Monetization') areas, particularly those who could supply grain or goods to army camps. The primary source of direct tax revenue was individuals, who paid a [poll tax](/wiki/Tax_per_head 'Tax per head') and a tax on their land, construed as a tax on its produce or productive capacity. Tax obligations were determined by the census: each head of household provided a headcount of his household, as well as an accounting of his property. A major source of indirect-tax revenue was the _portoria_, customs and tolls on trade, including among provinces. Towards the end of his reign, Augustus instituted a 4% tax on the sale of slaves, which Nero shifted from the purchaser to the dealers, who responded by raising their prices. An owner who manumitted a slave paid a \"freedom tax\", calculated at 5% of value. An [inheritance tax](/wiki/Inheritance_tax 'Inheritance tax') of 5% was assessed when Roman citizens above a certain net worth left property to anyone outside their immediate family. Revenues from the estate tax and from an auction tax went towards the veterans\' pension fund _([aerarium militare](/wiki/Aerarium_militare 'Aerarium militare'))_. 40 | 41 | Low taxes helped the Roman aristocracy increase their wealth, which equalled or exceeded the revenues of the central government. An emperor sometimes replenished his treasury by confiscating the estates of the \"super-rich\", but in the later period, the [resistance](/wiki/Tax_resistance 'Tax resistance') of the wealthy to paying taxes was one of the factors contributing to the collapse of the Empire. 42 | 43 | ## Economy 44 | 45 | Main article: [Roman economy](/wiki/Roman_economy 'Roman economy') 46 | 47 | The Empire is best thought of as a network of regional economies, based on a form of \"political capitalism\" in which the state regulated commerce to assure its own revenues. Economic growth, though not comparable to modern economies, was greater than that of most other societies prior to [industrialization](/wiki/Industrial_Revolution 'Industrial Revolution'). Territorial conquests permitted a large-scale reorganization of [land use](/wiki/Land_use 'Land use') that resulted in agricultural surplus and specialization, particularly in north Africa. Some cities were known for particular industries. The scale of urban building indicates a significant construction industry. Papyri preserve complex accounting methods that suggest elements of [economic rationalism](/wiki/Economic_rationalism 'Economic rationalism'), and the Empire was highly monetized. Although the means of communication and transport were limited in antiquity, transportation in the 1st and 2nd centuries expanded greatly, and trade routes connected regional economies. The [supply contracts for the army](/wiki/Economics_of_the_Roman_army 'Economics of the Roman army') drew on local suppliers near the base _([castrum](/wiki/Castrum 'Castrum'))_, throughout the province, and across provincial borders. [Economic historians](/wiki/Economic_history 'Economic history') vary in their calculations of the gross domestic product during the Principate. In the sample years of 14, 100, and 150 AD, estimates of per capita GDP range from 166 to 380 _[HS](/wiki/Sestertius 'Sestertius')_. The GDP per capita of [Italy]( 'Italia (Roman Empire)') is estimated as 40 to 66% higher than in the rest of the Empire, due to tax transfers from the provinces and the concentration of elite income. 48 | 49 | Economic dynamism resulted in social mobility. Although aristocratic values permeated traditional elite society, wealth requirements for [rank](#Census_rank) indicate a strong tendency towards [plutocracy](/wiki/Plutocracy 'Plutocracy'). Prestige could be obtained through investing one\'s wealth in grand estates or townhouses, luxury items, [public entertainments](#Spectacles), funerary monuments, and [religious dedications](/wiki/Votum 'Votum'). Guilds _([collegia](/wiki/Collegium 'Collegium'))_ and corporations _(corpora)_ provided support for individuals to succeed through networking. \"There can be little doubt that the lower classes of \... provincial towns of the Roman Empire enjoyed a high [standard of living](/wiki/Standard_of_living 'Standard of living') not equaled again in Western Europe until the 19th century\". Households in the top 1.5% of [income distribution](/wiki/Income_distribution 'Income distribution') captured about 20% of income. The \"vast majority\" produced more than half of the total income, but lived near [subsistence](/wiki/Subsistence 'Subsistence'). 50 | 51 | ### Currency and banking 52 | 53 | See also: [Roman currency](/wiki/Roman_currency 'Roman currency') and [Roman finance](/wiki/Roman_finance 'Roman finance') 54 | 55 | The early Empire was monetized to a near-universal extent, using money as a way to express [prices](/wiki/Price 'Price') and [debts](/wiki/Debt 'Debt'). The _[sestertius](/wiki/Sestertius 'Sestertius')_ (English \"sesterces\", symbolized as _HS_) was the basic unit of reckoning value into the 4th century, though the silver _[denarius](/wiki/Denarius 'Denarius')_, worth four sesterces, was also used beginning in the [Severan dynasty](/wiki/Severan_dynasty 'Severan dynasty'). The smallest coin commonly circulated was the bronze _[as]( 'As (Roman coin)')*, one-tenth _denarius_. [Bullion](/wiki/Bullion 'Bullion') and [ingots](/wiki/Ingot 'Ingot') seem not to have counted as _pecunia_ (\"money\") and were used only on the frontiers. Romans in the first and second centuries counted coins, rather than weighing them---an indication that the coin was valued on its face. This tendency towards [fiat money](/wiki/Fiat_money 'Fiat money') led to the [debasement](/wiki/Debasement 'Debasement') of Roman coinage in the later Empire. The standardization of money throughout the Empire promoted trade and [market integration](/wiki/Market_integration 'Market integration'). The high amount of metal coinage in circulation increased the [money supply](/wiki/Money_supply 'Money supply') for trading or saving. Rome had no [central bank](/wiki/Central_bank 'Central bank'), and regulation of the banking system was minimal. Banks of classical antiquity typically kept [less in reserves](/wiki/Fractional_reserve_banking 'Fractional reserve banking') than the full total of customers\' deposits. A typical bank had fairly limited [capital](/wiki/Financial_capital 'Financial capital'), and often only one principal. [Seneca](/wiki/Seneca_the_Younger 'Seneca the Younger') assumes that anyone involved in [Roman commerce](/wiki/Roman_commerce 'Roman commerce') needs access to [credit]( 'Credit (finance)'). A professional [deposit](/wiki/Deposit_account 'Deposit account') banker received and held deposits for a fixed or indefinite term, and lent money to third parties. The senatorial elite were involved heavily in private lending, both as creditors and borrowers. The holder of a debt could use it as a means of payment by transferring it to another party, without cash changing hands. Although it has sometimes been thought that ancient Rome lacked [documentary transactions](/wiki/Negotiable_instrument 'Negotiable instrument'), the system of banks throughout the Empire permitted the exchange of large sums without physically transferring coins, in part because of the risks of moving large amounts of cash. Only one serious credit shortage is known to have occurred in the early Empire, in 33 AD; generally, available capital exceeded the amount needed by borrowers. The central government itself did not borrow money, and without [public debt](/wiki/Public_debt 'Public debt') had to fund [deficits](/wiki/Government_budget_balance 'Government budget balance') from cash reserves. 56 | 57 | Emperors of the [Antonine](/wiki/Antonine_dynasty 'Antonine dynasty') and [Severan](/wiki/Severan_dynasty 'Severan dynasty') dynasties debased the currency, particularly the _denarius_, under the pressures of meeting military payrolls. Sudden inflation under [Commodus](/wiki/Commodus 'Commodus') damaged the credit market. In the mid-200s, the supply of [specie](/wiki/Bullion_coin 'Bullion coin') contracted sharply. Conditions during the [Crisis of the Third Century](/wiki/Crisis_of_the_Third_Century 'Crisis of the Third Century')---such as reductions in long-distance trade, disruption of mining operations, and the physical transfer of gold coinage outside the empire by invading enemies---greatly diminished the money supply and the banking sector. Although Roman coinage had long been fiat money or [fiduciary currency](/wiki/Fiduciary_currency 'Fiduciary currency'), general economic anxieties came to a head under [Aurelian](/wiki/Aurelian 'Aurelian'), and bankers lost confidence in coins. Despite [Diocletian](/wiki/Diocletian 'Diocletian')\'s introduction of the gold _[solidus]( 'Solidus (coin)')\_ and monetary reforms, the credit market of the Empire never recovered its former robustness. 58 | 59 | ### Mining and metallurgy 60 | 61 | Main articles: [Mining in ancient Rome](/wiki/Mining_in_ancient_Rome 'Mining in ancient Rome') and [Roman metallurgy](/wiki/Roman_metallurgy 'Roman metallurgy') 62 | 63 | The main mining regions of the Empire were the Iberian Peninsula (gold, silver, copper, tin, lead); Gaul (gold, silver, iron); Britain (mainly iron, lead, tin), the [Danubian provinces](/wiki/Danubian_provinces 'Danubian provinces') (gold, iron); [Macedonia]( 'Macedonia (Roman province)') and [Thrace](/wiki/Thracia 'Thracia') (gold, silver); and Asia Minor (gold, silver, iron, tin). Intensive large-scale mining---of alluvial deposits, and by means of [open-cast mining](/wiki/Open-cast_mining 'Open-cast mining') and [underground mining](/wiki/Underground_mining 'Underground mining')---took place from the reign of Augustus up to the early 3rd century, when the instability of the Empire disrupted production. 64 | 65 | [Hydraulic mining](/wiki/Hydraulic_mining 'Hydraulic mining') allowed [base](/wiki/Base_metal 'Base metal') and [precious metals](/wiki/Precious_metal 'Precious metal') to be extracted on a proto-industrial scale. The total annual iron output is estimated at 82,500 [tonnes](/wiki/Tonnes 'Tonnes'). Copper and lead production levels were unmatched until the [Industrial Revolution](/wiki/Industrial_Revolution 'Industrial Revolution'). At its peak around the mid-2nd century, the Roman silver stock is estimated at 10,000 t, five to ten times larger than the combined silver mass of [medieval Europe](/wiki/Early_Middle_Ages 'Early Middle Ages') and the [Caliphate](/wiki/Abbasid_Caliphate 'Abbasid Caliphate') around 800 AD. As an indication of the scale of Roman metal production, lead pollution in the [Greenland ice sheet](/wiki/Greenland_ice_sheet 'Greenland ice sheet') quadrupled over prehistoric levels during the Imperial era and dropped thereafter. 66 | 67 | ### Transportation and communication 68 | 69 | Further information: [Cursus publicus](/wiki/Cursus_publicus 'Cursus publicus') 70 | 71 | The Empire completely encircled the Mediterranean, which they called \"our sea\" _([mare nostrum](/wiki/Mare_nostrum 'Mare nostrum'))_. Roman sailing vessels navigated the Mediterranean as well as major rivers. Transport by water was preferred where possible, as moving commodities by land was more difficult. Vehicles, wheels, and ships indicate the existence of a great number of skilled woodworkers. 72 | 73 | Land transport utilized the advanced system of [Roman roads](/wiki/Roman_roads 'Roman roads'), called \"_viae_\". These roads were primarily built for military purposes, but also served commercial ends. The in-kind taxes paid by communities included the provision of personnel, animals, or vehicles for the _[cursus publicus](/wiki/Cursus_publicus 'Cursus publicus')_, the state mail and transport service established by Augustus. Relay stations were located along the roads every seven to twelve [Roman miles](/wiki/Roman_mile 'Roman mile'), and tended to grow into villages or trading posts. A _[mansio](/wiki/Mansio 'Mansio')_ (plural _mansiones_) was a privately run service station franchised by the imperial bureaucracy for the _cursus publicus_. The distance between _mansiones_ was determined by how far a wagon could travel in a day. Carts were usually pulled by mules, travelling about 4 mph. 74 | 75 | ### Trade and commodities 76 | 77 | See also: [Roman commerce](/wiki/Roman_commerce 'Roman commerce'), [Indo-Roman trade relations](/wiki/Indo-Roman_trade_relations 'Indo-Roman trade relations'), and [Sino-Roman relations](/wiki/Sino-Roman_relations 'Sino-Roman relations') 78 | 79 | Roman provinces traded among themselves, but trade extended outside the frontiers to regions as far away as [China](/wiki/Ancient_China 'Ancient China') and [India](/wiki/Gupta_Empire 'Gupta Empire'). Chinese trade was mostly conducted overland through middle men along the [Silk Road](/wiki/Silk_Road 'Silk Road'); Indian trade also occurred by sea from [Egyptian](/wiki/Roman_Egypt 'Roman Egypt') ports. The main [commodity](/wiki/Commodity 'Commodity') was grain. Also traded were olive oil, foodstuffs, _[garum](/wiki/Garum 'Garum')_ ([fish sauce](/wiki/Fish_sauce 'Fish sauce')), slaves, ore and manufactured metal objects, fibres and textiles, timber, [pottery](/wiki/Ancient_Roman_pottery 'Ancient Roman pottery'), [glassware](/wiki/Roman_glass 'Roman glass'), marble, [papyrus](/wiki/Papyrus 'Papyrus'), spices and _[materia medica](/wiki/Materia_medica 'Materia medica')_, ivory, pearls, and gemstones. Though most provinces could produce wine, [regional varietals](/wiki/Ancient_Rome_and_wine 'Ancient Rome and wine') were desirable and wine was a central trade good. 80 | 81 | ### Labour and occupations 82 | 83 | Inscriptions record 268 different occupations in Rome and 85 in Pompeii. Professional associations or trade guilds _(collegia)_ are attested for a wide range of occupations, some quite specialized. 84 | 85 | Work performed by slaves falls into five general categories: domestic, with epitaphs recording at least 55 different household jobs; [imperial or public service](/wiki/Slavery_in_ancient_Rome#Servus_publicus 'Slavery in ancient Rome'); urban crafts and services; agriculture; and mining. Convicts provided much of the labour in the mines or quarries, where conditions were notoriously brutal. In practice, there was little division of labour between slave and free, and most workers were illiterate and without special skills. The greatest number of common labourers were employed in agriculture: in Italian industrial farming _([latifundia](/wiki/Latifundia 'Latifundia'))_, these may have been mostly slaves, but elsewhere slave farm labour was probably less important. 86 | 87 | Textile and clothing production was a major source of employment. Both textiles and finished garments were traded and products were often named for peoples or towns, like a [fashion \"label\"](/wiki/Fashion_brand 'Fashion brand'). Better ready-to-wear was exported by local businessmen (_negotiatores_ or _mercatores_). Finished garments might be retailed by their sales agents, by _vestiarii_ (clothing dealers), or peddled by itinerant merchants. The [fullers](/wiki/Fulling 'Fulling') (_[fullones](/wiki/Fullonica 'Fullonica')_) and dye workers (_coloratores_) had their own guilds. _Centonarii_ were guild workers who specialized in textile production and the recycling of old clothes into [pieced goods](/wiki/Patchwork 'Patchwork'). 88 | 89 | ## Architecture and engineering 90 | 91 | Main articles: [Ancient Roman architecture](/wiki/Ancient_Roman_architecture 'Ancient Roman architecture'), [Roman engineering](/wiki/Roman_engineering 'Roman engineering'), and [Roman technology](/wiki/Roman_technology 'Roman technology') 92 | 93 | The chief [Roman contributions to architecture](/wiki/Ancient_Roman_architecture 'Ancient Roman architecture') were the [arch](/wiki/Arch 'Arch'), [vault]( 'Vault (architecture)') and [dome](/wiki/Dome 'Dome'). Some Roman structures still stand today, due in part to sophisticated methods of making cements and [concrete](/wiki/Roman_concrete 'Roman concrete'). [Roman temples](/wiki/Roman_temple 'Roman temple') developed [Etruscan](/wiki/Etruscan_architecture 'Etruscan architecture') and Greek forms, with some distinctive elements. [Roman roads](/wiki/Roman_roads 'Roman roads') are considered the most advanced built until the early 19th century. The system of roadways facilitated military policing, communications, and trade, and were resistant to floods and other environmental hazards. Some remained usable for over a thousand years. 94 | 95 | [Roman bridges](/wiki/Roman_bridges 'Roman bridges') were among the first large and lasting bridges, built from stone (and in most cases concrete) with the arch as the basic structure. The largest Roman bridge was [Trajan\'s bridge](/wiki/Trajan%27s_bridge "Trajan's bridge") over the lower Danube, constructed by [Apollodorus of Damascus](/wiki/Apollodorus_of_Damascus 'Apollodorus of Damascus'), which remained for over a millennium the longest bridge to have been built. The Romans built many [dams and reservoirs](/wiki/List_of_Roman_dams_and_reservoirs 'List of Roman dams and reservoirs') for water collection, such as the [Subiaco Dams](/wiki/Subiaco_Dams 'Subiaco Dams'), two of which fed the [Anio Novus](/wiki/Anio_Novus 'Anio Novus'), one of the largest aqueducts of Rome. 96 | 97 | The Romans constructed numerous [aqueducts](/wiki/Roman_aqueduct 'Roman aqueduct'). _[De aquaeductu](/wiki/De_aquaeductu 'De aquaeductu')_, a treatise by [Frontinus](/wiki/Frontinus 'Frontinus'), who served as [water commissioner](/wiki/Curator_Aquarum 'Curator Aquarum'), reflects the administrative importance placed on the water supply. Masonry channels carried water along a precise [gradient]( 'Grade (slope)'), using [gravity](/wiki/Gravity 'Gravity') alone. It was then collected in tanks and fed through pipes to public fountains, baths, [toilets](/wiki/Sanitation_in_ancient_Rome 'Sanitation in ancient Rome'), or industrial sites. The main aqueducts in Rome were the [Aqua Claudia](/wiki/Aqua_Claudia 'Aqua Claudia') and the [Aqua Marcia](/wiki/Aqua_Marcia 'Aqua Marcia'). The complex system built to supply Constantinople had its most distant supply drawn from over 120 km away along a route of more than 336 km. Roman aqueducts were built to remarkably fine [tolerance](/wiki/Engineering_tolerance 'Engineering tolerance'), and to a technological standard not equalled until modern times. The Romans also used aqueducts in their extensive mining operations across the empire. 98 | 99 | [Insulated glazing](/wiki/Insulated_glazing 'Insulated glazing') (or \"double glazing\") was used in the construction of [public baths](/wiki/Thermae 'Thermae'). Elite housing in cooler climates might have [hypocausts](/wiki/Hypocaust 'Hypocaust'), a form of central heating. The Romans were the first culture to assemble all essential components of the much later [steam engine](/wiki/Steam_engine 'Steam engine'): the crank and connecting rod system, [Hero](/wiki/Hero_of_Alexandria 'Hero of Alexandria')\'s [aeolipile](/wiki/Aeolipile 'Aeolipile') (generating steam power), the [cylinder](/wiki/Pneumatic_cylinder 'Pneumatic cylinder') and [piston](/wiki/Piston 'Piston') (in metal force pumps), non-return [valves](/wiki/Valves 'Valves') (in water pumps), and [gearing](/wiki/Gear_train 'Gear train') (in water mills and clocks). 100 | 101 | ## Daily life 102 | 103 | Main article: [Culture of ancient Rome](/wiki/Culture_of_ancient_Rome 'Culture of ancient Rome') 104 | 105 | ### City and country 106 | 107 | The city was viewed as fostering civilization by being \"properly designed, ordered, and adorned.\" Augustus undertook a vast building programme in Rome, supported public displays of art that expressed imperial ideology, and [reorganized the city](/wiki/14_regions_of_Augustan_Rome '14 regions of Augustan Rome') into neighbourhoods _([vici](/wiki/Vicus 'Vicus'))_ administered at the local level with police and firefighting services. A focus of Augustan monumental architecture was the [Campus Martius](/wiki/Campus_Martius 'Campus Martius'), an open area outside the city centre: the Altar of Augustan Peace ([_[Ara Pacis Augustae](/wiki/Ara_Pacis_Augustae 'Ara Pacis Augustae')_]) was located there, as was [an obelisk](/wiki/Obelisk_of_Montecitorio 'Obelisk of Montecitorio') imported from Egypt that formed the pointer _([gnomon](/wiki/Gnomon 'Gnomon'))_ of a [horologium](/wiki/Solarium_Augusti 'Solarium Augusti'). With its public gardens, the Campus was among the most attractive places in Rome to visit. 108 | 109 | City planning and urban lifestyles was influenced by the Greeks early on, and in the Eastern Empire, Roman rule shaped the development of cities that already had a strong Hellenistic character. Cities such as [Athens](/wiki/Ancient_Athens 'Ancient Athens'), [Aphrodisias](/wiki/Aphrodisias 'Aphrodisias'), [Ephesus](/wiki/Ephesus 'Ephesus') and [Gerasa](/wiki/Gerasa 'Gerasa') tailored city planning and architecture to imperial ideals, while expressing their individual identity and regional preeminence. In areas inhabited by Celtic-speaking peoples, Rome encouraged the development of urban centres with stone temples, forums, monumental fountains, and amphitheatres, often on or near the sites of preexisting walled settlements known as _[oppida](/wiki/Oppidum 'Oppidum')_. Urbanization in Roman Africa expanded on Greek and Punic coastal cities. 110 | 111 | The network of cities (_[coloniae]( 'Colonia (Roman)')_, _[municipia](/wiki/Municipium 'Municipium')_, _[civitates](/wiki/Civitas 'Civitas')_ or in Greek terms _[poleis](/wiki/Polis 'Polis')\_) was a primary cohesive force during the Pax Romana. Romans of the 1st and 2nd centuries were encouraged to \"inculcate the habits of peacetime\". As the classicist [Clifford Ando](/wiki/Clifford_Ando 'Clifford Ando') noted: 112 | 113 | > Most of the cultural [appurtenances](https://en.wiktionary.org/wiki/appurtenance 'wikt:appurtenance') popularly associated with imperial culture---[public cult](/wiki/Religion_in_ancient_Rome 'Religion in ancient Rome') and its [games](/wiki/Ludi 'Ludi') and [civic banquets](/wiki/Epulones 'Epulones'), competitions for artists, speakers, and athletes, as well as the funding of the great majority of public buildings and public display of art---were financed by private individuals, whose expenditures in this regard helped to justify their economic power and legal and provincial privileges. 114 | 115 | In the city of Rome, most people lived in multistory apartment buildings _([insulae]( 'Insula (building)'))_ that were often squalid firetraps. Public facilities---such as baths _([thermae](/wiki/Thermae 'Thermae'))_, toilets with running water _(latrinae)_, basins or elaborate fountains _([nymphea](/wiki/Nymphaeum 'Nymphaeum'))\_ delivering fresh water, and large-scale entertainments such as [chariot races](/wiki/Chariot_races 'Chariot races') and [gladiator combat](/wiki/Gladiator 'Gladiator')---were aimed primarily at the common people. Similar facilities were constructed in cities throughout the Empire, and some of the best-preserved Roman structures are in Spain, southern France, and northern Africa. 116 | 117 | The public baths served hygienic, social and cultural functions. Bathing was the focus of daily socializing. Roman baths were distinguished by a series of rooms that offered communal bathing in three temperatures, with amenities that might include an [exercise room](/wiki/Palaestra 'Palaestra'), [sauna](/wiki/Sudatorium 'Sudatorium'), [exfoliation]( 'Exfoliation (cosmetology)') spa, [ball court](/wiki/Sphaeristerium 'Sphaeristerium'), or outdoor swimming pool. Baths had [hypocaust](/wiki/Hypocaust 'Hypocaust') heating: the floors were suspended over hot-air channels. Public baths were part of urban culture [throughout the provinces](/wiki/List_of_Roman_public_baths 'List of Roman public baths'), but in the late 4th century, individual tubs began to replace communal bathing. Christians were advised to go to the baths only for hygiene. 118 | 119 | Rich families from Rome usually had two or more houses: a townhouse _([domus](/wiki/Domus 'Domus')_) and at least one luxury home _([villa](/wiki/Roman_villa 'Roman villa'))_ outside the city. The _domus_ was a privately owned single-family house, and might be furnished with a private bath _(balneum)_ but it was not a place to retreat from public life. Although some neighbourhoods show a higher concentration of such houses, they were not segregated enclaves. The _domus_ was meant to be visible and accessible. The atrium served as a reception hall in which the _[paterfamilias](/wiki/Paterfamilias 'Paterfamilias')_ (head of household) met with [clients](/wiki/Patronage_in_ancient_Rome 'Patronage in ancient Rome') every morning. It was a centre of family religious rites, containing a [shrine](/wiki/Lararium 'Lararium') and [images of family ancestors](/wiki/Roman_funerals_and_burial#Funerary_art 'Roman funerals and burial'). The houses were located on busy public roads, and ground-level spaces were often rented out as shops _([tabernae](/wiki/Taberna 'Taberna'))_. In addition to a kitchen garden---windowboxes might substitute in the _insulae_---townhouses typically enclosed a [peristyle](/wiki/Peristyle 'Peristyle') garden. 120 | 121 | The villa by contrast was an escape from the city, and in literature represents a lifestyle that balances intellectual and artistic interests _([otium](/wiki/Otium 'Otium'))_ with an appreciation of nature and agriculture. Ideally a villa commanded a view or vista, carefully framed by the architectural design. It might be located on a working estate, or in a \"resort town\" on the seacoast. 122 | 123 | Augustus\' programme of urban renewal, and the growth of Rome\'s population to as many as one million, was accompanied by nostalgia for rural life. Poetry idealized the lives of farmers and shepherds. Interior decorating often featured painted gardens, fountains, landscapes, vegetative ornament, and animals, rendered accurately enough to be identified by species. On a more practical level, the central government took an active interest in supporting [agriculture](/wiki/Agriculture_in_ancient_Rome 'Agriculture in ancient Rome'). Producing food was the priority of land use. Larger farms _([latifundia](/wiki/Latifundium 'Latifundium'))_ achieved an [economy of scale](/wiki/Economy_of_scale 'Economy of scale') that sustained urban life. Small farmers benefited from the development of local markets in towns and trade centres. Agricultural techniques such as [crop rotation](/wiki/Crop_rotation 'Crop rotation') and [selective breeding](/wiki/Selective_breeding 'Selective breeding') were disseminated throughout the Empire, and new crops were introduced from one province to another. 124 | 125 | Maintaining an affordable food supply to the city of Rome had become a major political issue in the late Republic, when the state began to provide a grain dole ([Cura Annonae](/wiki/Cura_Annonae 'Cura Annonae')) to citizens who registered for it (about 200,000--250,000 adult males in Rome). The dole cost at least 15% of state revenues, but improved living conditions among the lower classes, and subsidized the rich by allowing workers to spend more of their earnings on the wine and olive oil produced on estates. The grain dole also had symbolic value: it affirmed the emperor\'s position as universal benefactor, and the right of citizens to share in \"the fruits of conquest\". The _annona_, public facilities, and spectacular entertainments mitigated the otherwise dreary living conditions of lower-class Romans, and kept social unrest in check. The satirist [Juvenal](/wiki/Juvenal 'Juvenal'), however, saw \"[bread and circuses](/wiki/Bread_and_circuses 'Bread and circuses')\" _(panem et circenses)_ as emblematic of the loss of republican political liberty: 126 | 127 | > The public has long since cast off its cares: the people that once bestowed commands, consulships, legions and all else, now meddles no more and longs eagerly for just two things: bread and circuses. 128 | 129 | ### Health and disease 130 | 131 | Further information: [Disease in Imperial Rome](/wiki/Disease_in_Imperial_Rome 'Disease in Imperial Rome'), [Antonine plague](/wiki/Antonine_plague 'Antonine plague'), and [Plague of Cyprian](/wiki/Plague_of_Cyprian 'Plague of Cyprian') 132 | 133 | [Epidemics](/wiki/Epidemics 'Epidemics') were common in the ancient world, and occasional [pandemics](/wiki/Pandemic 'Pandemic') in the Empire killed millions. The Roman population was unhealthy. About 20 percent---a large percentage by ancient standards---lived in cities, Rome being the largest. The cities were a \"demographic sink\": the death rate exceeded the birth rate and constant immigration was necessary to maintain the population. Average lifespan is estimated at the mid-twenties, and perhaps more than half of children died before reaching adulthood. Dense urban populations and [poor sanitation](/wiki/Sanitation_in_ancient_Rome 'Sanitation in ancient Rome') contributed to disease. Land and sea connections facilitated and sped the transfer of infectious diseases across the empire\'s territories. The rich were not immune; only two of emperor Marcus Aurelius\'s fourteen children are known to have reached adulthood. 134 | 135 | The importance of a good diet to health was recognized by medical writers such as [Galen](/wiki/Galen 'Galen') (2nd century). Views on nutrition were influenced by beliefs like [humoral theory](/wiki/Humoral_theory 'Humoral theory'). A good indicator of nutrition and disease burden is average height: the average Roman was shorter in stature than the population of pre-Roman Italian societies and medieval Europe. 136 | 137 | ### Food and dining 138 | 139 | Main article: [Food and dining in the Roman Empire](/wiki/Food_and_dining_in_the_Roman_Empire 'Food and dining in the Roman Empire') 140 | 141 | See also: [Ancient Roman cuisine](/wiki/Ancient_Roman_cuisine 'Ancient Roman cuisine') and [Ancient Rome and wine](/wiki/Ancient_Rome_and_wine 'Ancient Rome and wine') 142 | 143 | Most apartments in Rome lacked kitchens, though a charcoal [brazier](/wiki/Brazier 'Brazier') could be used for rudimentary cookery. Prepared food was sold at pubs and bars, inns, and food stalls _([tabernae](/wiki/Taberna 'Taberna'), cauponae, [popinae](/wiki/Popina 'Popina'), [thermopolia](/wiki/Thermopolium 'Thermopolium'))_. [Carryout](/wiki/Carryout 'Carryout') and restaurants were for the lower classes; [fine dining](/wiki/Fine_dining 'Fine dining') appeared only at dinner parties in wealthy homes with a [chef](/wiki/Chef 'Chef') _(archimagirus)_ and kitchen staff, or banquets hosted by social clubs _([collegia]( 'Collegium (ancient Rome)'))*. 144 | 145 | Most Romans consumed at least 70% of their daily [calories](/wiki/Calorie 'Calorie') in the form of cereals and [legumes](/wiki/Legumes 'Legumes'). _[Puls]( 'Puls (food)')_ (pottage) was considered the food of the Romans, and could be elaborated to produce dishes similar to [polenta](/wiki/Polenta 'Polenta') or [risotto](/wiki/Risotto 'Risotto'). Urban populations and the military preferred bread. By the reign of [Aurelian](/wiki/Aurelian 'Aurelian'), the state had begun to distribute the \_annona_ as a daily ration of bread baked in state factories, and added [olive oil](/wiki/Olive_oil 'Olive oil'), wine, and pork to the dole. 146 | 147 | Roman literature focuses on the dining habits of the upper classes, for whom the evening meal _([cena](/wiki/Cena 'Cena'))_ had important social functions. Guests were entertained in a finely decorated dining room _([triclinium](/wiki/Triclinium 'Triclinium'))_ furnished with couches. By the late Republic, women dined, reclined, and drank wine along with men. The poet Martial describes a dinner, beginning with the _gustatio_ (\"tasting\" or \"appetizer\") salad. The main course was [kid](/wiki/Goat_meat 'Goat meat'), beans, greens, a chicken, and leftover ham, followed by a dessert of fruit and wine. Roman \"[foodies](/wiki/Foodie 'Foodie')\" indulged in [wild game](/wiki/Wild_game 'Wild game'), [fowl](/wiki/Fowl 'Fowl') such as [peacock](/wiki/Peacock 'Peacock') and [flamingo](/wiki/Flamingo 'Flamingo'), large fish ([mullet]( 'Mullet (fish)') was especially prized), and [shellfish](/wiki/Shellfish 'Shellfish'). Luxury ingredients were imported from the far reaches of empire. A book-length collection of Roman recipes is attributed to [Apicius](/wiki/Apicius 'Apicius'), a name for several figures in antiquity that became synonymous with \"[gourmet](/wiki/Gourmet 'Gourmet').\" 148 | 149 | Refined cuisine could be moralized as a sign of either civilized progress or decadent decline. Most often, because of the importance of landowning in Roman culture, produce---cereals, legumes, vegetables, and fruit---were considered more civilized foods than meat. The [Mediterranean staples](/wiki/Mediterranean_diet 'Mediterranean diet') of [bread](/wiki/Sacramental_bread 'Sacramental bread'), [wine](/wiki/Sacramental_wine 'Sacramental wine'), and [oil](/wiki/Chrism 'Chrism') were [sacralized](/wiki/Sanctification 'Sanctification') by Roman Christianity, while Germanic meat consumption became a mark of [paganism](/wiki/Germanic_paganism 'Germanic paganism'). Some philosophers and Christians resisted the demands of the body and the pleasures of food, and adopted [fasting](/wiki/Fasting 'Fasting') as an ideal. Food became simpler in general as urban life in the West diminished and trade routes were disrupted; the Church formally discouraged [gluttony](/wiki/Gluttony 'Gluttony'), and hunting and [pastoralism](/wiki/Pastoralism 'Pastoralism') were seen as simple and virtuous. 150 | 151 | ### Spectacles 152 | 153 | See also: [Ludi](/wiki/Ludi 'Ludi'), [Chariot racing](/wiki/Chariot_racing 'Chariot racing'), and [Recitationes](/wiki/Recitationes 'Recitationes') 154 | 155 | When [Juvenal](/wiki/Juvenal 'Juvenal') complained that the Roman people had exchanged their political liberty for \"bread and circuses\", he was referring to the state-provided grain dole and the _circenses_, events held in the entertainment venue called a _[circus]( 'Circus (building)')_. The largest such venue in Rome was the [Circus Maximus](/wiki/Circus_Maximus 'Circus Maximus'), the setting of [horse races](/wiki/Horse_racing 'Horse racing'), [chariot races](/wiki/Chariot_races 'Chariot races'), the equestrian [Troy Game](/wiki/Lusus_Troiae 'Lusus Troiae'), staged beast hunts _([venationes](/wiki/Venatio 'Venatio'))_, athletic contests, [gladiator combat](/wiki/Gladiator 'Gladiator'), and [historical re-enactments](/wiki/Historical_re-enactment 'Historical re-enactment'). From earliest times, several [religious festivals](/wiki/Roman_festivals 'Roman festivals') had featured games _([ludi](/wiki/Ludi 'Ludi'))_, primarily horse and chariot races _(ludi circenses)\_. The races retained religious significance in connection with agriculture, [initiation](/wiki/Initiation_ritual 'Initiation ritual'), and the cycle of birth and death. 156 | 157 | Under Augustus, public entertainments were presented on 77 days of the year; by the reign of Marcus Aurelius, this had expanded to 135. Circus games were preceded by an elaborate parade (_[pompa circensis](/wiki/Pompa_circensis 'Pompa circensis')_) that ended at the venue. Competitive events were held also in smaller venues such as the [amphitheatre](/wiki/Roman_amphitheater 'Roman amphitheater'), which became the characteristic Roman spectacle venue, and stadium. Greek-style athletics included [footraces]( 'Stadion (running race)'), [boxing](/wiki/Ancient_Greek_boxing 'Ancient Greek boxing'), [wrestling](/wiki/Greek_wrestling 'Greek wrestling'), and the [pancratium](/wiki/Pankration 'Pankration'). Aquatic displays, such as the mock sea battle _([naumachia](/wiki/Naumachia 'Naumachia'))_ and a form of \"water ballet\", were presented in engineered pools. State-supported [theatrical events](#Performing_arts) _([ludi scaenici](/wiki/Ludi_scaenici 'Ludi scaenici'))_ took place on temple steps or in grand stone theatres, or in the smaller enclosed theatre called an _[odeon]( 'Odeon (building)')\_. 158 | 159 | Circuses were the largest structure regularly built in the Roman world. The Flavian Amphitheatre, better known as the [Colosseum](/wiki/Colosseum 'Colosseum'), became the regular arena for blood sports in Rome. Many [Roman amphitheatres](/wiki/List_of_Roman_amphitheatres 'List of Roman amphitheatres'), [circuses]( 'Circus (building)') and [theatres]( 'Roman theatre (structure)') built in cities outside Italy are visible as ruins today. The local ruling elite were responsible for sponsoring spectacles and arena events, which both enhanced their status and drained their resources. The physical arrangement of the amphitheatre represented the order of Roman society: the emperor in his opulent box; senators and equestrians in reserved advantageous seats; women seated at a remove from the action; slaves given the worst places, and everybody else in-between. The crowd could call for an outcome by booing or cheering, but the emperor had the final say. Spectacles could quickly become sites of social and political protest, and emperors sometimes had to deploy force to put down crowd unrest, most notoriously at the [Nika riots](/wiki/Nika_riots 'Nika riots') in 532. 160 | 161 | The chariot teams were known by the [colours they wore](/wiki/Chariot_racing#Factions 'Chariot racing'). Fan loyalty was fierce and at times erupted into [sports riots](/wiki/Sports_riots 'Sports riots'). Racing was perilous, but charioteers were among the most celebrated and well-compensated athletes. Circuses were designed to ensure that no team had an unfair advantage and to minimize collisions (_naufragia_), which were nonetheless frequent and satisfying to the crowd. The races retained a magical aura through their early association with [chthonic](/wiki/Chthonic 'Chthonic') rituals: circus images were considered protective or lucky, [curse tablets](/wiki/Curse_tablet 'Curse tablet') have been found buried at the site of racetracks, and charioteers were often suspected of sorcery. Chariot racing continued into the Byzantine period under imperial sponsorship, but the decline of cities in the 6th and 7th centuries led to its eventual demise. 162 | 163 | The Romans thought gladiator contests had originated with [funeral games]( 'Funeral games (antiquity)') and [sacrifices](/wiki/Sacrifice_in_ancient_Roman_religion 'Sacrifice in ancient Roman religion'). Some of the earliest [styles of gladiator fighting](/wiki/List_of_Roman_gladiator_types 'List of Roman gladiator types') had ethnic designations such as \"[Thracian](/wiki/Thraex 'Thraex')\" or \"Gallic\". The staged combats were considered _munera_, \"services, offerings, benefactions\", initially distinct from the festival games _(ludi)_. To mark the opening of the Colosseum, [Titus](/wiki/Titus 'Titus') presented [100 days of arena events](/wiki/Inaugural_games_of_the_Flavian_Amphitheatre 'Inaugural games of the Flavian Amphitheatre'), with 3,000 gladiators competing on a single day. Roman fascination with gladiators is indicated by how widely they are depicted on mosaics, wall paintings, lamps, and in graffiti. Gladiators were trained combatants who might be slaves, convicts, or free volunteers. Death was not a necessary or even desirable outcome in matches between these highly skilled fighters, whose training was costly and time-consuming. By contrast, _noxii_ were convicts sentenced to the arena with little or no training, often unarmed, and with no expectation of survival; physical suffering and humiliation were considered appropriate [retributive justice](/wiki/Retributive_justice 'Retributive justice'). These executions were sometimes staged or ritualized as re-enactments of [myths](/wiki/Greek_mythology 'Greek mythology'), and amphitheatres were equipped with elaborate [stage machinery](/wiki/Stagecraft 'Stagecraft') to create special effects. 164 | 165 | Modern scholars have found the pleasure Romans took in the \"theatre of life and death\" difficult to understand. [Pliny the Younger](/wiki/Pliny_the_Younger 'Pliny the Younger') rationalized gladiator spectacles as good for the people, \"to inspire them to face honourable wounds and despise death, by exhibiting love of glory and desire for victory\". Some Romans such as [Seneca](/wiki/Seneca_the_Younger 'Seneca the Younger') were critical of the brutal spectacles, but found virtue in the courage and dignity of the defeated fighter---an attitude that finds its fullest expression with the [Christians martyred](/wiki/Christian_martyr 'Christian martyr') in the arena. Tertullian considered deaths in the arena to be nothing more than a dressed-up form of [human sacrifice](/wiki/Human_sacrifice 'Human sacrifice'). Even [martyr literature](/wiki/Acts_of_the_martyrs 'Acts of the martyrs'), however, offers \"detailed, indeed luxuriant, descriptions of bodily suffering\", and became a popular genre at times indistinguishable from fiction. 166 | -------------------------------------------------------------------------------- /sample-files/roman-empire-3.md: -------------------------------------------------------------------------------- 1 | ### Recreation 2 | 3 | The singular _[ludus]( 'Ludus (ancient Rome)')*, \"play, game, sport, training,\" had a wide range of meanings such as \"word play,\" \"theatrical performance,\" \"board game,\" \"primary school,\" and even \"gladiator training school\" (as in _[Ludus Magnus](/wiki/Ludus_Magnus 'Ludus Magnus')_). Activities for children and young people in the Empire included [hoop rolling](/wiki/Hoop_rolling#Ancient_Rome_and_Byzantium 'Hoop rolling') and [knucklebones](/wiki/Knucklebones 'Knucklebones') (_astragali_ or \"jacks\"). Girls had [dolls](/wiki/Doll 'Doll') made of wood, [terracotta](/wiki/Terracotta 'Terracotta'), and especially [bone and ivory](/wiki/Ivory_carving 'Ivory carving'). Ball games include [trigon]( 'Trigon (game)') and [harpastum](/wiki/Harpastum 'Harpastum'). People of all ages played [board games](/wiki/Board_game 'Board game'), including _[latrunculi](/wiki/Ludus_latrunculorum 'Ludus latrunculorum')_ (\"Raiders\") and _[XII scripta](/wiki/Ludus_duodecim_scriptorum 'Ludus duodecim scriptorum')_ (\"Twelve Marks\"). A game referred to as _alea_ (dice) or _tabula_ (the board) may have been similar to [backgammon](/wiki/Backgammon 'Backgammon'). [Dicing](/wiki/Dice 'Dice') as a form of gambling was disapproved of, but was a popular pastime during the festival of the [Saturnalia](/wiki/Saturnalia 'Saturnalia'). 4 | 5 | After adolescence, most physical training for males was of a military nature. The [Campus Martius](/wiki/Campus_Martius 'Campus Martius') originally was an exercise field where young men learned horsemanship and warfare. Hunting was also considered an appropriate pastime. According to [Plutarch](/wiki/Plutarch 'Plutarch'), conservative Romans disapproved of Greek-style athletics that promoted a fine body for its own sake, and condemned [Nero\'s efforts to encourage Greek-style athletic games](/wiki/Quinquennial_Neronia 'Quinquennial Neronia'). Some women trained as gymnasts and dancers, and a rare few as [female gladiators](/wiki/Gladiatrix 'Gladiatrix'). The \"Bikini Girls\" mosaic shows young women engaging in routines comparable to [rhythmic gymnastics](/wiki/Rhythmic_gymnastics 'Rhythmic gymnastics'). Women were encouraged to maintain health through activities such as playing ball, swimming, walking, or reading aloud (as a breathing exercise). 6 | 7 | ### Clothing 8 | 9 | Main article: [Clothing in ancient Rome](/wiki/Clothing_in_ancient_Rome 'Clothing in ancient Rome') 10 | 11 | Further information: [Roman hairstyles](/wiki/Roman_hairstyles 'Roman hairstyles'), [Roman jewelry](/wiki/Roman_jewelry 'Roman jewelry'), and [Cosmetics in ancient Rome](/wiki/Cosmetics_in_ancient_Rome 'Cosmetics in ancient Rome') 12 | 13 | In a status-conscious society like that of the Romans, clothing and personal adornment indicated the etiquette of interacting with the wearer. Wearing the correct clothing reflected a society in good order. There is little direct evidence of how Romans dressed in daily life, since portraiture may show the subject in clothing with symbolic value, and surviving textiles are rare. 14 | 15 | The [toga](/wiki/Toga 'Toga') was the distinctive national garment of the male citizen, but it was heavy and impractical, worn mainly for conducting political or court business and religious rites. It was a \"vast expanse\" of semi-circular white wool that could not be put on and draped correctly without assistance. The drapery became more intricate and structured over time. The _toga praetexta_, with a [purple or purplish-red](/wiki/Tyrian_purple 'Tyrian purple') stripe representing inviolability, was worn by children who had not come of age, [curule magistrates](/wiki/Executive_magistrates_of_the_Roman_Empire 'Executive magistrates of the Roman Empire'), and state priests. Only the emperor could wear an all-purple toga _(toga picta)_. 16 | 17 | Ordinary clothing was dark or colourful. The basic garment for all Romans, regardless of gender or wealth, was the simple sleeved [tunic](/wiki/Tunic 'Tunic'), with length differing by wearer. The tunics of poor people and labouring slaves were made from coarse wool in natural, dull shades; finer tunics were made of lightweight wool or linen. A man of the senatorial or equestrian order wore a tunic with two purple stripes _(clavi)_ woven vertically: the wider the stripe, the higher the wearer\'s status. Other garments could be layered over the tunic. Common male attire also included cloaks and in some regions [trousers](/wiki/Braccae 'Braccae'). In the 2nd century, emperors and elite men are often portrayed wearing the [pallium]( 'Pallium (Roman cloak)'), an originally Greek mantle; women are also portrayed in the pallium. [Tertullian](/wiki/Tertullian 'Tertullian') considered the pallium an appropriate garment both for Christians, in contrast to the toga, and for educated people. 18 | 19 | Roman clothing styles changed over time. In the [Dominate](/wiki/Dominate 'Dominate'), clothing worn by both soldiers and bureaucrats became highly decorated with geometrical patterns, stylized plant motifs, and in more elaborate examples, human or animal figures. Courtiers of the later Empire wore elaborate silk robes. The militarization of Roman society, and the waning of urban life, affected fashion: heavy military-style belts were worn by bureaucrats as well as soldiers, and the toga was abandoned, replaced by the pallium as a garment embodying social unity. 20 | 21 | ## Arts 22 | 23 | Main articles: [Roman art](/wiki/Roman_art 'Roman art') and [Art collection in ancient Rome](/wiki/Art_collection_in_ancient_Rome 'Art collection in ancient Rome') 24 | 25 | [Greek art](/wiki/Ancient_Greek_art 'Ancient Greek art') had a profound influence on Roman art. [Public art](/wiki/Public_art 'Public art')---including [sculpture](/wiki/Roman_sculpture 'Roman sculpture'), monuments such as [victory columns](/wiki/List_of_Roman_victory_columns 'List of Roman victory columns') or [triumphal arches](/wiki/Triumphal_arch 'Triumphal arch'), and the iconography on [coins](/wiki/Roman_currency 'Roman currency')---is often analysed for historical or ideological significance. In the private sphere, artistic objects were made for [religious dedications](/wiki/Votum 'Votum'), [funerary commemoration](/wiki/Roman_funerals_and_burial 'Roman funerals and burial'), domestic use, and commerce. The wealthy advertised their appreciation of culture through artwork and [decorative arts](/wiki/Decorative_arts 'Decorative arts') in their homes. Despite the value placed on art, even famous artists were of low social status, partly as they worked with their hands. 26 | 27 | ### Portraiture 28 | 29 | Main article: [Roman portraiture](/wiki/Roman_portraiture 'Roman portraiture') 30 | 31 | Portraiture, which survives mainly in sculpture, was the most copious form of imperial art. Portraits during the Augustan period utilize [classical proportions](/wiki/Classicism 'Classicism'), evolving later into a mixture of realism and idealism. Republican portraits were characterized by [verism](/wiki/Verism 'Verism'), but as early as the 2nd century BC, Greek [heroic nudity](/wiki/Heroic_nudity 'Heroic nudity') was adopted for conquering generals. Imperial portrait sculptures may model a mature head atop a youthful nude or semi-nude body with perfect musculature. Clothed in the toga or military regalia, the body communicates rank or role, not individual characteristics. Women of the emperor\'s family were often depicted as goddesses or divine personifications. 32 | 33 | Portraiture in painting is represented primarily by the [Fayum mummy portraits](/wiki/Fayum_mummy_portrait 'Fayum mummy portrait'), which evoke Egyptian and Roman traditions of commemorating the dead with realistic painting. Marble portrait sculpture were painted, but traces have rarely survived. 34 | 35 | ### Sculpture and sarcophagi 36 | 37 | Main articles: [Roman sculpture](/wiki/Roman_sculpture 'Roman sculpture') and [Ancient Roman sarcophagi](/wiki/Ancient_Roman_sarcophagi 'Ancient Roman sarcophagi') 38 | 39 | Examples of Roman sculpture survive abundantly, though often in damaged or fragmentary condition, including freestanding statuary in marble, bronze and [terracotta](/wiki/Ancient_Roman_pottery#Terracotta_figurines 'Ancient Roman pottery'), and [reliefs](/wiki/Relief 'Relief') from public buildings and monuments. Niches in amphitheatres were originally filled with statues, as were [formal gardens](/wiki/Roman_gardens 'Roman gardens'). Temples housed cult images of deities, often by famed sculptors. 40 | 41 | Elaborately carved marble and limestone [sarcophagi](/wiki/Sarcophagus 'Sarcophagus') are characteristic of the 2nd to 4th centuries. Sarcophagus relief has been called the \"richest single source of Roman iconography,\" depicting [mythological scenes](/wiki/Classical_mythology 'Classical mythology') or Jewish/Christian imagery as well as the deceased\'s life. 42 | 43 | ### Painting 44 | 45 | Initial Roman painting drew from [Etruscan](/wiki/Etruscan_art#Wall-painting 'Etruscan art') and [Greek](/wiki/Ancient_Greek_art#Painting 'Ancient Greek art') models and techniques. Examples of Roman paintings can be found in [palaces](/wiki/List_of_ancient_monuments_in_Rome#Palaces 'List of ancient monuments in Rome'), [catacombs](/wiki/List_of_ancient_monuments_in_Rome#Cemeteries 'List of ancient monuments in Rome') and [villas](/wiki/Roman_villa 'Roman villa'). Much of what is known of Roman painting is from the interior decoration of private homes, particularly as preserved by the [eruption of Vesuvius](/wiki/Eruption_of_Mount_Vesuvius_in_AD_79 'Eruption of Mount Vesuvius in AD 79'). In addition to decorative borders and panels with geometric or vegetative motifs, wall painting depicts scenes from mythology and theatre, landscapes and gardens, [spectacles](#Spectacles), everyday life, and [erotic art](/wiki/Erotic_art_in_Pompeii_and_Herculaneum 'Erotic art in Pompeii and Herculaneum'). 46 | 47 | ### Mosaic 48 | 49 | Main article: [Roman mosaic](/wiki/Roman_mosaic 'Roman mosaic') 50 | 51 | [Mosaics](/wiki/Mosaic 'Mosaic') are among the most enduring of Roman [decorative arts](/wiki/Decorative_arts 'Decorative arts'), and are found on floors and other architectural features. The most common is the [tessellated mosaic](/wiki/Opus_tessellatum 'Opus tessellatum'), formed from uniform pieces _([tesserae](/wiki/Tessera 'Tessera'))_ of materials such as stone and glass. _[Opus sectile](/wiki/Opus_sectile 'Opus sectile')_ is a related technique in which flat stone, usually coloured marble, is cut precisely into shapes from which geometric or figurative patterns are formed. This more difficult technique became especially popular for luxury surfaces in the 4th century (eg the [Basilica of Junius Bassus](/wiki/Basilica_of_Junius_Bassus 'Basilica of Junius Bassus')). 52 | 53 | [Figurative](/wiki/Figurative_art 'Figurative art') mosaics share many themes with painting, and in some cases use almost identical [compositions]( 'Composition (visual arts)'). Geometric patterns and mythological scenes occur throughout the Empire. In North Africa, a particularly rich source of mosaics, homeowners often chose scenes of life on their estates, hunting, agriculture, and local wildlife. Plentiful and major examples of Roman mosaics come also from present-day Turkey (particularly the ([Antioch mosaics](/wiki/Antioch_mosaics 'Antioch mosaics')), Italy, southern France, Spain, and Portugal. 54 | 55 | ### Decorative arts 56 | 57 | Further information: [Ancient Roman pottery](/wiki/Ancient_Roman_pottery 'Ancient Roman pottery') and [Roman glass](/wiki/Roman_glass 'Roman glass') 58 | 59 | [Decorative arts](/wiki/Decorative_arts 'Decorative arts') for luxury consumers included fine pottery, silver and bronze vessels and implements, and glassware. Pottery manufacturing was economically important, as were the glass and metalworking industries. Imports stimulated new regional centres of production. Southern Gaul became a leading producer of the finer red-gloss pottery _([terra sigillata](/wiki/Terra_sigillata 'Terra sigillata'))_ that was a major trade good in 1st-century Europe. [Glassblowing](/wiki/Glassblowing 'Glassblowing') was regarded by the Romans as originating in Syria in the 1st century BC, and by the 3rd century, Egypt and the [Rhineland](/wiki/Rhineland 'Rhineland') had become noted for fine glass. 60 | 61 | ### Performing arts 62 | 63 | Main articles: [Theatre of ancient Rome](/wiki/Theatre_of_ancient_Rome 'Theatre of ancient Rome') and [Music of ancient Rome](/wiki/Music_of_ancient_Rome 'Music of ancient Rome') 64 | 65 | In Roman tradition, borrowed from the Greeks, literary theatre was performed by all-male troupes that used face masks with exaggerated facial expressions to portray emotion. Female roles were played by men in [drag]( 'Drag (clothing)') (_[travesti]( 'Travesti (theatre)')\_). Roman literary theatre tradition is particularly well represented in [Latin literature](#Literature) by the tragedies of [Seneca](/wiki/Seneca_the_Younger 'Seneca the Younger'). 66 | 67 | More popular than literary theatre was the genre-defying _mimus_ theatre, which featured scripted scenarios with free improvisation, risqué language and sex scenes, action sequences, and political satire, along with dance, juggling, acrobatics, tightrope walking, striptease, and [dancing bears](/wiki/Dancing_bear 'Dancing bear'). Unlike literary theatre, _mimus_ was played without masks, and encouraged stylistic realism. Female roles were performed by women. _Mimus_ was related to _[pantomimus](/wiki/Pantomime#Ancient_Rome 'Pantomime')_, an early form of [story ballet](/wiki/Story_ballet 'Story ballet') that contained no spoken dialogue but rather a sung [libretto](/wiki/Libretto 'Libretto'), often mythological, either tragic or comic. 68 | 69 | Although sometimes regarded as foreign, [music](/wiki/Music_of_ancient_Rome 'Music of ancient Rome') and dance existed in Rome from earliest times. Music was customary at funerals, and the _[tibia](/wiki/Aulos 'Aulos')_, a woodwind instrument, was played at sacrifices. Song _([carmen]( 'Carmen (verse)'))_ was integral to almost every social occasion. Music was thought to reflect the orderliness of the cosmos. Various woodwinds and [\"brass\" instruments](/wiki/Brass_instrument 'Brass instrument') were played, as were [stringed instruments](/wiki/Stringed_instruments 'Stringed instruments') such as the _[cithara](/wiki/Cithara 'Cithara')_, and percussion. The _[cornu]( 'Cornu (horn)')_, a long tubular metal wind instrument, was used for military signals and on parade. These instruments spread throughout the provinces and are widely depicted in Roman art. The hydraulic pipe organ _([hydraulis](/wiki/Hydraulis 'Hydraulis'))_ was \"one of the most significant technical and musical achievements of antiquity\", and accompanied gladiator games and events in the amphitheatre. Although certain dances were seen at times as non-Roman or unmanly, dancing was embedded in religious rituals of archaic Rome. Ecstatic dancing was a feature of the [mystery religions](/wiki/Mystery_religions 'Mystery religions'), particularly the cults of [Cybele](/wiki/Cybele 'Cybele') and [Isis](/wiki/Isis 'Isis'). In the secular realm, dancing girls from [Syria]( 'Syria (Roman province)') and [Cadiz](/wiki/C%C3%A1diz 'Cádiz') were extremely popular. 70 | 71 | Like [gladiators](/wiki/Gladiator 'Gladiator'), entertainers were legally _[infames](/wiki/Infamia 'Infamia')_, technically free but little better than slaves. \"Stars\", however, could enjoy considerable wealth and celebrity, and mingled socially and often sexually with the elite. Performers supported each other by forming guilds, and several memorials for theatre members survive. Theatre and dance were often condemned by [Christian polemicists](/wiki/Christian_polemic 'Christian polemic') in the later Empire. 72 | 73 | Estimates of the average [literacy rate](/wiki/Literacy_rate 'Literacy rate') range from 5 to over 30%. The Roman obsession with documents and inscriptions indicates the value placed on the written word. Laws and edicts were posted as well as read out. Illiterate Roman subjects could have a government scribe _([scriba]( 'Scriba (ancient Rome)'))* read or write their official documents for them. The military produced extensive written records. The [Babylonian Talmud](/wiki/Babylonian_Talmud 'Babylonian Talmud') declared \"if all seas were ink, all reeds were pen, all skies parchment, and all men scribes, they would be unable to set down the full scope of the Roman government\'s concerns.\" 74 | 75 | [Numeracy](/wiki/Numeracy 'Numeracy') was necessary for commerce. Slaves were numerate and literate in significant numbers; some were highly educated. Graffiti and low-quality inscriptions with misspellings and [solecisms](/wiki/Solecism 'Solecism') indicate casual literacy among non-elites. 76 | 77 | The Romans had an extensive [priestly archive](/wiki/Glossary_of_ancient_Roman_religion#libri_pontificales 'Glossary of ancient Roman religion'), and inscriptions appear throughout the Empire in connection with [votives](/wiki/Votum 'Votum') dedicated by ordinary people, as well as \"[magic spells](/wiki/Magic_in_the_Greco-Roman_world 'Magic in the Greco-Roman world')\" (eg the [Greek Magical Papyri](/wiki/Greek_Magical_Papyri 'Greek Magical Papyri')). 78 | 79 | Books were expensive, since each copy had to be written out on a papyrus roll _(volumen)_ by scribes. The [codex](/wiki/Codex 'Codex')---pages bound to a spine---was still a novelty in the 1st century, but by the end of the 3rd century was replacing the _volumen_. Commercial book production was established by the late Republic, and by the 1st century certain neighbourhoods of Rome and Western provincial cities were known for their bookshops. The quality of editing varied wildly, and [plagiarism](/wiki/Plagiarism 'Plagiarism') or [forgery](/wiki/Literary_forgery 'Literary forgery') were common, since there was no [copyright law](/wiki/Copyright_law 'Copyright law'). 80 | 81 | Collectors amassed personal libraries, and a fine library was part of the cultivated leisure _([otium](/wiki/Otium 'Otium'))_ associated with the villa lifestyle. Significant collections might attract \"in-house\" scholars, and an individual benefactor might endow a community with a library (as [Pliny the Younger](/wiki/Pliny_the_Younger 'Pliny the Younger') did in [Comum](/wiki/Comum 'Comum')). Imperial libraries were open to users on a limited basis, and represented a [literary canon](/wiki/Literary_canon 'Literary canon'). Books considered subversive might be publicly burned, and [Domitian](/wiki/Domitian 'Domitian') crucified copyists for reproducing works deemed treasonous. 82 | 83 | Literary texts were often shared aloud at meals or with reading groups. Public readings (_[recitationes](/wiki/Recitationes 'Recitationes')_) expanded from the 1st through the 3rd century, giving rise to \"consumer literature\" for entertainment. Illustrated books, including erotica, were popular, but are poorly represented by extant fragments. 84 | 85 | Literacy began to decline during the [Crisis of the Third Century](/wiki/Crisis_of_the_Third_Century 'Crisis of the Third Century'). The emperor Julian banned Christians from teaching the classical curriculum, but the [Church Fathers](/wiki/Church_Fathers 'Church Fathers') and other Christians adopted Latin and Greek literature, philosophy and science in biblical interpretation. As the Western Roman Empire declined, reading became rarer even for those within the Church hierarchy, although it continued in the [Byzantine Empire](/wiki/Byzantine_Empire 'Byzantine Empire'). 86 | 87 | ### Education 88 | 89 | Main article: [Education in ancient Rome](/wiki/Education_in_ancient_Rome 'Education in ancient Rome') 90 | 91 | Traditional Roman education was moral and practical. Stories were meant to instil Roman values _([mores maiorum](/wiki/Mos_maiorum 'Mos maiorum'))_. Parents were expected to act as role models, and working parents passed their skills to their children, who might also enter apprenticeships. Young children were attended by a [pedagogue]( 'Paedagogus (occupation)'), usually a Greek slave or former slave, who kept the child safe, taught self-discipline and public behaviour, attended class and helped with tutoring. 92 | 93 | Formal education was available only to families who could pay for it; lack of state support contributed to low literacy. Primary education in reading, writing, and arithmetic might take place at home if parents hired or bought a teacher. Other children attended \"public\" schools organized by a schoolmaster _([ludimagister](/wiki/Ludi_magister 'Ludi magister'))_ paid by parents. _Vernae_ (homeborn slave children) might share in-home or public schooling. Boys and girls received primary education generally from ages 7 to 12, but classes were not segregated by grade or age. Most schools employed [corporal punishment](/wiki/Corporal_punishment 'Corporal punishment'). For the socially ambitious, education in Greek as well as Latin was necessary. Schools became more numerous during the Empire, increasing educational opportunities. 94 | 95 | At the age of 14, upperclass males made their [rite of passage](/wiki/Sexuality_in_ancient_Rome#Rites_of_passage 'Sexuality in ancient Rome') into adulthood, and began to learn leadership roles through mentoring from a senior family member or family friend. Higher education was provided by _[grammatici]( 'Grammarian (Greco-Roman)')_ or _[rhetores](/wiki/Rhetor 'Rhetor')_. The \_grammaticus_ or \"grammarian\" taught mainly Greek and Latin literature, with history, geography, philosophy or mathematics treated as explications of the text. With the rise of Augustus, contemporary Latin authors such as Virgil and Livy also became part of the curriculum. The _rhetor_ was a teacher of oratory or public speaking. The art of speaking _(ars dicendi)_ was highly prized, and _eloquentia_ (\"speaking ability, eloquence\") was considered the \"glue\" of civilized society. Rhetoric was not so much a body of knowledge (though it required a command of the [literary canon](/wiki/Literary_canon 'Literary canon')) as it was a mode of expression that distinguished those who held social power. The ancient model of rhetorical training---\"restraint, coolness under pressure, modesty, and good humour\"---endured into the 18th century as a Western educational ideal. 96 | 97 | In Latin, _illiteratus_ could mean both \"unable to read and write\" and \"lacking in cultural awareness or sophistication.\" Higher education promoted career advancement. Urban elites throughout the Empire shared a literary culture imbued with Greek educational ideals _([paideia](/wiki/Paideia 'Paideia'))_. Hellenistic cities sponsored schools of higher learning to express cultural achievement. Young Roman men often went abroad to study rhetoric and philosophy, mostly to Athens. The curriculum in the East was more likely to include music and physical training. On the Hellenistic model, Vespasian [endowed chairs](/wiki/Endowed_chair 'Endowed chair') of grammar, Latin and Greek rhetoric, and philosophy at Rome, and gave secondary teachers special exemptions from taxes and legal penalties. In the Eastern Empire, [Berytus](/wiki/Berytus 'Berytus') (present-day [Beirut](/wiki/Beirut 'Beirut')) was unusual in offering a Latin education, and became famous for its [school of Roman law](/wiki/Law_School_of_Beirut 'Law School of Beirut'). The cultural movement known as the [Second Sophistic](/wiki/Second_Sophistic 'Second Sophistic') (1st--3rd century AD) promoted the assimilation of Greek and Roman social, educational, and esthetic values. 98 | 99 | Literate women ranged from cultured aristocrats to girls trained to be [calligraphers](/wiki/Calligrapher 'Calligrapher') and [scribes](/wiki/Scribe 'Scribe'). The ideal woman in Augustan love poetry was educated and well-versed in the arts. Education seems to have been standard for daughters of the senatorial and equestrian orders. An educated wife was an asset for the socially ambitious household. 100 | 101 | ### Literature 102 | 103 | Main article: [Latin literature](/wiki/Latin_literature 'Latin literature') 104 | 105 | See also: [Latin poetry](/wiki/Latin_poetry 'Latin poetry') 106 | 107 | [Literature under Augustus]( 'Augustan literature (ancient Rome)'), along with that of the Republic, has been viewed as the \"Golden Age\" of Latin literature, embodying [classical ideals](/wiki/Classicism 'Classicism'). The three most influential Classical Latin poets---[Virgil](/wiki/Virgil 'Virgil'), [Horace](/wiki/Horace 'Horace'), and [Ovid](/wiki/Ovid 'Ovid')---belong to this period. Virgil\'s _[Aeneid](/wiki/Aeneid 'Aeneid')_ was a national epic in the manner of the [Homeric epics](/wiki/Homeric_epics 'Homeric epics') of Greece. Horace perfected the use of [Greek lyric](/wiki/Greek_lyric 'Greek lyric') [metres]( 'Metre (poetry)') in Latin verse. Ovid\'s erotic poetry was enormously popular, but ran afoul of Augustan morality, contributing to his exile. Ovid\'s _[Metamorphoses](/wiki/Metamorphoses 'Metamorphoses')_ wove together [Greco-Roman mythology](/wiki/Greco-Roman_mythology 'Greco-Roman mythology'); his versions of [Greek myths](/wiki/Greek_mythology 'Greek mythology') became a primary source of later [classical mythology](/wiki/Classical_mythology 'Classical mythology'), and his work was hugely influential on [medieval literature](/wiki/Medieval_literature 'Medieval literature'). Latin writers were immersed in [Greek literary traditions](/wiki/Ancient_Greek_literature 'Ancient Greek literature'), and adapted its forms and content, but Romans regarded [satire](/wiki/Satire 'Satire') as a genre in which they surpassed the Greeks. The early [Principate](/wiki/Principate 'Principate') produced the satirists [Persius](/wiki/Persius 'Persius') and [Juvenal](/wiki/Juvenal 'Juvenal'). 108 | 109 | The mid-1st through mid-2nd century has conventionally been called the \"[Silver Age](/wiki/Silver_age 'Silver age')\" of Latin literature. The three leading writers---[Seneca](/wiki/Seneca_the_Younger 'Seneca the Younger'), [Lucan](/wiki/Lucan 'Lucan'), and [Petronius](/wiki/Petronius 'Petronius')---committed suicide after incurring [Nero](/wiki/Nero 'Nero')\'s displeasure. [Epigrammatist](/wiki/Epigram 'Epigram') and social observer [Martial](/wiki/Martial 'Martial') and the epic poet [Statius](/wiki/Statius 'Statius'), whose poetry collection _[Silvae](/wiki/Silvae 'Silvae')_ influenced [Renaissance literature](/wiki/Renaissance_literature 'Renaissance literature'), wrote during the reign of [Domitian](/wiki/Domitian 'Domitian'). Other authors of the Silver Age included [Pliny the Elder](/wiki/Pliny_the_Elder 'Pliny the Elder'), author of the encyclopedic _[Natural History]( 'Natural History (Pliny)')\_; his nephew, [Pliny the Younger](/wiki/Pliny_the_Younger 'Pliny the Younger'); and the historian [Tacitus](/wiki/Tacitus 'Tacitus'). 110 | 111 | The principal Latin prose author of the [Augustan age]( 'Augustan literature (ancient Rome)') is the [historian](/wiki/Roman_historiography 'Roman historiography') [Livy](/wiki/Livy 'Livy'), whose account of [Rome\'s founding](/wiki/Founding_of_Rome 'Founding of Rome') became the most familiar version in modern-era literature. Among Imperial historians who wrote in Greek are [Dionysius of Halicarnassus](/wiki/Dionysius_of_Halicarnassus 'Dionysius of Halicarnassus'), [Josephus](/wiki/Josephus 'Josephus'), and [Cassius Dio](/wiki/Cassius_Dio 'Cassius Dio'). Other major Greek authors of the Empire include the biographer [Plutarch](/wiki/Plutarch 'Plutarch'), the geographer [Strabo](/wiki/Strabo 'Strabo'), and the rhetorician and satirist [Lucian](/wiki/Lucian 'Lucian'). _[The Twelve Caesars](/wiki/The_Twelve_Caesars 'The Twelve Caesars')_ by [Suetonius](/wiki/Suetonius 'Suetonius') is a primary source for imperial biography. 112 | 113 | From the 2nd to the 4th centuries, Christian authors were in active dialogue with the [classical tradition](/wiki/Classical_tradition 'Classical tradition'). [Tertullian](/wiki/Tertullian 'Tertullian') was one of the earliest prose authors with a distinctly Christian voice. After the [conversion of Constantine](/wiki/Conversion_of_Constantine 'Conversion of Constantine'), Latin literature is dominated by the Christian perspective. In the late 4th century, [Jerome](/wiki/Jerome 'Jerome') produced the Latin translation of the Bible that became authoritative as the [Vulgate](/wiki/Vulgate 'Vulgate'). [Augustine](/wiki/Augustine 'Augustine') in _[The City of God against the Pagans](/wiki/The_City_of_God_against_the_Pagans 'The City of God against the Pagans')_ builds a vision of an eternal, spiritual Rome, a new _[imperium sine fine](#Geography_and_demography)_ that will outlast the collapsing Empire. 114 | 115 | In contrast to the unity of Classical Latin, the literary esthetic of late antiquity has a [tessellated](/wiki/Tessellation 'Tessellation') quality. A continuing interest in the religious traditions of Rome prior to Christian dominion is found into the 5th century, with the _Saturnalia_ of [Macrobius](/wiki/Macrobius 'Macrobius') and _The Marriage of Philology and Mercury_ of [Martianus Capella](/wiki/Martianus_Capella 'Martianus Capella'). Prominent Latin poets of late antiquity include [Ausonius](/wiki/Ausonius 'Ausonius'), [Prudentius](/wiki/Prudentius 'Prudentius'), [Claudian](/wiki/Claudian 'Claudian'), and [Sidonius Apollinaris](/wiki/Sidonius_Apollinaris 'Sidonius Apollinaris'). 116 | 117 | ## Religion 118 | 119 | Main articles: [Religion in ancient Rome](/wiki/Religion_in_ancient_Rome 'Religion in ancient Rome') and [Roman imperial cult](/wiki/Roman_imperial_cult 'Roman imperial cult') 120 | 121 | See also: [History of the Jews in the Roman Empire](/wiki/History_of_the_Jews_in_the_Roman_Empire 'History of the Jews in the Roman Empire'), [Early Christianity](/wiki/Early_Christianity 'Early Christianity'), [Religious persecution in the Roman Empire](/wiki/Religious_persecution_in_the_Roman_Empire 'Religious persecution in the Roman Empire'), and [Christianization of the Roman Empire as diffusion of innovation](/wiki/Christianization_of_the_Roman_Empire_as_diffusion_of_innovation 'Christianization of the Roman Empire as diffusion of innovation') 122 | 123 | The Romans thought of themselves as highly religious, and attributed their success to their collective piety _([pietas](/wiki/Pietas 'Pietas'))_ and good relations with the gods _([pax deorum](/wiki/Pax_deorum 'Pax deorum'))_. The archaic religion believed to have come from the earliest [kings of Rome](/wiki/Kings_of_Rome 'Kings of Rome') was the foundation of the _[mos maiorum](/wiki/Mos_maiorum 'Mos maiorum')_, \"the way of the ancestors\", central to Roman identity. The priesthoods of the state religion were filled from the same pool of men who held public office, and the [Pontifex Maximus](/wiki/Pontifex_Maximus 'Pontifex Maximus') was the emperor. 124 | 125 | Roman religion was practical and contractual, based on the principle of _[do ut des](/wiki/Do_ut_des 'Do ut des')_, \"I give that you might give.\" Religion depended on knowledge and the [correct practice](/wiki/Orthopraxy 'Orthopraxy') of prayer, ritual, and sacrifice, not on faith or dogma, although Latin literature preserves learned speculation on the nature of the divine. For ordinary Romans, religion was a part of daily life. Each home had a household shrine to offer prayers and [libations](/wiki/Libation 'Libation') to the family\'s domestic deities. Neighbourhood shrines and sacred places such as springs and groves dotted the city. The [Roman calendar](/wiki/Roman_calendar 'Roman calendar') was structured around religious observances; as many as 135 days were devoted to [religious festivals](/wiki/Roman_festivals 'Roman festivals') and games (_[ludi](/wiki/Ludi 'Ludi'))_. 126 | 127 | In the wake of the [Republic\'s collapse](/wiki/Collapse_of_the_Roman_Republic 'Collapse of the Roman Republic'), state religion adapted to support the new regime. Augustus justified one-man rule with a vast programme of religious revivalism and reform. [Public vows](/wiki/Vota_pro_salute_rei_publicae 'Vota pro salute rei publicae') now were directed at the wellbeing of the emperor. So-called \"emperor worship\" expanded on a grand scale the traditional [veneration of the ancestral dead](/wiki/Roman_funerals_and_burial 'Roman funerals and burial') and of the _[Genius]( 'Genius (mythology)')_, the divine [tutelary](/wiki/Tutelary_deity 'Tutelary deity') of every individual. Upon death, an emperor could be made a state divinity (_[divus](/wiki/Divus 'Divus')_) by vote of the Senate. The [Roman imperial cult](/wiki/Roman_imperial_cult 'Roman imperial cult'), influenced by [Hellenistic ruler cult](/wiki/Hellenistic_ruler_cult 'Hellenistic ruler cult'), became one of the major ways Rome advertised its presence in the provinces and cultivated shared cultural identity. Cultural precedent in the Eastern provinces facilitated a rapid dissemination of Imperial cult, extending as far as [Najran](/wiki/Najran 'Najran'), in present-day [Saudi Arabia](/wiki/Saudi_Arabia 'Saudi Arabia'). Rejection of the state religion became tantamount to treason. This was the context for Rome\'s conflict with [Christianity](/wiki/Early_Christianity 'Early Christianity'), which Romans variously regarded as a form of atheism and _[superstitio](/wiki/Glossary_of_ancient_Roman_religion#superstitio 'Glossary of ancient Roman religion')\_. 128 | 129 | The Romans are known for the [great number of deities](/wiki/List_of_Roman_deities 'List of Roman deities') they honoured. As the Romans extended their territories, their general policy was to promote stability among diverse peoples by absorbing local deities and cults rather than eradicating them, building temples that framed local theology within Roman religion. Inscriptions throughout the Empire record the side-by-side worship of local and Roman deities, including dedications made by Romans to local gods. By the height of the Empire, numerous [syncretic or reinterpreted gods](/wiki/Interpretatio_romana 'Interpretatio romana') were cultivated, among them cults of [Cybele](/wiki/Cybele 'Cybele'), [Isis](/wiki/Isis 'Isis'), [Epona](/wiki/Epona 'Epona'), and of solar gods such as [Mithras](/wiki/Mithras 'Mithras') and [Sol Invictus](/wiki/Sol_Invictus 'Sol Invictus'), found as far north as [Roman Britain](/wiki/Roman_Britain 'Roman Britain'). Because Romans had never been obligated to cultivate one god or cult only, [religious tolerance](/wiki/Religious_tolerance 'Religious tolerance') was not an issue. 130 | 131 | [Mystery religions](/wiki/Mystery_religions 'Mystery religions'), which offered initiates salvation in the afterlife, were a matter of personal choice, practiced in addition to one\'s [family rites](/wiki/Sacra_gentilicia 'Sacra gentilicia') and public religion. The mysteries, however, involved exclusive oaths and secrecy, which conservative Romans viewed with suspicion as characteristic of \"[magic](/wiki/Magic_in_the_Greco-Roman_world 'Magic in the Greco-Roman world')\", conspiracy (_coniuratio_), and subversive activity. Thus, sporadic and sometimes brutal attempts were made to suppress religionists. In Gaul, the power of the [druids](/wiki/Druid 'Druid') was checked, first by forbidding Roman citizens to belong to the order, and then by banning druidism altogether. However, Celtic traditions were reinterpreted within the context of Imperial theology, and a new [Gallo-Roman religion](/wiki/Gallo-Roman_religion 'Gallo-Roman religion') coalesced; its capital at the [Sanctuary of the Three Gauls](/wiki/Sanctuary_of_the_Three_Gauls 'Sanctuary of the Three Gauls') established precedent for Western cult as a form of Roman-provincial identity. 132 | 133 | The monotheistic rigour of [Judaism](/wiki/Judaism 'Judaism') posed difficulties for Roman policy that led at times to compromise and granting of special exemptions. Tertullian noted that Judaism, unlike Christianity, was considered a _[religio licita](/wiki/Religio_licita 'Religio licita')_, \"legitimate religion.\" The [Jewish--Roman wars](/wiki/Jewish%E2%80%93Roman_wars 'Jewish–Roman wars') resulted from political as well as religious conflicts; the [siege of Jerusalem]( 'Siege of Jerusalem (70)') in 70 AD led to the sacking of the temple and the dispersal of Jewish political power (see [Jewish diaspora](/wiki/Jewish_diaspora 'Jewish diaspora')). 134 | 135 | Christianity emerged in [Roman Judaea]( 'Judaea (Roman province)') as a [Jewish religious sect](/wiki/Jewish_Christian 'Jewish Christian') in the 1st century and gradually [spread](/wiki/Spread_of_Christianity 'Spread of Christianity') out of [Jerusalem](/wiki/Jerusalem_in_Christianity 'Jerusalem in Christianity') throughout the Empire and beyond. Imperially authorized persecutions were limited and sporadic, with martyrdoms occurring most often under the authority of local officials. [Tacitus](/wiki/Tacitus 'Tacitus') reports that after the [Great Fire of Rome](/wiki/Great_Fire_of_Rome 'Great Fire of Rome') in AD 64, the emperor attempted to deflect blame from himself onto the Christians. A major persecution occurred under the emperor [Domitian](/wiki/Domitian 'Domitian') and a [persecution in 177](/wiki/Persecution_in_Lyon 'Persecution in Lyon') took place at Lugdunum, the Gallo-Roman religious capital. A letter from [Pliny the Younger](/wiki/Pliny_the_Younger 'Pliny the Younger'), governor of [Bithynia](/wiki/Bithynia 'Bithynia'), describes his persecution and executions of Christians. The [Decian persecution](/wiki/Decian_persecution 'Decian persecution') of 246--251 seriously threatened the [Christian Church](/wiki/Christian_Church 'Christian Church'), but ultimately strengthened Christian defiance. [Diocletian](/wiki/Diocletian 'Diocletian') undertook the [most severe persecution of Christians](/wiki/Diocletianic_Persecution 'Diocletianic Persecution'), from 303 to 311. 136 | 137 | From the 2nd century onward, the [Church Fathers](/wiki/Church_Fathers 'Church Fathers') condemned the diverse religions practiced throughout the Empire as \"pagan\". In the early 4th century, [Constantine I](/wiki/Constantine_I 'Constantine I') became the first emperor to [convert to Christianity](/wiki/Convert_to_Christianity 'Convert to Christianity'). He supported the Church financially and made laws that favored it, but the new religion was already successful, having moved from less than 50,000 to over a million adherents between 150 and 250. Constantine and his successors banned public sacrifice while tolerating other traditional practices. Constantine never engaged in a [purge](/wiki/Purge 'Purge'), there were no \"pagan martyrs\" during his reign, and people who had not converted to Christianity remained in important positions at court. [Julian](/wiki/Julian_the_Apostate 'Julian the Apostate') attempted to revive traditional public sacrifice and [Hellenistic religion](/wiki/Hellenistic_religion 'Hellenistic religion'), but met Christian resistance and lack of popular support. 138 | 139 | Christians of the 4th century believed the conversion of Constantine showed that Christianity had triumphed over paganism (in Heaven) and little further action besides such rhetoric was necessary. Thus, their focus was [heresy](/wiki/Heresy_in_Christianity 'Heresy in Christianity'). According to [Peter Brown]( 'Peter Brown (historian)'), \"In most areas, polytheists were not molested, and apart from a few ugly incidents of local violence, Jewish communities also enjoyed a century of stable, even privileged, existence\". There were anti-pagan laws, but they were not generally enforced; through the 6th century, centers of paganism existed in Athens, Gaza, Alexandria, and elsewhere. 140 | 141 | According to recent Jewish scholarship, toleration of the Jews was maintained under Christian emperors. This did not extend to [heretics](/wiki/Christian_heresy 'Christian heresy'): Theodosius I made multiple laws and acted against alternate forms of Christianity, and heretics were persecuted and killed by both the government and the church throughout Late Antiquity. Non-Christians were not persecuted until the 6th century. Rome\'s original religious hierarchy and ritual influenced Christian forms, and many pre-Christian practices survived in Christian festivals and local traditions. 142 | 143 | ## Legacy 144 | 145 | Main article: [Legacy of the Roman Empire](/wiki/Legacy_of_the_Roman_Empire 'Legacy of the Roman Empire') 146 | 147 | Several states claimed to be the Roman Empire\'s successor. The [Holy Roman Empire](/wiki/Holy_Roman_Empire 'Holy Roman Empire') was established in 800 when [Pope Leo III](/wiki/Pope_Leo_III 'Pope Leo III') crowned [Charlemagne](/wiki/Charlemagne 'Charlemagne') as [Roman emperor](/wiki/Roman_emperor 'Roman emperor'). The [Russian Tsardom](/wiki/Tsardom_of_Russia 'Tsardom of Russia'), as inheritor of the Byzantine Empire\'s [Orthodox Christian](/wiki/Eastern_Orthodox_Church 'Eastern Orthodox Church') tradition, counted itself the [Third Rome](/wiki/Third_Rome 'Third Rome') (Constantinople having been the second), in accordance with the concept of [translatio imperii](/wiki/Translatio_imperii 'Translatio imperii'). The last Eastern Roman titular, [Andreas Palailogos](/wiki/Andreas_Palaiologos 'Andreas Palaiologos'), sold the title of Emperor of Constantinople to [Charles VIII of France](/wiki/Charles_VIII_of_France 'Charles VIII of France'); upon Charles\' death, Palaiologos reclaimed the title and on his death granted it to [Ferdinand and Isabella](/wiki/Ferdinand_and_Isabella 'Ferdinand and Isabella') and their successors, who never used it. When the [Ottomans](/wiki/Ottoman_Empire 'Ottoman Empire'), who based their state on the Byzantine model, took Constantinople in 1453, [Mehmed II](/wiki/Mehmed_II 'Mehmed II') established his capital there and claimed to sit on the throne of the Roman Empire. He even launched an [invasion of Otranto](/wiki/Ottoman_invasion_of_Otranto 'Ottoman invasion of Otranto') with the purpose of re-uniting the Empire, which was aborted by his death. In the medieval West, \"Roman\" came to mean the church and the Catholic Pope. The Greek form [Romaioi](/wiki/Romaioi 'Romaioi') remained attached to the Greek-speaking Christian population of the Byzantine Empire and is still used by [Greeks](/wiki/Greeks 'Greeks'). 148 | 149 | The Roman Empire\'s control of the Italian peninsula influenced [Italian nationalism](/wiki/Italian_nationalism 'Italian nationalism') and the [unification of Italy](/wiki/Unification_of_Italy 'Unification of Italy') (_[Risorgimento](/wiki/Risorgimento 'Risorgimento')_) in 1861. Roman imperialism was claimed by fascist ideology, particularly by the [Italian Empire](/wiki/Italian_Empire 'Italian Empire') and [Nazi Germany](/wiki/Nazi_Germany 'Nazi Germany'). 150 | 151 | In the United States, the [founders](/wiki/Founding_Fathers_of_the_United_States 'Founding Fathers of the United States') were educated in the [classical tradition](/wiki/Classical_tradition 'Classical tradition'), and used classical models for [landmarks in Washington, D.C.](/wiki/List_of_National_Historic_Landmarks_in_Washington,_D.C. 'List of National Historic Landmarks in Washington, D.C.'). The founders saw [Athenian democracy](/wiki/Athenian_democracy 'Athenian democracy') and [Roman republicanism](/wiki/Roman_republic 'Roman republic') as models for the [mixed constitution](/wiki/Mixed_constitution 'Mixed constitution'), but regarded the emperor as a figure of tyranny. 152 | 153 | ## See also 154 | 155 | - [Outline of ancient Rome](/wiki/Outline_of_ancient_Rome 'Outline of ancient Rome') 156 | - [List of political systems in France](/wiki/List_of_political_systems_in_France 'List of political systems in France') 157 | - [List of Roman dynasties](/wiki/List_of_Roman_dynasties 'List of Roman dynasties') 158 | - [Daqin](/wiki/Daqin 'Daqin') (\"Great [Qin](/wiki/Qin_dynasty 'Qin dynasty')\"), the ancient Chinese name for the Roman Empire; see also [Sino-Roman relations](/wiki/Sino-Roman_relations 'Sino-Roman relations') 159 | - [Imperial Italy]( 'Imperial Italy (fascist)') 160 | - [Byzantine Empire under the Justinian dynasty](/wiki/Byzantine_Empire_under_the_Justinian_dynasty 'Byzantine Empire under the Justinian dynasty') 161 | -------------------------------------------------------------------------------- /supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | .env 5 | -------------------------------------------------------------------------------- /supabase/config.toml: -------------------------------------------------------------------------------- 1 | # A string used to distinguish different Supabase projects on the same host. Defaults to the 2 | # working directory name when running `supabase init`. 3 | project_id = "chatgpt-your-files" 4 | 5 | [api] 6 | enabled = true 7 | # Port to use for the API URL. 8 | port = 54321 9 | # Schemas to expose in your API. Tables, views and stored procedures in this schema will get API 10 | # endpoints. `public` is always included. 11 | schemas = ["public", "graphql_public"] 12 | # Extra schemas to add to the search_path of every request. `public` is always included. 13 | extra_search_path = ["public", "extensions"] 14 | # The maximum number of rows returns from a view, table, or stored procedure. Limits payload size 15 | # for accidental or malicious requests. 16 | max_rows = 1000 17 | 18 | [db] 19 | # Port to use for the local database URL. 20 | port = 54322 21 | # Port used by db diff command to initialize the shadow database. 22 | shadow_port = 54320 23 | # The database major version to use. This has to be the same as your remote database's. Run `SHOW 24 | # server_version;` on the remote database to check. 25 | major_version = 15 26 | 27 | [db.pooler] 28 | enabled = false 29 | # Port to use for the local connection pooler. 30 | port = 54329 31 | # Specifies when a server connection can be reused by other clients. 32 | # Configure one of the supported pooler modes: `transaction`, `session`. 33 | pool_mode = "transaction" 34 | # How many server connections to allow per user/database pair. 35 | default_pool_size = 20 36 | # Maximum number of client connections allowed. 37 | max_client_conn = 100 38 | 39 | [realtime] 40 | enabled = true 41 | # Bind realtime via either IPv4 or IPv6. (default: IPv4) 42 | # ip_version = "IPv6" 43 | # The maximum length in bytes of HTTP request headers. (default: 4096) 44 | # max_header_length = 4096 45 | 46 | [studio] 47 | enabled = true 48 | # Port to use for Supabase Studio. 49 | port = 54323 50 | # External URL of the API server that frontend connects to. 51 | api_url = "http://127.0.0.1" 52 | # OpenAI API Key to use for Supabase AI in the Supabase Studio. 53 | openai_api_key = "env(OPENAI_API_KEY)" 54 | 55 | # Email testing server. Emails sent with the local dev setup are not actually sent - rather, they 56 | # are monitored, and you can view the emails that would have been sent from the web interface. 57 | [inbucket] 58 | enabled = true 59 | # Port to use for the email testing server web interface. 60 | port = 54324 61 | # Uncomment to expose additional ports for testing user applications that send emails. 62 | # smtp_port = 54325 63 | # pop3_port = 54326 64 | 65 | [storage] 66 | enabled = true 67 | # The maximum file size allowed (e.g. "5MB", "500KB"). 68 | file_size_limit = "50MiB" 69 | 70 | [storage.image_transformation] 71 | enabled = true 72 | 73 | [auth] 74 | enabled = true 75 | # The base URL of your website. Used as an allow-list for redirects and for constructing URLs used 76 | # in emails. 77 | site_url = "http://127.0.0.1:3000" 78 | # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. 79 | additional_redirect_urls = ["https://127.0.0.1:3000"] 80 | # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). 81 | jwt_expiry = 3600 82 | # If disabled, the refresh token will never expire. 83 | enable_refresh_token_rotation = true 84 | # Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. 85 | # Requires enable_refresh_token_rotation = true. 86 | refresh_token_reuse_interval = 10 87 | # Allow/disallow new user signups to your project. 88 | enable_signup = true 89 | # Allow/disallow anonymous sign-ins to your project. 90 | enable_anonymous_sign_ins = false 91 | # Allow/disallow testing manual linking of accounts 92 | enable_manual_linking = false 93 | 94 | [auth.email] 95 | # Allow/disallow new user signups via email to your project. 96 | enable_signup = true 97 | # If enabled, a user will be required to confirm any email change on both the old, and new email 98 | # addresses. If disabled, only the new email is required to confirm. 99 | double_confirm_changes = true 100 | # If enabled, users need to confirm their email address before signing in. 101 | enable_confirmations = false 102 | # Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. 103 | max_frequency = "1s" 104 | 105 | # Uncomment to customize email template 106 | # [auth.email.template.invite] 107 | # subject = "You have been invited" 108 | # content_path = "./supabase/templates/invite.html" 109 | 110 | [auth.sms] 111 | # Allow/disallow new user signups via SMS to your project. 112 | enable_signup = true 113 | # If enabled, users need to confirm their phone number before signing in. 114 | enable_confirmations = false 115 | # Template for sending OTP to users 116 | template = "Your code is {{ .Code }} ." 117 | # Controls the minimum amount of time that must pass before sending another sms otp. 118 | max_frequency = "5s" 119 | 120 | # Use pre-defined map of phone number to OTP for testing. 121 | # [auth.sms.test_otp] 122 | # 4152127777 = "123456" 123 | 124 | # This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. 125 | # [auth.hook.custom_access_token] 126 | # enabled = true 127 | # uri = "pg-functions:////" 128 | 129 | # Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. 130 | [auth.sms.twilio] 131 | enabled = false 132 | account_sid = "" 133 | message_service_sid = "" 134 | # DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: 135 | auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" 136 | 137 | # Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, 138 | # `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, 139 | # `twitter`, `slack`, `spotify`, `workos`, `zoom`. 140 | [auth.external.apple] 141 | enabled = false 142 | client_id = "" 143 | # DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: 144 | secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" 145 | # Overrides the default auth redirectUrl. 146 | redirect_uri = "" 147 | # Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, 148 | # or any other third-party OIDC providers. 149 | url = "" 150 | # If enabled, the nonce check will be skipped. Required for local sign in with Google auth. 151 | skip_nonce_check = false 152 | 153 | [analytics] 154 | enabled = false 155 | port = 54327 156 | vector_port = 54328 157 | # Configure one of the supported backends: `postgres`, `bigquery`. 158 | backend = "postgres" 159 | 160 | # Experimental features may be deprecated any time 161 | [experimental] 162 | # Configures Postgres storage engine to use OrioleDB (S3) 163 | orioledb_version = "" 164 | # Configures S3 bucket URL, eg. .s3-.amazonaws.com 165 | s3_host = "env(S3_HOST)" 166 | # Configures S3 bucket region, eg. us-east-1 167 | s3_region = "env(S3_REGION)" 168 | # Configures AWS_ACCESS_KEY_ID for S3 bucket 169 | s3_access_key = "env(S3_ACCESS_KEY)" 170 | # Configures AWS_SECRET_ACCESS_KEY for S3 bucket 171 | s3_secret_key = "env(S3_SECRET_KEY)" 172 | -------------------------------------------------------------------------------- /supabase/functions/_lib/database.ts: -------------------------------------------------------------------------------- 1 | export type Json = 2 | | string 3 | | number 4 | | boolean 5 | | null 6 | | { [key: string]: Json | undefined } 7 | | Json[] 8 | 9 | export interface Database { 10 | graphql_public: { 11 | Tables: { 12 | [_ in never]: never 13 | } 14 | Views: { 15 | [_ in never]: never 16 | } 17 | Functions: { 18 | graphql: { 19 | Args: { 20 | operationName?: string 21 | query?: string 22 | variables?: Json 23 | extensions?: Json 24 | } 25 | Returns: Json 26 | } 27 | } 28 | Enums: { 29 | [_ in never]: never 30 | } 31 | CompositeTypes: { 32 | [_ in never]: never 33 | } 34 | } 35 | public: { 36 | Tables: { 37 | document_sections: { 38 | Row: { 39 | content: string 40 | document_id: number 41 | embedding: string | null 42 | id: number 43 | } 44 | Insert: { 45 | content: string 46 | document_id: number 47 | embedding?: string | null 48 | id?: never 49 | } 50 | Update: { 51 | content?: string 52 | document_id?: number 53 | embedding?: string | null 54 | id?: never 55 | } 56 | Relationships: [ 57 | { 58 | foreignKeyName: "document_sections_document_id_fkey" 59 | columns: ["document_id"] 60 | referencedRelation: "documents" 61 | referencedColumns: ["id"] 62 | }, 63 | { 64 | foreignKeyName: "document_sections_document_id_fkey" 65 | columns: ["document_id"] 66 | referencedRelation: "documents_with_storage_path" 67 | referencedColumns: ["id"] 68 | } 69 | ] 70 | } 71 | documents: { 72 | Row: { 73 | created_at: string 74 | created_by: string 75 | id: number 76 | name: string 77 | storage_object_id: string 78 | } 79 | Insert: { 80 | created_at?: string 81 | created_by?: string 82 | id?: never 83 | name: string 84 | storage_object_id: string 85 | } 86 | Update: { 87 | created_at?: string 88 | created_by?: string 89 | id?: never 90 | name?: string 91 | storage_object_id?: string 92 | } 93 | Relationships: [ 94 | { 95 | foreignKeyName: "documents_created_by_fkey" 96 | columns: ["created_by"] 97 | referencedRelation: "users" 98 | referencedColumns: ["id"] 99 | }, 100 | { 101 | foreignKeyName: "documents_storage_object_id_fkey" 102 | columns: ["storage_object_id"] 103 | referencedRelation: "objects" 104 | referencedColumns: ["id"] 105 | } 106 | ] 107 | } 108 | } 109 | Views: { 110 | documents_with_storage_path: { 111 | Row: { 112 | created_at: string | null 113 | created_by: string | null 114 | id: number | null 115 | name: string | null 116 | storage_object_id: string | null 117 | storage_object_path: string | null 118 | } 119 | Relationships: [ 120 | { 121 | foreignKeyName: "documents_created_by_fkey" 122 | columns: ["created_by"] 123 | referencedRelation: "users" 124 | referencedColumns: ["id"] 125 | }, 126 | { 127 | foreignKeyName: "documents_storage_object_id_fkey" 128 | columns: ["storage_object_id"] 129 | referencedRelation: "objects" 130 | referencedColumns: ["id"] 131 | } 132 | ] 133 | } 134 | } 135 | Functions: { 136 | match_document_sections: { 137 | Args: { 138 | embedding: string 139 | match_threshold: number 140 | } 141 | Returns: { 142 | content: string 143 | document_id: number 144 | embedding: string | null 145 | id: number 146 | }[] 147 | } 148 | supabase_url: { 149 | Args: Record 150 | Returns: string 151 | } 152 | } 153 | Enums: { 154 | [_ in never]: never 155 | } 156 | CompositeTypes: { 157 | [_ in never]: never 158 | } 159 | } 160 | storage: { 161 | Tables: { 162 | buckets: { 163 | Row: { 164 | allowed_mime_types: string[] | null 165 | avif_autodetection: boolean | null 166 | created_at: string | null 167 | file_size_limit: number | null 168 | id: string 169 | name: string 170 | owner: string | null 171 | public: boolean | null 172 | updated_at: string | null 173 | } 174 | Insert: { 175 | allowed_mime_types?: string[] | null 176 | avif_autodetection?: boolean | null 177 | created_at?: string | null 178 | file_size_limit?: number | null 179 | id: string 180 | name: string 181 | owner?: string | null 182 | public?: boolean | null 183 | updated_at?: string | null 184 | } 185 | Update: { 186 | allowed_mime_types?: string[] | null 187 | avif_autodetection?: boolean | null 188 | created_at?: string | null 189 | file_size_limit?: number | null 190 | id?: string 191 | name?: string 192 | owner?: string | null 193 | public?: boolean | null 194 | updated_at?: string | null 195 | } 196 | Relationships: [ 197 | { 198 | foreignKeyName: "buckets_owner_fkey" 199 | columns: ["owner"] 200 | referencedRelation: "users" 201 | referencedColumns: ["id"] 202 | } 203 | ] 204 | } 205 | migrations: { 206 | Row: { 207 | executed_at: string | null 208 | hash: string 209 | id: number 210 | name: string 211 | } 212 | Insert: { 213 | executed_at?: string | null 214 | hash: string 215 | id: number 216 | name: string 217 | } 218 | Update: { 219 | executed_at?: string | null 220 | hash?: string 221 | id?: number 222 | name?: string 223 | } 224 | Relationships: [] 225 | } 226 | objects: { 227 | Row: { 228 | bucket_id: string | null 229 | created_at: string | null 230 | id: string 231 | last_accessed_at: string | null 232 | metadata: Json | null 233 | name: string | null 234 | owner: string | null 235 | path_tokens: string[] | null 236 | updated_at: string | null 237 | version: string | null 238 | } 239 | Insert: { 240 | bucket_id?: string | null 241 | created_at?: string | null 242 | id?: string 243 | last_accessed_at?: string | null 244 | metadata?: Json | null 245 | name?: string | null 246 | owner?: string | null 247 | path_tokens?: string[] | null 248 | updated_at?: string | null 249 | version?: string | null 250 | } 251 | Update: { 252 | bucket_id?: string | null 253 | created_at?: string | null 254 | id?: string 255 | last_accessed_at?: string | null 256 | metadata?: Json | null 257 | name?: string | null 258 | owner?: string | null 259 | path_tokens?: string[] | null 260 | updated_at?: string | null 261 | version?: string | null 262 | } 263 | Relationships: [ 264 | { 265 | foreignKeyName: "objects_bucketId_fkey" 266 | columns: ["bucket_id"] 267 | referencedRelation: "buckets" 268 | referencedColumns: ["id"] 269 | } 270 | ] 271 | } 272 | } 273 | Views: { 274 | [_ in never]: never 275 | } 276 | Functions: { 277 | can_insert_object: { 278 | Args: { 279 | bucketid: string 280 | name: string 281 | owner: string 282 | metadata: Json 283 | } 284 | Returns: undefined 285 | } 286 | extension: { 287 | Args: { 288 | name: string 289 | } 290 | Returns: string 291 | } 292 | filename: { 293 | Args: { 294 | name: string 295 | } 296 | Returns: string 297 | } 298 | foldername: { 299 | Args: { 300 | name: string 301 | } 302 | Returns: unknown 303 | } 304 | get_size_by_bucket: { 305 | Args: Record 306 | Returns: { 307 | size: number 308 | bucket_id: string 309 | }[] 310 | } 311 | search: { 312 | Args: { 313 | prefix: string 314 | bucketname: string 315 | limits?: number 316 | levels?: number 317 | offsets?: number 318 | search?: string 319 | sortcolumn?: string 320 | sortorder?: string 321 | } 322 | Returns: { 323 | name: string 324 | id: string 325 | updated_at: string 326 | created_at: string 327 | last_accessed_at: string 328 | metadata: Json 329 | }[] 330 | } 331 | } 332 | Enums: { 333 | [_ in never]: never 334 | } 335 | CompositeTypes: { 336 | [_ in never]: never 337 | } 338 | } 339 | } 340 | 341 | -------------------------------------------------------------------------------- /supabase/functions/_lib/markdown-parser.ts: -------------------------------------------------------------------------------- 1 | import { Root, RootContent } from 'mdast'; 2 | import { fromMarkdown } from 'mdast-util-from-markdown'; 3 | import { toMarkdown } from 'mdast-util-to-markdown'; 4 | import { toString } from 'mdast-util-to-string'; 5 | import { u } from 'unist-builder'; 6 | 7 | export type Json = Record< 8 | string, 9 | string | number | boolean | null | Json[] | { [key: string]: Json } 10 | >; 11 | 12 | export type Section = { 13 | content: string; 14 | heading?: string; 15 | part?: number; 16 | total?: number; 17 | }; 18 | 19 | export type ProcessedMd = { 20 | sections: Section[]; 21 | }; 22 | 23 | /** 24 | * Splits a `mdast` tree into multiple trees based on 25 | * a predicate function. Will include the splitting node 26 | * at the beginning of each tree. 27 | * 28 | * Useful to split a markdown file into smaller sections. 29 | */ 30 | export function splitTreeBy( 31 | tree: Root, 32 | predicate: (node: RootContent) => boolean 33 | ) { 34 | return tree.children.reduce((trees, node) => { 35 | const [lastTree] = trees.slice(-1); 36 | 37 | if (!lastTree || predicate(node)) { 38 | const tree: Root = u('root', [node]); 39 | return trees.concat(tree); 40 | } 41 | 42 | lastTree.children.push(node); 43 | return trees; 44 | }, []); 45 | } 46 | 47 | /** 48 | * Splits markdown content by heading for embedding indexing. 49 | * Keeps heading in each chunk. 50 | * 51 | * If a section is still greater than `maxSectionLength`, that section 52 | * is chunked into smaller even-sized sections (by character length). 53 | */ 54 | export function processMarkdown( 55 | content: string, 56 | maxSectionLength = 2500 57 | ): ProcessedMd { 58 | const mdTree = fromMarkdown(content); 59 | 60 | if (!mdTree) { 61 | return { 62 | sections: [], 63 | }; 64 | } 65 | 66 | const sectionTrees = splitTreeBy(mdTree, (node) => node.type === 'heading'); 67 | 68 | const sections = sectionTrees.flatMap
((tree) => { 69 | const [firstNode] = tree.children; 70 | const content = toMarkdown(tree); 71 | 72 | const heading = 73 | firstNode.type === 'heading' ? toString(firstNode) : undefined; 74 | 75 | // Chunk sections if they are too large 76 | if (content.length > maxSectionLength) { 77 | const numberChunks = Math.ceil(content.length / maxSectionLength); 78 | const chunkSize = Math.ceil(content.length / numberChunks); 79 | const chunks = []; 80 | 81 | for (let i = 0; i < numberChunks; i++) { 82 | chunks.push(content.substring(i * chunkSize, (i + 1) * chunkSize)); 83 | } 84 | 85 | return chunks.map((chunk, i) => ({ 86 | content: chunk, 87 | heading, 88 | part: i + 1, 89 | total: numberChunks, 90 | })); 91 | } 92 | 93 | return { 94 | content, 95 | heading, 96 | }; 97 | }); 98 | 99 | return { 100 | sections, 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /supabase/functions/chat/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js'; 2 | import { OpenAIStream, StreamingTextResponse } from 'ai'; 3 | import { codeBlock } from 'common-tags'; 4 | import OpenAI from 'openai'; 5 | import { Database } from '../_lib/database.ts'; 6 | 7 | const openai = new OpenAI({ 8 | apiKey: Deno.env.get('OPENAI_API_KEY'), 9 | }); 10 | 11 | // These are automatically injected 12 | const supabaseUrl = Deno.env.get('SUPABASE_URL'); 13 | const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY'); 14 | 15 | export const corsHeaders = { 16 | 'Access-Control-Allow-Origin': '*', 17 | 'Access-Control-Allow-Headers': 18 | 'authorization, x-client-info, apikey, content-type', 19 | }; 20 | 21 | Deno.serve(async (req) => { 22 | // Handle CORS 23 | if (req.method === 'OPTIONS') { 24 | return new Response('ok', { headers: corsHeaders }); 25 | } 26 | if (!supabaseUrl || !supabaseAnonKey) { 27 | return new Response( 28 | JSON.stringify({ 29 | error: 'Missing environment variables.', 30 | }), 31 | { 32 | status: 500, 33 | headers: { 'Content-Type': 'application/json' }, 34 | } 35 | ); 36 | } 37 | 38 | const authorization = req.headers.get('Authorization'); 39 | 40 | if (!authorization) { 41 | return new Response( 42 | JSON.stringify({ error: `No authorization header passed` }), 43 | { 44 | status: 500, 45 | headers: { 'Content-Type': 'application/json' }, 46 | } 47 | ); 48 | } 49 | 50 | const supabase = createClient(supabaseUrl, supabaseAnonKey, { 51 | global: { 52 | headers: { 53 | authorization, 54 | }, 55 | }, 56 | auth: { 57 | persistSession: false, 58 | }, 59 | }); 60 | 61 | const { messages, embedding } = await req.json(); 62 | 63 | const { data: documents, error: matchError } = await supabase 64 | .rpc('match_document_sections', { 65 | embedding, 66 | match_threshold: 0.8, 67 | }) 68 | .select('content') 69 | .limit(5); 70 | 71 | if (matchError) { 72 | console.error(matchError); 73 | 74 | return new Response( 75 | JSON.stringify({ 76 | error: 'There was an error reading your documents, please try again.', 77 | }), 78 | { 79 | status: 500, 80 | headers: { 'Content-Type': 'application/json' }, 81 | } 82 | ); 83 | } 84 | 85 | const injectedDocs = 86 | documents && documents.length > 0 87 | ? documents.map(({ content }) => content).join('\n\n') 88 | : 'No documents found'; 89 | 90 | console.log(injectedDocs); 91 | 92 | const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = 93 | [ 94 | { 95 | role: 'user', 96 | content: codeBlock` 97 | You're an AI assistant who answers questions about documents. 98 | 99 | You're a chat bot, so keep your replies succinct. 100 | 101 | You're only allowed to use the documents below to answer the question. 102 | 103 | If the question isn't related to these documents, say: 104 | "Sorry, I couldn't find any information on that." 105 | 106 | If the information isn't available in the below documents, say: 107 | "Sorry, I couldn't find any information on that." 108 | 109 | Do not go off topic. 110 | 111 | Documents: 112 | ${injectedDocs} 113 | `, 114 | }, 115 | ...messages, 116 | ]; 117 | 118 | const completionStream = await openai.chat.completions.create({ 119 | model: 'gpt-3.5-turbo-0125', 120 | messages: completionMessages, 121 | max_tokens: 1024, 122 | temperature: 0, 123 | stream: true, 124 | }); 125 | 126 | const stream = OpenAIStream(completionStream); 127 | return new StreamingTextResponse(stream, { headers: corsHeaders }); 128 | }); 129 | -------------------------------------------------------------------------------- /supabase/functions/embed/index.ts: -------------------------------------------------------------------------------- 1 | // Setup type definitions for built-in Supabase Runtime APIs 2 | /// 3 | 4 | import { createClient } from '@supabase/supabase-js'; 5 | import { Database } from '../_lib/database.ts'; 6 | 7 | const model = new Supabase.ai.Session('gte-small'); 8 | 9 | const supabaseUrl = Deno.env.get('SUPABASE_URL'); 10 | const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY'); 11 | 12 | Deno.serve(async (req) => { 13 | if (!supabaseUrl || !supabaseAnonKey) { 14 | return new Response( 15 | JSON.stringify({ 16 | error: 'Missing environment variables.', 17 | }), 18 | { 19 | status: 500, 20 | headers: { 'Content-Type': 'application/json' }, 21 | } 22 | ); 23 | } 24 | 25 | const authorization = req.headers.get('Authorization'); 26 | 27 | if (!authorization) { 28 | return new Response( 29 | JSON.stringify({ error: `No authorization header passed` }), 30 | { 31 | status: 500, 32 | headers: { 'Content-Type': 'application/json' }, 33 | } 34 | ); 35 | } 36 | 37 | const supabase = createClient(supabaseUrl, supabaseAnonKey, { 38 | global: { 39 | headers: { 40 | authorization, 41 | }, 42 | }, 43 | auth: { 44 | persistSession: false, 45 | }, 46 | }); 47 | 48 | const { ids, table, contentColumn, embeddingColumn } = await req.json(); 49 | 50 | const { data: rows, error: selectError } = await supabase 51 | .from(table) 52 | .select(`id, ${contentColumn}` as '*') 53 | .in('id', ids) 54 | .is(embeddingColumn, null); 55 | 56 | if (selectError) { 57 | return new Response(JSON.stringify({ error: selectError }), { 58 | status: 500, 59 | headers: { 'Content-Type': 'application/json' }, 60 | }); 61 | } 62 | 63 | for (const row of rows) { 64 | const { id, [contentColumn]: content } = row; 65 | 66 | if (!content) { 67 | console.error(`No content available in column '${contentColumn}'`); 68 | continue; 69 | } 70 | 71 | const output = (await model.run(content, { 72 | mean_pool: true, 73 | normalize: true, 74 | })) as number[]; 75 | 76 | const embedding = JSON.stringify(output); 77 | 78 | const { error } = await supabase 79 | .from(table) 80 | .update({ 81 | [embeddingColumn]: embedding, 82 | }) 83 | .eq('id', id); 84 | 85 | if (error) { 86 | console.error( 87 | `Failed to save embedding on '${table}' table with id ${id}` 88 | ); 89 | } 90 | 91 | console.log( 92 | `Generated embedding ${JSON.stringify({ 93 | table, 94 | id, 95 | contentColumn, 96 | embeddingColumn, 97 | })}` 98 | ); 99 | } 100 | 101 | return new Response(null, { 102 | status: 204, 103 | headers: { 'Content-Type': 'application/json' }, 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /supabase/functions/import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "@std/": "https://deno.land/std@0.168.0/", 4 | 5 | "@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.21.0", 6 | "openai": "https://esm.sh/openai@4.10.0", 7 | "common-tags": "https://esm.sh/common-tags@1.8.2", 8 | "ai": "https://esm.sh/ai@2.2.13", 9 | 10 | "mdast-util-from-markdown": "https://esm.sh/v132/mdast-util-from-markdown@2.0.0", 11 | "mdast-util-to-markdown": "https://esm.sh/v132/mdast-util-to-markdown@2.1.0", 12 | "mdast-util-to-string": "https://esm.sh/v132/mdast-util-to-string@4.0.0", 13 | "unist-builder": "https://esm.sh/v132/unist-builder@4.0.0", 14 | "mdast": "https://esm.sh/v132/@types/mdast@4.0.0/index.d.ts", 15 | 16 | "https://esm.sh/v132/decode-named-character-reference@1.0.2/esnext/decode-named-character-reference.mjs": "https://esm.sh/decode-named-character-reference@1.0.2?target=deno" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /supabase/functions/process/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js'; 2 | import { Database } from '../_lib/database.ts'; 3 | import { processMarkdown } from '../_lib/markdown-parser.ts'; 4 | 5 | const supabaseUrl = Deno.env.get('SUPABASE_URL'); 6 | const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY'); 7 | 8 | Deno.serve(async (req) => { 9 | if (!supabaseUrl || !supabaseAnonKey) { 10 | return new Response( 11 | JSON.stringify({ 12 | error: 'Missing environment variables.', 13 | }), 14 | { 15 | status: 500, 16 | headers: { 'Content-Type': 'application/json' }, 17 | } 18 | ); 19 | } 20 | 21 | const authorization = req.headers.get('Authorization'); 22 | 23 | if (!authorization) { 24 | return new Response( 25 | JSON.stringify({ error: `No authorization header passed` }), 26 | { 27 | status: 500, 28 | headers: { 'Content-Type': 'application/json' }, 29 | } 30 | ); 31 | } 32 | 33 | const supabase = createClient(supabaseUrl, supabaseAnonKey, { 34 | global: { 35 | headers: { 36 | authorization, 37 | }, 38 | }, 39 | auth: { 40 | persistSession: false, 41 | }, 42 | }); 43 | 44 | const { document_id } = await req.json(); 45 | 46 | const { data: document } = await supabase 47 | .from('documents_with_storage_path') 48 | .select() 49 | .eq('id', document_id) 50 | .single(); 51 | 52 | if (!document?.storage_object_path) { 53 | return new Response( 54 | JSON.stringify({ error: 'Failed to find uploaded document' }), 55 | { 56 | status: 500, 57 | headers: { 'Content-Type': 'application/json' }, 58 | } 59 | ); 60 | } 61 | 62 | const { data: file } = await supabase.storage 63 | .from('files') 64 | .download(document.storage_object_path); 65 | 66 | if (!file) { 67 | return new Response( 68 | JSON.stringify({ error: 'Failed to download storage object' }), 69 | { 70 | status: 500, 71 | headers: { 'Content-Type': 'application/json' }, 72 | } 73 | ); 74 | } 75 | 76 | const fileContents = await file.text(); 77 | const processedMd = processMarkdown(fileContents); 78 | 79 | const { error } = await supabase.from('document_sections').insert( 80 | processedMd.sections.map(({ content }) => ({ 81 | document_id, 82 | content, 83 | })) 84 | ); 85 | 86 | if (error) { 87 | console.error(error); 88 | return new Response( 89 | JSON.stringify({ error: 'Failed to save document sections' }), 90 | { 91 | status: 500, 92 | headers: { 'Content-Type': 'application/json' }, 93 | } 94 | ); 95 | } 96 | 97 | console.log( 98 | `Saved ${processedMd.sections.length} sections for file '${document.name}'` 99 | ); 100 | 101 | return new Response(null, { 102 | status: 204, 103 | headers: { 'Content-Type': 'application/json' }, 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /supabase/migrations/20231006192603_files.sql: -------------------------------------------------------------------------------- 1 | create schema private; 2 | 3 | insert into storage.buckets (id, name) 4 | values ('files', 'files') 5 | on conflict do nothing; 6 | 7 | create or replace function private.uuid_or_null(str text) 8 | returns uuid 9 | language plpgsql 10 | as $$ 11 | begin 12 | return str::uuid; 13 | exception when invalid_text_representation then 14 | return null; 15 | end; 16 | $$; 17 | 18 | create policy "Authenticated users can upload files" 19 | on storage.objects for insert to authenticated with check ( 20 | bucket_id = 'files' and 21 | owner = auth.uid() and 22 | private.uuid_or_null(path_tokens[1]) is not null 23 | ); 24 | 25 | create policy "Users can view their own files" 26 | on storage.objects for select to authenticated using ( 27 | bucket_id = 'files' and owner = auth.uid() 28 | ); 29 | 30 | create policy "Users can update their own files" 31 | on storage.objects for update to authenticated with check ( 32 | bucket_id = 'files' and owner = auth.uid() 33 | ); 34 | 35 | create policy "Users can delete their own files" 36 | on storage.objects for delete to authenticated using ( 37 | bucket_id = 'files' and owner = auth.uid() 38 | ); 39 | -------------------------------------------------------------------------------- /supabase/migrations/20231006212813_documents.sql: -------------------------------------------------------------------------------- 1 | create extension if not exists pg_net with schema extensions; 2 | create extension if not exists vector with schema extensions; 3 | 4 | create table documents ( 5 | id bigint primary key generated always as identity, 6 | name text not null, 7 | storage_object_id uuid not null references storage.objects (id), 8 | created_by uuid not null references auth.users (id) default auth.uid(), 9 | created_at timestamp with time zone not null default now() 10 | ); 11 | 12 | create view documents_with_storage_path 13 | with (security_invoker=true) 14 | as 15 | select documents.*, storage.objects.name as storage_object_path 16 | from documents 17 | join storage.objects 18 | on storage.objects.id = documents.storage_object_id; 19 | 20 | create table document_sections ( 21 | id bigint primary key generated always as identity, 22 | document_id bigint not null references documents (id), 23 | content text not null, 24 | embedding vector (384) 25 | ); 26 | 27 | create index on document_sections using hnsw (embedding vector_ip_ops); 28 | 29 | alter table documents enable row level security; 30 | alter table document_sections enable row level security; 31 | 32 | create policy "Users can insert documents" 33 | on documents for insert to authenticated with check ( 34 | auth.uid() = created_by 35 | ); 36 | 37 | create policy "Users can query their own documents" 38 | on documents for select to authenticated using ( 39 | auth.uid() = created_by 40 | ); 41 | 42 | create policy "Users can insert document sections" 43 | on document_sections for insert to authenticated with check ( 44 | document_id in ( 45 | select id 46 | from documents 47 | where created_by = auth.uid() 48 | ) 49 | ); 50 | 51 | create policy "Users can update their own document sections" 52 | on document_sections for update to authenticated using ( 53 | document_id in ( 54 | select id 55 | from documents 56 | where created_by = auth.uid() 57 | ) 58 | ) with check ( 59 | document_id in ( 60 | select id 61 | from documents 62 | where created_by = auth.uid() 63 | ) 64 | ); 65 | 66 | create policy "Users can query their own document sections" 67 | on document_sections for select to authenticated using ( 68 | document_id in ( 69 | select id 70 | from documents 71 | where created_by = auth.uid() 72 | ) 73 | ); 74 | 75 | create function supabase_url() 76 | returns text 77 | language plpgsql 78 | security definer 79 | as $$ 80 | declare 81 | secret_value text; 82 | begin 83 | select decrypted_secret into secret_value from vault.decrypted_secrets where name = 'supabase_url'; 84 | return secret_value; 85 | end; 86 | $$; 87 | 88 | create function private.handle_storage_update() 89 | returns trigger 90 | language plpgsql 91 | as $$ 92 | declare 93 | document_id bigint; 94 | result int; 95 | begin 96 | insert into documents (name, storage_object_id, created_by) 97 | values (new.path_tokens[2], new.id, new.owner) 98 | returning id into document_id; 99 | 100 | select 101 | net.http_post( 102 | url := supabase_url() || '/functions/v1/process', 103 | headers := jsonb_build_object( 104 | 'Content-Type', 'application/json', 105 | 'Authorization', current_setting('request.headers')::json->>'authorization' 106 | ), 107 | body := jsonb_build_object( 108 | 'document_id', document_id 109 | ) 110 | ) 111 | into result; 112 | 113 | return null; 114 | end; 115 | $$; 116 | 117 | create trigger on_file_upload 118 | after insert on storage.objects 119 | for each row 120 | execute procedure private.handle_storage_update(); 121 | -------------------------------------------------------------------------------- /supabase/migrations/20231007002735_embed.sql: -------------------------------------------------------------------------------- 1 | -- General purpose trigger function to generate text embeddings 2 | -- on newly inserted rows. 3 | -- 4 | -- Calls an edge function at `/embed` in batches that asynchronously 5 | -- generates the embeddings and stores them on each record. 6 | -- 7 | -- Trigger is expected to have the format: 8 | -- 9 | -- create trigger 10 | -- after insert on 11 | -- referencing new table as inserted 12 | -- for each statement 13 | -- execute procedure private.embed(, ); 14 | -- 15 | -- Expects 2 arguments: `private.embed(, )` 16 | -- where the first argument indicates the source column containing the text content, 17 | -- and the second argument indicates the destination column to store the embedding. 18 | -- 19 | -- Also supports 2 more optional arguments: `private.embed(, , , )` 20 | -- where the third argument indicates the number of records to include in each edge function call (default 5), 21 | -- and the fourth argument specifies the HTTP connection timeout for each edge function call (default 300000 ms). 22 | create function private.embed() 23 | returns trigger 24 | language plpgsql 25 | as $$ 26 | declare 27 | content_column text = TG_ARGV[0]; 28 | embedding_column text = TG_ARGV[1]; 29 | batch_size int = case when array_length(TG_ARGV, 1) >= 3 then TG_ARGV[2]::int else 5 end; 30 | timeout_milliseconds int = case when array_length(TG_ARGV, 1) >= 4 then TG_ARGV[3]::int else 5 * 60 * 1000 end; 31 | batch_count int = ceiling((select count(*) from inserted) / batch_size::float); 32 | begin 33 | -- Loop through each batch and invoke an edge function to handle the embedding generation 34 | for i in 0 .. (batch_count-1) loop 35 | perform 36 | net.http_post( 37 | url := supabase_url() || '/functions/v1/embed', 38 | headers := jsonb_build_object( 39 | 'Content-Type', 'application/json', 40 | 'Authorization', current_setting('request.headers')::json->>'authorization' 41 | ), 42 | body := jsonb_build_object( 43 | 'ids', (select json_agg(ds.id) from (select id from inserted limit batch_size offset i*batch_size) ds), 44 | 'table', TG_TABLE_NAME, 45 | 'contentColumn', content_column, 46 | 'embeddingColumn', embedding_column 47 | ), 48 | timeout_milliseconds := timeout_milliseconds 49 | ); 50 | end loop; 51 | 52 | return null; 53 | end; 54 | $$; 55 | 56 | create trigger embed_document_sections 57 | after insert on document_sections 58 | referencing new table as inserted 59 | for each statement 60 | execute procedure private.embed(content, embedding); 61 | -------------------------------------------------------------------------------- /supabase/migrations/20231007040908_match.sql: -------------------------------------------------------------------------------- 1 | -- Matches document sections using vector similarity search on embeddings 2 | -- 3 | -- Returns a setof document_sections so that we can use PostgREST resource embeddings (joins with other tables) 4 | -- Additional filtering like limits can be chained to this function call 5 | create or replace function match_document_sections(embedding vector(384), match_threshold float) 6 | returns setof document_sections 7 | language plpgsql 8 | as $$ 9 | #variable_conflict use_variable 10 | begin 11 | return query 12 | select * 13 | from document_sections 14 | 15 | -- The inner product is negative, so we negate match_threshold 16 | where document_sections.embedding <#> embedding < -match_threshold 17 | 18 | -- Our embeddings are normalized to length 1, so cosine similarity 19 | -- and inner product will produce the same query results. 20 | -- Using inner product which can be computed faster. 21 | -- 22 | -- For the different distance functions, see https://github.com/pgvector/pgvector 23 | order by document_sections.embedding <#> embedding; 24 | end; 25 | $$; 26 | -------------------------------------------------------------------------------- /supabase/migrations/20240223144537_cascade.sql: -------------------------------------------------------------------------------- 1 | alter table document_sections 2 | drop constraint document_sections_document_id_fkey, 3 | add constraint document_sections_document_id_fkey 4 | foreign key (document_id) 5 | references documents(id) 6 | on delete cascade; 7 | -------------------------------------------------------------------------------- /supabase/seed.sql: -------------------------------------------------------------------------------- 1 | select vault.create_secret( 2 | 'http://api.supabase.internal:8000', 3 | 'supabase_url' 4 | ); 5 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate")], 76 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "plugins": [ 22 | { 23 | "name": "next" 24 | } 25 | ], 26 | "paths": { 27 | "@/*": [ 28 | "./*" 29 | ] 30 | } 31 | }, 32 | "include": [ 33 | "next-env.d.ts", 34 | "**/*.ts", 35 | "**/*.tsx", 36 | ".next/types/**/*.ts", 37 | "supabase/functions/_lib/database.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "supabase/functions" 42 | ] 43 | } --------------------------------------------------------------------------------