Skip to content

Instantly share code, notes, and snippets.

@eugene-yaroslavtsev
Last active April 7, 2024 05:00
Show Gist options
  • Save eugene-yaroslavtsev/bcbc6c52b0f9662b1634f9a0f87e4d27 to your computer and use it in GitHub Desktop.
Save eugene-yaroslavtsev/bcbc6c52b0f9662b1634f9a0f87e4d27 to your computer and use it in GitHub Desktop.
ChatGPT prompt sharing app
schema.prisma
```
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(uuid())
createdAt DateTime @default(now())
email String @unique
entries Entry[]
}
model Entry {
id String @id @default(uuid())
createdAt DateTime @default(now())
userId String
user User @relation(fields: [userId], references: [id])
prompt String
templateVariables Json
versions Version[]
likes Int @default(0)
dislikes Int @default(0)
}
model Version {
id String @id @default(uuid())
createdAt DateTime @default(now())
entryId String
entry Entry @relation(fields: [entryId], references: [id])
responseText String
}
```
src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3001);
}
bootstrap();
src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PrismaModule } from './prisma/prisma.module';
import { EntriesModule } from './entries/entries.module';
@Module({
imports: [
ConfigModule.forRoot(),
PrismaModule,
EntriesModule,
],
})
export class AppModule {}
src/prisma/prisma.service.ts
import { PrismaClient } from '@prisma/client';
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
src/prisma/prisma.module.ts
import { Module } from '@nestjs/common'
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
src/entries/entries.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { EntriesService } from './entries.service';
import { CreateEntryDto } from './dto/create-entry.dto';
import { UpdateEntryDto } from './dto/update-entry.dto';
import { Entry } from './entities/entry.entity';
@Controller('entries')
export class EntriesController {
constructor(
private readonly entriesService: EntriesService,
private readonly chatgptService: ChatgptService,
) {}
@Post()
create(@Body() createEntryDto: CreateEntryDto) {
return this.entriesService.create(createEntryDto);
}
@Get()
findAll() {
return this.entriesService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.entriesService.findOne(id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateEntryDto: UpdateEntryDto) {
return this.entriesService.update(id, updateEntryDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.entriesService.remove(id);
}
@Post(':id/versions')
async createVersion(@Param('id') entryId: string): Promise<Entry> {
const entry = await this.entriesService.findOne(entryId);
const promptWithVars = await this.entriesService.interpolatePrompt(entry);
const response = await this.chatgptService.sendPrompt(promptWithVars);
return this.entriesService.createVersion(entryId, response);
}
@Patch(':id/like')
async likeEntry(@Param('id') entryId: string): Promise<Entry> {
return this.entriesService.likeEntry(entryId);
}
@Patch(':id/dislike')
async dislikeEntry(@Param('id') entryId: string): Promise<Entry> {
return this.entriesService.dislikeEntry(entryId);
}
}
src/entries/entries.module.ts
import { Module } from '@nestjs/common'
import { EntriesService } from './entries.service';
import { EntriesController } from './entries.controller';
import { PrismaModule } from '../prisma/prisma.module';
import { ChatgptModule } from '../chatgpt/chatgpt.module';
@Module({
imports: [PrismaModule, ChatgptModule],
controllers: [EntriesController],
providers: [EntriesService],
})
export class EntriesModule {}
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateEntryDto } from './dto/create-entry.dto';
import { UpdateEntryDto } from './dto/update-entry.dto';
import { Entry } from './entities/entry.entity';
@Injectable()
export class EntriesService {
constructor(private prisma: PrismaService) {}
// ... all other methods from previous responses
async interpolatePrompt(entry: Entry): Promise<string> {
const templateVars = JSON.parse(entry.templateVariables);
let promptWithVars = entry.prompt;
for (const key in templateVars) {
const regex = new RegExp(`\\$${key}`, 'g');
promptWithVars = promptWithVars.replace(regex, templateVars[key]);
}
return promptWithVars;
}
}
src/chatgpt/chatgpt.module.ts
import { Module } from '@nestjs/common';
import { ChatgptService } from './chatgpt.service';
@Module({
providers: [ChatgptService],
exports: [ChatgptService],
})
export class ChatgptModule {}
src/chatgpt/chatgpt.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
@Injectable()
export class ChatgptService {
constructor(private configService: ConfigService) {}
async sendPrompt(prompt: string): Promise<string> {
const response = await axios.post(
'https://api.openai.com/v1/engines/davinci-codex/completions',
{
prompt,
max_tokens: 100,
n: 1,
stop: null,
temperature: 0.7,
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.configService.get('OPENAI_API_KEY')}`,
},
},
);
return response.data.choices[0].text.trim();
}
}
src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);
reportWebVitals();
src/App.tsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import EntryList from './components/EntryList';
import EntryCreator from './components/EntryCreator';
import SingleEntryView from './components/SingleEntryView';
import VersionCreator from './components/VersionCreator';
import { Entry } from './models/entry.model';
import { Box } from '@mui/material';
const apiUrl = 'http://localhost:3001/entries';
function App() {
const [entries, setEntries] = useState<Entry[]>([]);
const [selectedEntry, setSelectedEntry] = useState<Entry | null>(null);
const fetchEntries = async () => {
const response = await axios.get<Entry[]>(apiUrl);
setEntries(response.data);
};
useEffect(() => {
fetchEntries();
}, []);
const handleCreateEntry = async (entry: Entry) => {
await axios.post(apiUrl, entry);
fetchEntries();
};
const handleCreateVersion = async () => {
await axios.post(`${apiUrl}/${selectedEntry.id}/versions`);
setSelectedEntry(null);
fetchEntries();
};
return (
<Box sx={{ flexGrow: 1, p: 2 }}>
<EntryCreator onCreateEntry={handleCreateEntry} />
<EntryList entries={entries} onSelectEntry={setSelectedEntry} />
{selectedEntry && (
<Box sx={{ mt: 2 }}>
<SingleEntryView entry={selectedEntry} />
<Box sx={{ mt: 2 }}>
<VersionCreator entry={selectedEntry} onCreateVersion={handleCreateVersion} />
</Box>
</Box>
)}
</Box>
);
}
export default App;
src/components/EntryList.tsx
import React from 'react';
import { Entry } from '../models/entry.model';
import { List, ListItem, ListItemText } from '@mui/material';
interface EntryListProps {
entries: Entry[];
onSelectEntry: (entry: Entry) => void;
}
const EntryList: React.FC<EntryListProps> = ({ entries, onSelectEntry }) => {
return (
<List>
{entries.map((entry) => (
<ListItem button key={entry.id} onClick={() => onSelectEntry(entry)}>
<ListItemText primary={entry.prompt} />
</ListItem>
))}
</List>
);
};
export default EntryList;
src/components/EntryCreator.tsx
import React, { useState } from 'react';
import { Entry } from '../models/entry.model';
import { TextField, Button, Box } from '@mui/material';
interface EntryCreatorProps {
onCreateEntry: (entry: Entry) => void;
}
const EntryCreator: React.FC<EntryCreatorProps> = ({ onCreateEntry }) => {
const [prompt, setPrompt] = useState('');
const [templateVariables, setTemplateVariables] = useState<Record<string, string>>({});
const handleCreateEntry = () => {
onCreateEntry({ prompt, templateVariables: JSON.stringify(templateVariables) });
setPrompt('');
setTemplateVariables({});
};
const handleVariableChange = (key: string, value: string) => {
setTemplateVariables((prevState) => ({ ...prevState, [key]: value }));
};
const variableInputs = Object.entries(templateVariables).map(([key, value]) => (
<TextField
key={key}
label={key}
value={value}
onChange={(e) => handleVariableChange(key, e.target.value)}
sx={{ mt: 1 }}
/>
));
const handleAddVariable = () => {
const key = `VAR_${Object.keys(templateVariables).length + 1}`;
setTemplateVariables((prevState) => ({ ...prevState, [key]: '' }));
};
return (
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<TextField
label="Prompt"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
fullWidth
/>
{variableInputs}
<Button variant="outlined" onClick={handleAddVariable} sx={{ mt: 1 }}>
Add Variable
</Button>
<Button variant="contained" onClick={handleCreateEntry} sx={{ mt: 1 }}>
Create Entry
</Button>
</Box>
);
};
export default EntryCreator;
src/components/SingleEntryView.tsx
import React from 'react';
import { Entry } from '../models/entry.model';
import { Box, Typography } from '@mui/material';
import ReactMarkdown from 'react-markdown';
interface SingleEntryViewProps {
entry: Entry;
}
const SingleEntryView: React.FC<SingleEntryViewProps> = ({ entry }) => {
const latestVersion = entry.versions[entry.versions.length - 1];
return (
<Box>
<Typography variant="h5" gutterBottom>
{entry.prompt}
</Typography>
<ReactMarkdown>{latestVersion.responseText}</ReactMarkdown>
</Box>
);
};
export default SingleEntryView;
src/components/VersionCreator.tsx
import React, { useState } from 'react';
import { Entry } from '../models/entry.model';
import { Button, CircularProgress, Box } from '@mui/material';
interface VersionCreatorProps {
entry: Entry;
onCreateVersion: () => void;
}
const VersionCreator: React.FC<VersionCreatorProps> = ({ entry, onCreateVersion }) => {
const [isLoading, setIsLoading] = useState(false);
const handleClick = async () => {
setIsLoading(true);
await onCreateVersion();
setIsLoading(false);
};
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Button variant="contained" onClick={handleClick} disabled={isLoading}>
Create New Version
</Button>
{isLoading && <CircularProgress size={24} sx={{ ml: 1 }} />}
</Box>
);
};
export default VersionCreator;
src/models/entry.model.ts
export interface Entry {
id: string;
createdAt: string;
prompt: string;
templateVariables: string;
versions: Version[];
likes: number;
dislikes: number;
}
export interface Version {
id: string;
createdAt: string;
responseText: string;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment