Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/app/api/ai/classify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { classification } from "@/app/schemas/content-classification"
import OpenAI from "openai"
import { OAIStream, withResponseModel } from "zod-stream"

import { RateLimiter } from "@/lib/rate-limiter"

const oai = new OpenAI({
apiKey: process.env["OPENAI_API_KEY"] ?? undefined,
organization: process.env["OPENAI_ORG_ID"] ?? undefined
})

export const runtime = "edge"

interface IRequest {
// response_model: { schema: typeof dashboardSchema; name: string }
messages: OpenAI.ChatCompletionMessageParam[]
}

export async function POST(request: Request) {
const rateLimit = await RateLimiter(request)

if (!rateLimit || rateLimit?.isLimited) {
return new Response("Daily limit exceeded", {
status: 429
})
}
const { messages } = (await request.json()) as IRequest

const params = withResponseModel({
response_model: {
schema: classification,
name: "classification of content"
},
params: {
temperature: 0.2,
seed: 1,
messages,

model: "gpt-4-1106-preview",
stream: true
},
mode: "TOOLS"
})

const extractionStream = await oai.chat.completions.create(params)

return new Response(
OAIStream({
res: extractionStream
})
)
}
150 changes: 150 additions & 0 deletions src/app/content-analysis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { useState } from "react"
import { useJsonStream } from "stream-hooks"

import { Overview } from "@/components/overview"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Textarea } from "@/components/ui/textarea"

import { classification } from "./schemas/content-classification"
import { treasury } from "./text"

export const ContentAnalysis = () => {
const [submitted, setSubmitted] = useState(false)
const [content, _setContent] = useState(treasury)
const [prompt, _setPrompt] = useState(
"I care mostly about the ideas in this report that can help me improve my business "
)

const { data, loading, startStream } = useJsonStream({
schema: classification
})

const submitMessage = async () => {
try {
setSubmitted(true)
await startStream({
url: "/api/ai/classify",
method: "POST",
body: {
messages: [
{
content: `You are tasks with classifying the following content.

Think about what this content could be.

Then piece together the information you have to classify it.

Here is some additional context from the user of the tool: ${prompt}
`,

role: "system"
},

{
content: content,
role: "user"
}
]
}
})
} catch (e) {
console.error(e)
}
}

return !submitted ? (
<>
<div>
<div>
<p>Status: {loading ? "fetching" : submitted ? "submitted" : "idle"}</p>
</div>
<p>
This example parses any kind of content and classifies and pulls out some vital
information from it. You can copy and paste email threads, newsletters or any other kind
of content. It will then classify the content and pull out the important information.
</p>
<br />
<p>This prompt will be sent as context before the content</p>
<Textarea
className="mb-12 w-full"
defaultValue={prompt}
onChange={e => _setPrompt(e.target.value)}
/>
<p>Content to classify</p>
<Textarea
className="mb-12 min-h-[50vh] w-full"
defaultValue={content}
onChange={e => _setContent(e.target.value)}
/>
<Button onClick={submitMessage}>Generate report</Button>
</div>
</>
) : (
<div className="">
<div>
<p>Status: {loading ? "fetching" : submitted ? "submitted" : "idle"}</p>
</div>
<Card>
<CardHeader>
<CardTitle>Tasks</CardTitle>
</CardHeader>
<CardContent>
<pre>{JSON.stringify(data.tasks, null, 2)}</pre>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Calendar</CardTitle>
</CardHeader>
<CardContent>
<pre>{JSON.stringify(data.calendar, null, 2)}</pre>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Ideas</CardTitle>
</CardHeader>
<CardContent>
{data.insights ? (
data.insights.map((idea, i) => (
<div key={i} className="py-2">
<h2 className="text-lg pb-2">{idea.topic}</h2>
<p className="pl-2">{idea.detailed_description}</p>
</div>
))
) : (
<p>No ideas yet!</p>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>People</CardTitle>
</CardHeader>
<CardContent>
{data.people ? (
data.people.map((person, i) => (
<div key={i} className="py-2">
<h2 className="text-lg">
{person.name} - {person.role}
</h2>
<p className="pl-2">{person.context}</p>
</div>
))
) : (
<p>No people yet!</p>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Follow Ups</CardTitle>
</CardHeader>
<CardContent>
<pre>{JSON.stringify(data.follow_ups, null, 2)}</pre>
</CardContent>
</Card>
</div>
)
}
8 changes: 5 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { ScrollArea } from "@/components/ui/scroll-area"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

import { ContentAnalysis } from "./content-analysis"
import { Dashboard } from "./dashboard"

export default function page() {
Expand All @@ -12,13 +13,14 @@ export default function page() {
<Tabs defaultValue="overview" className="space-y-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics" disabled>
Analytics
</TabsTrigger>
<TabsTrigger value="parser">Content Parser</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4">
<Dashboard />
</TabsContent>
<TabsContent value="parser" className="space-y-4">
<ContentAnalysis />
</TabsContent>
</Tabs>
</div>
</ScrollArea>
Expand Down
80 changes: 80 additions & 0 deletions src/app/schemas/content-classification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { z } from "zod"

export const classification = z.object({
people: z
.object({
name: z.string(),
role: z.string().nullable(),
email: z.string().nullable(),
phone: z.string().nullable(),
location: z.string().nullable(),
department: z.string().nullable(),
company: z.string().nullable(),
context: z.string({
description: "What is the context of this person in the content?"
})
})
.array(),
calendar: z
.object(
{
event_name: z.string(),
start_time: z.date().nullable(),
end_time: z.date().nullable(),
location: z.string().nullable(),
invitees: z
.string({ description: "Invitees should be a comma separated list of emails" })
.nullable()
},
{
description:
"Is there a calendar event in this piece of content that we should be aware of?"
}
)
.array(),
tasks: z
.object(
{
task_name: z.string(),
due_date: z.date().nullable(),
start_date: z.date().nullable(),
priority: z.string().nullable(),
description: z.string().nullable()
},
{
description: "Is there a task in this piece of content that we should be aware of?"
}
)
.array(),
insights: z
.object(
{
topic: z.string({ description: "What is the insight about?" }),
detailed_description: z.string({
description: "What is the insight about in more thorough detail?"
}),
date: z.string({ description: "When was this thought had?" }) // zodDate not supported error?
// related: z.string({ description: "What other thoughts are related to this one?" }).nullable() THIS SHOULD CALL ANOTHER FUNCTION TO FIND RELATED NOTES!
},
{
description: `Is there a insight in this piece of content that the reader should be aware of?

Each insight should be a separate and distinct thought or idea.`
}
)
.array(),

follow_ups: z
.object(
{
follow_up: z.string({ description: "What is the follow up about?" }),
due_date: z.date({ description: "When is the follow up due?" }),
completed: z.boolean({ description: "Is the follow up completed?" })
},
{
description:
"Knowing the context of the content when would and what would be a good time to follow up?"
}
)
.array()
})