diff --git a/.prettierrc.cjs b/.prettierrc.cjs index 3598602..216e445 100644 --- a/.prettierrc.cjs +++ b/.prettierrc.cjs @@ -9,7 +9,14 @@ module.exports = { bracketSameLine: false, arrowParens: 'always', trailingComma: 'none', - importOrder: ['^node:.*', '', '^(~/(.*)$)', '^[./]'], + importOrder: [ + '^node:.*', + 'server-only', + 'openai/shims/web', + '', + '^([~@]/(.*)$)', + '^[./]' + ], importOrderSeparation: true, importOrderSortSpecifiers: true, importOrderGroupNamespaceSpecifiers: true diff --git a/playground/.eslintrc.json b/playground/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/playground/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 0000000..fd3dbb5 --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/playground/app/assistants/[assistantId]/components/code-viewer.tsx b/playground/app/assistants/[assistantId]/components/code-viewer.tsx new file mode 100644 index 0000000..b711a47 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/code-viewer.tsx @@ -0,0 +1,93 @@ +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger +} from '@/components/ui/dialog' + +export function CodeViewer() { + return ( + + + + + + + + View code + + + You can use the following code to start integrating your current + prompt and settings into your application. + + + +
+
+
+              
+                
+                  import os
+                
+                
+                  import openai
+                
+                
+                
+                  openai.api_key = os.getenv(
+                  
+                    "OPENAI_API_KEY"
+                  
+                  )
+                
+                
+                response = openai.Completion.create(
+                
+                  {' '}
+                  model=
+                  "davinci",
+                
+                
+                  {' '}
+                  prompt="",
+                
+                
+                  {' '}
+                  temperature=0.9,
+                
+                
+                  {' '}
+                  max_tokens=5,
+                
+                
+                  {' '}
+                  top_p=1,
+                
+                
+                  {' '}
+                  frequency_penalty=0,
+                
+                
+                  {' '}
+                  presence_penalty=0,
+                
+                )
+              
+            
+
+ +
+

+ Your API Key can be found here. You should use environment + variables or a secret management tool to expose your key to your + applications. +

+
+
+
+
+ ) +} diff --git a/playground/app/assistants/[assistantId]/components/maxlength-selector.tsx b/playground/app/assistants/[assistantId]/components/maxlength-selector.tsx new file mode 100644 index 0000000..51bfcc3 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/maxlength-selector.tsx @@ -0,0 +1,58 @@ +'use client' + +import * as React from 'react' +import type { SliderProps } from '@radix-ui/react-slider' + +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' +import { Label } from '@/components/ui/label' +import { Slider } from '@/components/ui/slider' + +interface MaxLengthSelectorProps { + defaultValue: SliderProps['defaultValue'] +} + +export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) { + const [value, setValue] = React.useState(defaultValue) + + return ( +
+ + +
+
+ + + + {value} + +
+ + +
+
+ + + The maximum number of tokens to generate. Requests can use up to 2,048 + or 4,000 tokens, shared between prompt and completion. The exact limit + varies by model. + +
+
+ ) +} diff --git a/playground/app/assistants/[assistantId]/components/model-selector.tsx b/playground/app/assistants/[assistantId]/components/model-selector.tsx new file mode 100644 index 0000000..4ece787 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/model-selector.tsx @@ -0,0 +1,173 @@ +'use client' + +import * as React from 'react' +import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons' +import type { PopoverProps } from '@radix-ui/react-popover' + +import { Button } from '@/components/ui/button' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from '@/components/ui/command' +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' +import { Label } from '@/components/ui/label' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/components/ui/popover' +import { useMutationObserver } from '@/hooks/use-mutation-observer' +import { cn } from '@/lib/utils' + +import type { Model, ModelType } from '../data/models' + +interface ModelSelectorProps extends PopoverProps { + types: readonly ModelType[] + models: Model[] +} + +export function ModelSelector({ models, types, ...props }: ModelSelectorProps) { + const [open, setOpen] = React.useState(false) + const [selectedModel, setSelectedModel] = React.useState(models[0]) + const [peekedModel, setPeekedModel] = React.useState(models[0]) + + return ( +
+ + + + + + + The model which will generate the completion. Some models are suitable + for natural language tasks, others specialize in code. Learn more. + + + + + + + + + + + +
+

{peekedModel.name}

+
+ {peekedModel.description} +
+ + {peekedModel.strengths ? ( +
+
+ Strengths +
+
    + {peekedModel.strengths} +
+
+ ) : null} +
+
+ + + + + + No Models found. + + + + {types.map((type) => ( + + {models + .filter((model) => model.type === type) + .map((model) => ( + setPeekedModel(model)} + onSelect={() => { + setSelectedModel(model) + setOpen(false) + }} + /> + ))} + + ))} + + +
+
+
+
+ ) +} + +interface ModelItemProps { + model: Model + isSelected: boolean + onSelect: () => void + onPeek: (model: Model) => void +} + +function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) { + const ref = React.useRef(null) + + useMutationObserver(ref, (mutations: any[]) => { + for (const mutation of mutations) { + if (mutation.type === 'attributes') { + if (mutation.attributeName === 'aria-selected') { + onPeek(model) + } + } + } + }) + + return ( + + {model.name} + + + + ) +} diff --git a/playground/app/assistants/[assistantId]/components/preset-actions.tsx b/playground/app/assistants/[assistantId]/components/preset-actions.tsx new file mode 100644 index 0000000..a122362 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/preset-actions.tsx @@ -0,0 +1,133 @@ +'use client' + +import * as React from 'react' +import { Dialog } from '@radix-ui/react-dialog' +import { DotsHorizontalIcon } from '@radix-ui/react-icons' +import { toast } from 'sonner' + +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle +} from '@/components/ui/alert-dialog' +import { Button } from '@/components/ui/button' +import { + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu' +import { Label } from '@/components/ui/label' +import { Switch } from '@/components/ui/switch' + +export function PresetActions() { + const [open, setIsOpen] = React.useState(false) + const [showDeleteDialog, setShowDeleteDialog] = React.useState(false) + + return ( + <> + + + + + + + setIsOpen(true)}> + Content filter preferences + + + + + setShowDeleteDialog(true)} + className='text-red-600' + > + Delete preset + + + + + + + + Content filter preferences + + + The content filter flags text that may violate our content policy. + It's powered by our moderation endpoint which is free to use + to moderate your OpenAI API traffic. Learn more. + + + +
+

+ Playground Warnings +

+ +
+ + +
+
+ + + + +
+
+ + + + + Are you sure absolutely sure? + + + This action cannot be undone. This preset will no longer be + accessible by you or others you've shared it with. + + + + + Cancel + + + + + + + ) +} diff --git a/playground/app/assistants/[assistantId]/components/preset-save.tsx b/playground/app/assistants/[assistantId]/components/preset-save.tsx new file mode 100644 index 0000000..3433e23 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/preset-save.tsx @@ -0,0 +1,49 @@ +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger +} from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' + +export function PresetSave() { + return ( + + + + + + + + Save preset + + + This will save the current playground state as a preset which you + can access later or share with others. + + + +
+
+ + +
+ +
+ + +
+
+ + + + +
+
+ ) +} diff --git a/playground/app/assistants/[assistantId]/components/preset-selector.tsx b/playground/app/assistants/[assistantId]/components/preset-selector.tsx new file mode 100644 index 0000000..9cab3b2 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/preset-selector.tsx @@ -0,0 +1,90 @@ +'use client' + +import * as React from 'react' +import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons' +import type { PopoverProps } from '@radix-ui/react-popover' +import { useRouter } from 'next/navigation' + +import { Button } from '@/components/ui/button' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem +} from '@/components/ui/command' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/components/ui/popover' +import { cn } from '@/lib/utils' + +import type { Preset } from '../data/presets' + +interface PresetSelectorProps extends PopoverProps { + presets: Preset[] +} + +export function PresetSelector({ presets, ...props }: PresetSelectorProps) { + const [open, setOpen] = React.useState(false) + const [selectedPreset, setSelectedPreset] = React.useState() + const router = useRouter() + + return ( + + + + + + + + + + No presets found. + + + {presets.map((preset) => ( + { + setSelectedPreset(preset) + setOpen(false) + }} + > + {preset.name} + + + + ))} + + + + router.push('/examples')}> + More examples + + + + + + ) +} diff --git a/playground/app/assistants/[assistantId]/components/preset-share.tsx b/playground/app/assistants/[assistantId]/components/preset-share.tsx new file mode 100644 index 0000000..54759e0 --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/preset-share.tsx @@ -0,0 +1,52 @@ +import { CopyIcon } from '@radix-ui/react-icons' + +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/components/ui/popover' + +export function PresetShare() { + return ( + + + + + + +
+

Share preset

+ +

+ Anyone who has this link and an OpenAI account will be able to view + this. +

+
+ +
+
+ + + +
+ + +
+
+
+ ) +} diff --git a/playground/app/assistants/[assistantId]/components/temperature-selector.tsx b/playground/app/assistants/[assistantId]/components/temperature-selector.tsx new file mode 100644 index 0000000..9803c9a --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/temperature-selector.tsx @@ -0,0 +1,59 @@ +'use client' + +import * as React from 'react' +import type { SliderProps } from '@radix-ui/react-slider' + +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' +import { Label } from '@/components/ui/label' +import { Slider } from '@/components/ui/slider' + +interface TemperatureSelectorProps { + defaultValue: SliderProps['defaultValue'] +} + +export function TemperatureSelector({ + defaultValue +}: TemperatureSelectorProps) { + const [value, setValue] = React.useState(defaultValue) + + return ( +
+ + +
+
+ + + {value} + +
+ + +
+
+ + + Controls randomness: lowering results in less random completions. As + the temperature approaches zero, the model will become deterministic + and repetitive. + +
+
+ ) +} diff --git a/playground/app/assistants/[assistantId]/components/top-p-selector.tsx b/playground/app/assistants/[assistantId]/components/top-p-selector.tsx new file mode 100644 index 0000000..cfe487a --- /dev/null +++ b/playground/app/assistants/[assistantId]/components/top-p-selector.tsx @@ -0,0 +1,57 @@ +'use client' + +import * as React from 'react' +import type { SliderProps } from '@radix-ui/react-slider' + +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' +import { Label } from '@/components/ui/label' +import { Slider } from '@/components/ui/slider' + +interface TopPSelectorProps { + defaultValue: SliderProps['defaultValue'] +} + +export function TopPSelector({ defaultValue }: TopPSelectorProps) { + const [value, setValue] = React.useState(defaultValue) + + return ( +
+ + +
+
+ + + + {value} + +
+ + +
+
+ + + Control diversity via nucleus sampling: 0.5 means half of all + likelihood-weighted options are considered. + +
+
+ ) +} diff --git a/playground/app/assistants/[assistantId]/data/models.ts b/playground/app/assistants/[assistantId]/data/models.ts new file mode 100644 index 0000000..b526d4e --- /dev/null +++ b/playground/app/assistants/[assistantId]/data/models.ts @@ -0,0 +1,62 @@ +export const types = ['GPT-3', 'Codex'] as const + +export type ModelType = (typeof types)[number] + +export interface Model { + id: string + name: string + description: string + strengths?: string + type: Type +} + +export const models: Model[] = [ + { + id: 'c305f976-8e38-42b1-9fb7-d21b2e34f0da', + name: 'text-davinci-003', + description: + 'Most capable GPT-3 model. Can do any task the other models can do, often with higher quality, longer output and better instruction-following. Also supports inserting completions within text.', + type: 'GPT-3', + strengths: + 'Complex intent, cause and effect, creative generation, search, summarization for audience' + }, + { + id: '464a47c3-7ab5-44d7-b669-f9cb5a9e8465', + name: 'text-curie-001', + description: 'Very capable, but faster and lower cost than Davinci.', + type: 'GPT-3', + strengths: + 'Language translation, complex classification, sentiment, summarization' + }, + { + id: 'ac0797b0-7e31-43b6-a494-da7e2ab43445', + name: 'text-babbage-001', + description: 'Capable of straightforward tasks, very fast, and lower cost.', + type: 'GPT-3', + strengths: 'Moderate classification, semantic search' + }, + { + id: ' be638fb1-973b-4471-a49c-290325085802', + name: 'text-ada-001', + description: + 'Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost.', + type: 'GPT-3', + strengths: + 'Parsing text, simple classification, address correction, keywords' + }, + { + id: 'b43c0ea9-5ad4-456a-ae29-26cd77b6d0fb', + name: 'code-davinci-002', + description: + 'Most capable Codex model. Particularly good at translating natural language to code. In addition to completing code, also supports inserting completions within code.', + type: 'Codex' + }, + { + id: 'bbd57291-4622-4a21-9eed-dd6bd786fdd1', + name: 'code-cushman-001', + description: + 'Almost as capable as Davinci Codex, but slightly faster. This speed advantage may make it preferable for real-time applications.', + type: 'Codex', + strengths: 'Real-time application where low-latency is preferable' + } +] diff --git a/playground/app/assistants/[assistantId]/data/presets.ts b/playground/app/assistants/[assistantId]/data/presets.ts new file mode 100644 index 0000000..b75c821 --- /dev/null +++ b/playground/app/assistants/[assistantId]/data/presets.ts @@ -0,0 +1,47 @@ +export interface Preset { + id: string + name: string +} + +export const presets: Preset[] = [ + { + id: '9cb0e66a-9937-465d-a188-2c4c4ae2401f', + name: 'Grammatical Standard English' + }, + { + id: '61eb0e32-2391-4cd3-adc3-66efe09bc0b7', + name: 'Summarize for a 2nd grader' + }, + { + id: 'a4e1fa51-f4ce-4e45-892c-224030a00bdd', + name: 'Text to command' + }, + { + id: 'cc198b13-4933-43aa-977e-dcd95fa30770', + name: 'Q&A' + }, + { + id: 'adfa95be-a575-45fd-a9ef-ea45386c64de', + name: 'English to other languages' + }, + { + id: 'c569a06a-0bd6-43a7-adf9-bf68c09e7a79', + name: 'Parse unstructured data' + }, + { + id: '15ccc0d7-f37a-4f0a-8163-a37e162877dc', + name: 'Classification' + }, + { + id: '4641ef41-1c0f-421d-b4b2-70fe431081f3', + name: 'Natural language to Python' + }, + { + id: '48d34082-72f3-4a1b-a14d-f15aca4f57a0', + name: 'Explain code' + }, + { + id: 'dfd42fd5-0394-4810-92c6-cc907d3bfd1a', + name: 'Chat' + } +] diff --git a/playground/app/assistants/[assistantId]/page.tsx b/playground/app/assistants/[assistantId]/page.tsx new file mode 100644 index 0000000..bcb9adc --- /dev/null +++ b/playground/app/assistants/[assistantId]/page.tsx @@ -0,0 +1,365 @@ +import { CounterClockwiseClockIcon } from '@radix-ui/react-icons' +import type { Metadata } from 'next' +import Image from 'next/image' + +import { Button } from '@/components/ui/button' +import { + HoverCard, + HoverCardContent, + HoverCardTrigger +} from '@/components/ui/hover-card' +import { Label } from '@/components/ui/label' +import { Separator } from '@/components/ui/separator' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Textarea } from '@/components/ui/textarea' +import { getAssistantById } from '@/lib/openai' + +import { CodeViewer } from './components/code-viewer' +import { MaxLengthSelector } from './components/maxlength-selector' +import { ModelSelector } from './components/model-selector' +import { PresetActions } from './components/preset-actions' +import { PresetSave } from './components/preset-save' +import { PresetSelector } from './components/preset-selector' +import { PresetShare } from './components/preset-share' +import { TemperatureSelector } from './components/temperature-selector' +import { TopPSelector } from './components/top-p-selector' +import { models, types } from './data/models' +import { presets } from './data/presets' + +export const metadata: Metadata = { + title: 'Playground', + description: 'The OpenAI Playground built using the components.' +} + +export default async function AssistantPage({ + params: { assistantId } +}: { + params: { + assistantId: string + } +}) { + const assistant = await getAssistantById(assistantId) + + // TODO + + return ( + <> +
+ Playground + + Playground +
+ +
+
+

Playground

+
+ + + + +
+ + +
+ + +
+
+ + + + +
+
+
+
+ + + + Mode + + + + + Choose the interface that best suits your task. You can + provide: a simple prompt to complete, starting and ending + text to insert a completion within, or some text with + instructions to edit it. + + + + + + Complete + + + + + + + + + + + + + Insert + + + + + + + + + + Edit + + + + + + + + + + +
+ + + + + + + + +
+ +
+ +
+