Skip to content

Commit

Permalink
Merge branch 'main' of github.com:webdevcody/survive-the-night-sim
Browse files Browse the repository at this point in the history
  • Loading branch information
webdevcody committed Oct 17, 2024
2 parents 35d7e79 + 0086cd9 commit c52346b
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Add optional environment variable/s for simulating real AI models without mockup

- `npx convex env set GEMINI_API_KEY YOUR_API_KEY`
- `npx convex env set OPENAI_API_KEY YOUR_API_KEY`
- `npx convex env set ANTHROPIC_API_KEY YOUR_API_KEY`
- `npx convex env set PERPLEXITY_API_KEY YOUR_API_KEY`

also, you may need to run, but I think the initial setup does that.

Expand Down
26 changes: 25 additions & 1 deletion app/play/[level]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,31 @@ export default function PlayLevelPage({
}, [map]);

if (!map) {
return <div>Loading...</div>;
return (
<div className="container mx-auto min-h-screen flex flex-col items-center py-12 pb-24 gap-8">
<div className="w-full flex justify-between items-center">
<Button variant="outline" asChild className="flex gap-2 items-center">
<Link href="/play" passHref>
<ChevronLeftIcon /> Play Different Night
</Link>
</Button>
{flags?.showTestPage && (
<Tabs
value={mode}
onValueChange={(value) => setMode(value as "play" | "test")}
>
<TabsList>
<TabsTrigger value="play">Play</TabsTrigger>
<TabsTrigger value="test">Test AI</TabsTrigger>
</TabsList>
</Tabs>
)}
</div>
<h1 className="text-3xl font-bold text-center">Night #{level}</h1>

<p>Loading...</p>
</div>
);
}

const handleRetryClicked = () => {
Expand Down
13 changes: 12 additions & 1 deletion app/play/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,23 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";

export default function PlayPage() {
const maps = useQuery(api.maps.getMaps);

if (!maps) {
return <div>Loading...</div>;
return (
<div className="container mx-auto min-h-screen py-12 pb-24 gap-8">
<h1 className="text-3xl font-bold mb-6 text-center">Choose a Night</h1>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{Array.from({ length: 6 }).map((_, index) => (
<Skeleton key={index} className="h-96" />
))}
</div>
</div>
);
}

return (
Expand Down
15 changes: 15 additions & 0 deletions components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"

function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
)
}

export { Skeleton }
8 changes: 8 additions & 0 deletions convex/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ export const AI_MODELS = [
model: "gpt-4o",
name: "OpenAI - 4o Mini",
},
{
model: "claude-3.5-sonnet",
name: "Claude 3.5 Sonnnet"
},
{
model: "perplexity-llama-3.1",
name: "Perplextity AI"
}
];

export const AI_MODEL_IDS = AI_MODELS.map((model) => model.model);
Expand Down
44 changes: 44 additions & 0 deletions models/claude-3-5-sonnet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Anthropic } from "@anthropic-ai/sdk";
import { type ModelHandler } from ".";

export const claude35sonnet: ModelHandler = async (prompt, map) => {
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});

const response = await anthropic.messages.create({
model: "claude-3-sonnet-20240307",
max_tokens: 1024,
temperature: 0,
system: prompt,
messages: [
{
role: "user",
content: JSON.stringify(map),
},
],
});

const content = response.content[0];

if (content.type !== "text") {
throw new Error("Unexpected response type from Claude");
}

const parsedResponse = JSON.parse(content.text);

// Validate the response structure
if (
!Array.isArray(parsedResponse.boxCoordinates) ||
!Array.isArray(parsedResponse.playerCoordinates) ||
typeof parsedResponse.reasoning !== "string"
) {
throw new Error("Invalid response structure");
}

return {
boxCoordinates: parsedResponse.boxCoordinates,
playerCoordinates: parsedResponse.playerCoordinates,
reasoning: parsedResponse.reasoning,
};
};
10 changes: 10 additions & 0 deletions models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { gemini15pro } from "./gemini-1.5-pro";
import { gpt4o } from "./gpt-4o";
import { claude35sonnet } from "./claude-3-5-sonnet";
import { perplexityModel } from "./perplexity-llama";

export type ModelHandler = (
prompt: string,
Expand Down Expand Up @@ -94,6 +96,14 @@ export async function runModel(
result = await gpt4o(prompt, map);
break;
}
case "claude-3.5-sonnet": {
result = await claude35sonnet(prompt, map);
break;
}
case "perplexity-llama-3.1": {
result = await perplexityModel(prompt, map);
break;
}
default: {
throw new Error(`Tried running unknown model '${modelId}'`);
}
Expand Down
87 changes: 87 additions & 0 deletions models/perplexity-llama.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import axios from 'axios';
import { z } from 'zod';
import { ModelHandler } from './index';

const PerplexityResponseSchema = z.object({
id: z.string(),
model: z.string(),
object: z.string(),
created: z.number(),
choices: z.array(
z.object({
index: z.number(),
finish_reason: z.string(),
message: z.object({
role: z.string(),
content: z.string(),
}),
delta: z.object({
role: z.string(),
content: z.string(),
}),
})
),
usage: z.object({
prompt_tokens: z.number(),
completion_tokens: z.number(),
total_tokens: z.number(),
}),
});

const GameResponseSchema = z.object({
reasoning: z.string(),
playerCoordinates: z.array(z.number()),
boxCoordinates: z.array(z.array(z.number())),
});

export const perplexityModel: ModelHandler = async (prompt: string, map: string[][]) => {
const apiKey = process.env.PERPLEXITY_API_KEY;
if (!apiKey) {
throw new Error('PERPLEXITY_API_KEY is not set in the environment variables');
}

const messages = [
{ role: 'system', content: 'Be precise and concise.' },
{ role: 'user', content: prompt },
{ role: 'user', content: JSON.stringify(map) },
];

const data = {
model: 'llama-3.1-sonar-large-128k-online',
messages,
temperature: 0.2,
top_p: 0.9,
return_citations: true,
search_domain_filter: ['perplexity.ai'],
return_images: false,
return_related_questions: false,
search_recency_filter: 'month',
top_k: 0,
stream: false,
presence_penalty: 0,
frequency_penalty: 1,
};

try {
const response = await axios.post('https://api.perplexity.ai/chat/completions', data, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});

const validatedResponse = PerplexityResponseSchema.parse(response.data);
const content = validatedResponse.choices[0].message.content;
const parsedContent = JSON.parse(content);
const gameResponse = GameResponseSchema.parse(parsedContent);

return {
boxCoordinates: gameResponse.boxCoordinates,
playerCoordinates: gameResponse.playerCoordinates,
reasoning: gameResponse.reasoning,
};
} catch (error) {
console.error('Failed to run Perplexity model Error:', error);
throw new Error('Failed to run Perplexity model');
}
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"lint": "next lint"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.29.1",
"@auth/core": "^0.34.2",
"@convex-dev/auth": "^0.0.71",
"@google/generative-ai": "^0.21.0",
Expand All @@ -25,6 +26,7 @@
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"convex": "^1.16.0",
Expand Down

0 comments on commit c52346b

Please sign in to comment.