-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add the backend api #10
base: main
Are you sure you want to change the base?
Changes from all commits
4c4fea2
191c9f8
1f740c0
69c5edc
f978e86
d8a54fe
bbc8d85
09fdc69
bce263a
fa444d6
4c9373e
77d1b87
536f2cc
5b90e7d
c548376
1377967
5bbe439
4c4bdd3
7f0fa63
93edddc
abcc79d
f92b2f8
17dfa66
4cddb22
80c713a
e6e14c3
6dba50b
a6895fa
33e910b
349068b
3190c66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { query, mutation } from "./_generated/server"; | ||
import { v } from "convex/values"; | ||
|
||
export const list = query({ | ||
args: { roomId: v.id("rooms") }, | ||
handler: async (ctx, args) => { | ||
// Grab the most recent messages. | ||
const messages = await ctx.db | ||
.query("messages") | ||
.filter((q) => q.eq(q.field("roomId"), args.roomId)) | ||
.order("desc") | ||
.collect(); | ||
// Reverse the list so that it's in a chronological order. | ||
const messageWithUsername = await Promise.all( | ||
messages.map(async (message) => { | ||
// Find the likes for each message | ||
const player = await ctx.db.get(message.playerId); | ||
// Join the count of likes with the message data | ||
return { | ||
...message, | ||
// Format smileys | ||
username: player?.username, | ||
}; | ||
}), | ||
); | ||
// Reverse the list so that it's in chronological order. | ||
return messageWithUsername.reverse(); | ||
}, | ||
}); | ||
|
||
// player sends a message in a room | ||
export const send = mutation({ | ||
args: { body: v.string(), playerId: v.id("player"), roomId: v.id("rooms") }, | ||
handler: async (ctx, args) => { | ||
// Send a new message. | ||
await ctx.db.insert("messages", { | ||
body: args.body, | ||
playerId: args.playerId, | ||
roomId: args.roomId, | ||
}); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
"use node"; | ||
|
||
import { action } from "./_generated/server"; | ||
import { v } from "convex/values"; | ||
import { api } from "../convex/_generated/api"; | ||
import OpenAI from "openai"; | ||
|
||
// Initialize the OpenAI client with the given API key | ||
const openai = new OpenAI({ | ||
apiKey: process.env.OPENAI_API_KEY, // This is also the default, can be omitted | ||
}); | ||
// start a game | ||
export const startGames = action({ | ||
args: { | ||
topic: v.string(), | ||
numberOfPlayer: v.number(), | ||
roomId: v.id("rooms"), | ||
}, | ||
handler: async (ctx, args) => { | ||
try { | ||
const checkTopicResponse = await openai.completions.create({ | ||
model: "text-davinci-003", | ||
prompt: `Is the topic ${args.topic} good? | ||
A good topic must be a single or two-word topic like 'animal', 'plants', 'cars', 'celebrities', etc. | ||
Also, a good topic must be a topic from which other words that can be drawn can be found. | ||
Anything that can be suitable for all audiences. | ||
A bad topic is something sexual or harassing or something like "hello" is also a bad topic. | ||
Your answer should just be true or false.`, | ||
}); | ||
|
||
if (checkTopicResponse.choices[0]?.text.includes("false")) { | ||
return { | ||
message: "The backend got an error: It's a bad topic!", | ||
}; | ||
} | ||
|
||
const response = await openai.completions.create({ | ||
model: "text-davinci-003", | ||
prompt: `Suggest ${args.numberOfPlayer} words for an topic that is '${args.topic}'`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this prompt is not so specific as it has to be. I just checked it in ChatGPT and the answer is not an array (as expected). You must be very specific, eg: const prompt = `Give me a list of ${args.nnumberOfPlayer} word related
to the topic "${args.topic}" as a json array format. Don't
include anything else in your response. The json array
must be as this: '{"words": ["word1", "word2", "word3"]}'.` This prompt is just an example and we're not sure that it works. Maybe you need to test it in ChatGPT and show in this pr some examples of what ChatGPT returned. In your code, you will need to check if the response is in the expected format and try again if it fails. Limit the tries to 10 or something like that to avoid executing infinite calls to openai. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This api I can't test, because I can't use it In China,So if you have the pay key, you can fix this problem There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can test using ChatGPT from https://chat.openai.com/ , but I'll try to get a key |
||
}); | ||
|
||
// Pull the message content out of the response | ||
const words = response.choices[0]?.text; | ||
// save the words and the topic into the database | ||
if (words && words.length > 0) { | ||
await ctx.runMutation(api.player.startGames, { | ||
topic: args.topic, | ||
words: words, | ||
roomId: args.roomId, | ||
}); | ||
return words; | ||
} else { | ||
console.error("The Open AI return nothing"); | ||
return { | ||
message: | ||
"The backend got an error: The maximum amount of users is 8!", | ||
}; | ||
} | ||
} catch (error: unknown) { | ||
if (error instanceof OpenAI.APIError) { | ||
console.error("Status", error.status); // e.g. 401 | ||
console.error("Message", error.message); // e.g. The authentication token you passed was invalid... | ||
console.error("Error Code", error.code); // e.g. 'invalid_api_key' | ||
console.error("Error Type", error.type); // e.g. 'invalid_request_error' | ||
return { message: "The backend got an error", error: error }; | ||
} else { | ||
console.error(error); | ||
return { message: "The backend got an error", error: error }; | ||
} | ||
} | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { query, mutation } from "./_generated/server"; | ||
import { v } from "convex/values"; | ||
|
||
// start a game | ||
export const startGames = mutation({ | ||
args: { topic: v.string(), words: v.string(), roomId: v.id("rooms") }, | ||
handler: async (ctx, args) => { | ||
await ctx.db.patch(args.roomId, { | ||
topic: args.topic, | ||
words: args.words, | ||
}); | ||
// update the player who in this room | ||
const playerList = await ctx.db | ||
.query("player") | ||
.filter((q) => q.eq(q.field("roomId"), args.roomId)) | ||
.collect(); | ||
for (let i = 0; i < playerList.length; i++) { | ||
const player = playerList[i]; | ||
if (player) { | ||
await ctx.db.patch(player._id, { roomId: args.roomId, score: 0 }); | ||
} else { | ||
console.error("The data error"); | ||
} | ||
} | ||
}, | ||
}); | ||
|
||
// check the user's word is correct or not | ||
export const checkWordFromUser = mutation({ | ||
args: { | ||
playerId: v.id("player"), | ||
wordFromUser: v.string(), | ||
roomId: v.id("rooms"), | ||
}, | ||
handler: async (ctx, args) => { | ||
const room = await ctx.db.get(args.roomId); | ||
// Update the score | ||
if (room?.words?.includes(args.wordFromUser)) { | ||
const player = await ctx.db.get(args.playerId); | ||
const newScore = player?.score ? player?.score + 1 : 1; | ||
ctx.db.patch(args.playerId, { score: newScore }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have to check if the user have not answered it yet. If you don't check it, I can spam any of the word and get unlimited score. Also, check that the word is not my word. I shouldn't be able to get score by sending my own word. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, here can change use your idea, thank you |
||
} | ||
}, | ||
}); | ||
|
||
// get all the players in a room | ||
export const getPlayersByRoomId = query({ | ||
args: { _id: v.id("rooms") }, | ||
handler: async (ctx, args) => { | ||
await ctx.db | ||
.query("player") | ||
.filter((q) => q.eq(q.field("roomId"), args._id)) | ||
.collect(); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { query, mutation } from "./_generated/server"; | ||
import { v } from "convex/values"; | ||
|
||
// create room | ||
export const createRoom = mutation({ | ||
args: { | ||
username: v.string(), | ||
timeLimit: v.number(), | ||
pairs: v.boolean(), | ||
chatEnabled: v.boolean(), | ||
}, | ||
handler: async (ctx, args) => { | ||
const roomName = args.username + "'s Room"; | ||
const roomId = await ctx.db.insert("rooms", { | ||
roomName, | ||
timeLimit: args.timeLimit, | ||
pairs: args.pairs, | ||
chatEnabled: args.chatEnabled, | ||
}); | ||
const playerId = await ctx.db.insert("player", { | ||
username: args.username, | ||
roomId: roomId, | ||
}); | ||
return { playId: playerId, roomId: roomId }; | ||
}, | ||
}); | ||
|
||
// let somebody join into room | ||
export const joinRoomByRoomId = mutation({ | ||
args: { username: v.string(), roomId: v.id("rooms") }, | ||
handler: async (ctx, args) => { | ||
const players = await ctx.db | ||
.query("player") | ||
.filter((q) => q.eq(q.field("roomId"), args.roomId)) | ||
.collect(); | ||
if (players.length >= 8) { | ||
return { | ||
message: "The backend got an error: The maximum amount of users is 8!", | ||
}; | ||
} | ||
await ctx.db.insert("player", { | ||
username: args.username, | ||
roomId: args.roomId, | ||
}); | ||
return ctx.db | ||
.query("player") | ||
.filter((q) => q.eq(q.field("roomId"), args.roomId)) | ||
.collect(); | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { defineSchema, defineTable } from "convex/server"; | ||
import { v } from "convex/values"; | ||
|
||
export default defineSchema({ | ||
player: defineTable({ | ||
username: v.string(), | ||
roomId: v.id("rooms"), | ||
score: v.optional(v.number()), | ||
}), | ||
rooms: defineTable({ | ||
roomName: v.string(), | ||
topic: v.optional(v.string()), | ||
words: v.optional(v.string()), | ||
chatEnabled: v.boolean(), | ||
timeLimit: v.number(), | ||
pairs: v.boolean(), | ||
}), | ||
messages: defineTable({ | ||
playerId: v.id("player"), | ||
roomId: v.id("rooms"), | ||
body: v.string(), | ||
}), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a check that if the user that sends the the request to start the game is the creator of the room