Pārlūkot izejas kodu

Create route to create a new turn

Lee Morgan 1 mēnesi atpakaļ
vecāks
revīzija
d4c835788b

+ 12 - 0
docs/paths/game/session/turn/create.yaml

@@ -21,6 +21,18 @@ parameters:
       type: string
       format: uuid
       example: 019ec150-72ef-7f9e-b164-6dbbcc045e29
+requestBody:
+  content:
+    application/json:
+      schema:
+        type: object
+        required:
+          - content
+        properties:
+          content:
+            type: string
+            description: User response and instructions for the turn
+            example: Pull out my sword and swing at the enemy
 
 responses:
   "200":

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 3 - 1
docs/redoc-static.html


+ 2 - 42
src/controllers/game/session/create.rs

@@ -8,15 +8,10 @@ use crate::{
     models::{
         user::User,
         game::Game,
-        character::Character,
         session::Session,
         turn::Turn
     },
-    logic::prompt::{
-        PromptMessage,
-        PromptRole,
-        send_message
-    }
+    logic::prompt
 };
 
 #[derive(Serialize)]
@@ -35,9 +30,8 @@ pub async fn route(
     let user = user_auth(&db, &req, true).await?;
     let game_id = Uuid::parse_str(&path.into_inner())?;
     let (game, mut characters, sessions) = Game::get_full_game(&db, game_id.clone(), Some(user.id)).await?;
-    let prompt = new_session_prompt(game.game_context.clone(), characters.remove(0), sessions);
-    let response = send_message(prompt).await?;
     let session = Session::new(game_id);
+    let response = prompt(game, characters.remove(0), sessions, vec![], None, true).await?;
     let turn = Turn::new(
         session.id,
         response.input_tokens,
@@ -56,37 +50,3 @@ pub async fn route(
         tokens_remaining: tokens_remaining
     }))
 }
-
-fn new_session_prompt(
-    game_context: String,
-    character: Character,
-    sessions: Vec<Session>
-) -> Vec<PromptMessage> {
-    let mut text = "".to_string();
-
-    text += "You are acting as a game master for an RPG and running the game.\n";
-    text += &format!("Here is the context for the world: {}\n", game_context);
-    text += &format!(
-        "Character name is {}, here is a description of the character: {}\n",
-        character.name,
-        character.description
-    );
-
-    if sessions.len() == 0 {
-        text += "This is the first session in this game, there have been no previous sessions\n";
-    } else {
-        for (i, session) in sessions.iter().enumerate() {
-            match &session.summary {
-                Some(s) => {text += &format!("Summary of session {}: {}\n", i, s)},
-                None => ()
-            }
-        }
-    }
-
-    text += "Start off a new session for the player";
-
-    vec![PromptMessage {
-        role: PromptRole::System,
-        content: text
-    }]
-}

+ 1 - 0
src/controllers/game/session/mod.rs

@@ -1 +1,2 @@
 pub mod create;
+pub mod turn;

+ 71 - 0
src/controllers/game/session/turn/create.rs

@@ -0,0 +1,71 @@
+use actix_web::{HttpResponse, HttpRequest, web, post};
+use serde::{Serialize, Deserialize};
+use sqlx::PgPool;
+use uuid::Uuid;
+use crate::{
+    http_error::HttpError,
+    auth::user_auth,
+    models::{
+        game::Game,
+        turn::Turn,
+        user::User
+    },
+    logic::prompt
+};
+
+#[derive(Deserialize)]
+struct Body {
+    content: String
+}
+
+#[derive(Deserialize)]
+struct Path {
+    game_id: String
+}
+
+#[derive(Serialize)]
+struct Response {
+    turn: Turn,
+    tokens_remaining: i32
+}
+
+#[post("/game/{game_id}/session/{session_id}/turn")]
+pub async fn route(
+    db: web::Data<PgPool>,
+    path: web::Path<Path>,
+    web::Json(body): web::Json<Body>,
+    req: HttpRequest
+) -> Result<HttpResponse, HttpError> {
+    let user = user_auth(&db, &req, true).await?;
+    let game_id = Uuid::parse_str(&path.game_id)?;
+
+    let (game, mut characters, sessions) = Game::get_full_game(&db, game_id, Some(user.id)).await?;
+    let recent_session = sessions.iter().max_by_key(|s| s.started_at).unwrap();
+    let turns = Turn::find_by_session(&db, recent_session.id).await?;
+    let recent_session_id = recent_session.id;
+
+    let response = prompt(
+        game,
+        characters.remove(0),
+        sessions,
+        turns,
+        Some(body.content.clone()),
+        false
+    ).await?;
+
+    let new_turn = Turn::new(
+        recent_session_id,
+        response.input_tokens,
+        response.output_tokens,
+        body.content,
+        response.message
+    );
+
+    User::add_tokens(&db, user.id, -new_turn.tokens_used).await?;
+
+    let tokens_remaining = user.tokens - new_turn.tokens_used;
+    Ok(HttpResponse::Ok().json(Response {
+        turn: new_turn,
+        tokens_remaining: tokens_remaining
+    }))
+}

+ 1 - 0
src/controllers/game/session/turn/mod.rs

@@ -0,0 +1 @@
+pub mod create;

+ 1 - 1
src/logic/mod.rs

@@ -2,10 +2,10 @@ pub mod valid_password;
 pub mod hash_password;
 pub mod compare_password;
 pub mod create_cookie;
-
 pub mod prompt;
 
 pub use valid_password::valid_password;
 pub use hash_password::hash_password;
 pub use compare_password::compare_password;
 pub use create_cookie::create_cookie;
+pub use prompt::prompt;

+ 82 - 3
src/logic/prompt.rs

@@ -3,18 +3,24 @@ use serde::Serialize;
 use serde_json::{json, Value};
 use crate::{
     http_error::HttpError,
+    models::{
+        game::Game,
+        character::Character,
+        session::Session,
+        turn::Turn
+    },
     DEEPINFRA_TOKEN
 };
 
 #[derive(Serialize)]
-pub struct PromptMessage {
+struct PromptMessage {
     pub role: PromptRole,
     pub content: String
 }
 
 #[derive(Serialize)]
 #[serde(rename_all = "lowercase")]
-pub enum PromptRole {
+enum PromptRole {
     User,
     System,
     Assistant
@@ -26,7 +32,80 @@ pub struct PromptResponse {
     pub output_tokens: i32
 }
 
-pub async fn send_message(prompt: Vec<PromptMessage>) -> Result<PromptResponse, HttpError> {
+pub async fn prompt(
+    game: Game,
+    character: Character,
+    sessions: Vec<Session>,
+    turns: Vec<Turn>,
+    new_input: Option<String>,
+    new_game: bool
+) -> Result<PromptResponse, HttpError> {
+    let system_message = create_system_message(game, character, sessions, new_game);
+    let mut prompt = create_turn_prompts(turns);
+    prompt.insert(0, system_message);
+    if let Some(p) = new_input {
+        prompt.push(PromptMessage {
+            role: PromptRole::User,
+            content: p
+        });
+    }
+    Ok(send_message(prompt).await?)
+}
+
+fn create_system_message(
+    game: Game,
+    character: Character,
+    sessions: Vec<Session>,
+    new_game: bool
+) -> PromptMessage {
+    let mut text = "".to_string();
+    text += "You are acting as a game master for an RPG and running the game.\n";
+    text += &format!("Here is the world context: {}\n", game.game_context);
+    text += &format!(
+        "Character name is {}, here is a description of the character: {}\n",
+        character.name,
+        character.description
+    );
+
+    if sessions.len() == 0 {
+        text += "This is the first session in this game.\n";
+    } else {
+        for(i, session) in sessions.iter().enumerate() {
+            match &session.summary {
+                Some(s) => {text += &format!("Summary of session {}: {}\n", i, s)},
+                None => ()
+            }
+        }
+    }
+
+    if new_game {
+        text += "Start off a new session for the player";
+    }
+
+    PromptMessage {
+        role: PromptRole::System,
+        content: text
+    }
+}
+
+fn create_turn_prompts(turns: Vec<Turn>) -> Vec<PromptMessage> {
+    let mut prompts = Vec::new();
+
+    for turn in turns {
+        prompts.push(PromptMessage {
+            role: PromptRole::User,
+            content: turn.user_text
+        });
+        prompts.push(PromptMessage {
+            role: PromptRole::Assistant,
+            content: turn.llm_text
+        });
+    }
+
+    prompts
+}
+
+async fn send_message(prompt: Vec<PromptMessage>) -> Result<PromptResponse, HttpError> {
     let client = Client::new();
     let key = DEEPINFRA_TOKEN.get().unwrap();
 

+ 1 - 0
src/main.rs

@@ -70,6 +70,7 @@ async fn main() -> std::io::Result<()> {
             .configure(routes::game::game::config)
             .configure(routes::game::character::config)
             .configure(routes::game::session::config)
+            .configure(routes::game::turn::config)
     })
         .bind(("0.0.0.0", port))
         .expect("Failed to bind to port")

+ 2 - 1
src/models/game.rs

@@ -91,7 +91,8 @@ impl Game {
         let sessions = sqlx::query_as::<_, Session>(
             "SELECT *
             FROM sessions
-            WHERE game_id = $1"
+            WHERE game_id = $1
+            ORDER BY started_at ASC"
         )
             .bind(id)
             .fetch_all(db);

+ 16 - 2
src/models/turn.rs

@@ -1,10 +1,10 @@
 use uuid::Uuid;
 use chrono::{DateTime, Utc};
-use sqlx::PgPool;
+use sqlx::{PgPool, FromRow};
 use serde::Serialize;
 use crate::http_error::HttpError;
 
-#[derive(Serialize)]
+#[derive(Serialize, FromRow)]
 pub struct Turn {
     pub id: Uuid,
     pub session_id: Uuid,
@@ -48,4 +48,18 @@ impl Turn {
 
         Ok(())
     }
+
+    pub async fn find_by_session(db: &PgPool, session_id: Uuid) -> Result<Vec<Turn>, HttpError> {
+        let turns = sqlx::query_as::<_, Turn>(
+            "SELECT *
+            FROM turns
+            WHERE session_id = $1
+            ORDER BY created_at ASC"
+        )
+            .bind(session_id)
+            .fetch_all(db)
+            .await?;
+
+        Ok(turns)
+    }
 }

+ 1 - 0
src/routes/game/mod.rs

@@ -1,3 +1,4 @@
 pub mod game;
 pub mod character;
 pub mod session;
+pub mod turn;

+ 8 - 0
src/routes/game/turn.rs

@@ -0,0 +1,8 @@
+use actix_web::web::ServiceConfig;
+use crate::controllers::game::session::turn::{
+    create
+};
+
+pub fn config(cfg: &mut ServiceConfig) {
+    cfg.service(create::route);
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels