diff --git a/server/src/http/anthropic.rs b/server/src/http/anthropic.rs index 1fc90e1..e0929bd 100644 --- a/server/src/http/anthropic.rs +++ b/server/src/http/anthropic.rs @@ -1,18 +1,40 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; -use crate::http::payloads::ApiKeyPayload; -use crate::llm; -use poem_openapi::payload::Json; +use crate::llm::chat; +use poem_openapi::{Object, OpenApi, payload::Json}; +use serde::Deserialize; +use std::sync::Arc; -pub async fn get_anthropic_api_key_exists(ctx: &AppContext) -> OpenApiResult> { - let exists = - llm::chat::get_anthropic_api_key_exists(ctx.store.as_ref()).map_err(bad_request)?; - Ok(Json(exists)) +#[derive(Deserialize, Object)] +struct ApiKeyPayload { + api_key: String, } -pub async fn set_anthropic_api_key( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult> { - llm::chat::set_anthropic_api_key(ctx.store.as_ref(), payload.0.api_key).map_err(bad_request)?; - Ok(Json(true)) +pub struct AnthropicApi { + ctx: Arc, +} + +impl AnthropicApi { + pub fn new(ctx: Arc) -> Self { + Self { ctx } + } +} + +#[OpenApi] +impl AnthropicApi { + #[oai(path = "/anthropic/key/exists", method = "get")] + async fn get_anthropic_api_key_exists(&self) -> OpenApiResult> { + let exists = + chat::get_anthropic_api_key_exists(self.ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(exists)) + } + + #[oai(path = "/anthropic/key", method = "post")] + async fn set_anthropic_api_key( + &self, + payload: Json, + ) -> OpenApiResult> { + chat::set_anthropic_api_key(self.ctx.store.as_ref(), payload.0.api_key) + .map_err(bad_request)?; + Ok(Json(true)) + } } diff --git a/server/src/http/chat.rs b/server/src/http/chat.rs index e851127..371f88b 100644 --- a/server/src/http/chat.rs +++ b/server/src/http/chat.rs @@ -1,8 +1,17 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; use crate::llm::chat; -use poem_openapi::payload::Json; +use poem_openapi::{OpenApi, payload::Json}; +use std::sync::Arc; -pub async fn cancel_chat(ctx: &AppContext) -> OpenApiResult> { - chat::cancel_chat(&ctx.state).map_err(bad_request)?; - Ok(Json(true)) +pub struct ChatApi { + pub ctx: Arc, +} + +#[OpenApi] +impl ChatApi { + #[oai(path = "/chat/cancel", method = "post")] + async fn cancel_chat(&self) -> OpenApiResult> { + chat::cancel_chat(&self.ctx.state).map_err(bad_request)?; + Ok(Json(true)) + } } diff --git a/server/src/http/fs.rs b/server/src/http/fs.rs index b26707b..ec5d17c 100644 --- a/server/src/http/fs.rs +++ b/server/src/http/fs.rs @@ -1,34 +1,50 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; -use crate::http::payloads::{FilePathPayload, WriteFilePayload}; use crate::io::fs; -use poem_openapi::payload::Json; +use poem_openapi::{Object, OpenApi, payload::Json}; +use serde::Deserialize; +use std::sync::Arc; -pub async fn read_file( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult> { - let content = fs::read_file(payload.0.path, &ctx.state) - .await - .map_err(bad_request)?; - Ok(Json(content)) +#[derive(Deserialize, Object)] +struct FilePathPayload { + pub path: String, } -pub async fn write_file( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult> { - fs::write_file(payload.0.path, payload.0.content, &ctx.state) - .await - .map_err(bad_request)?; - Ok(Json(true)) +#[derive(Deserialize, Object)] +struct WriteFilePayload { + pub path: String, + pub content: String, } -pub async fn list_directory( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult>> { - let entries = fs::list_directory(payload.0.path, &ctx.state) - .await - .map_err(bad_request)?; - Ok(Json(entries)) +pub struct FsApi { + pub ctx: Arc, +} + +#[OpenApi] +impl FsApi { + #[oai(path = "/fs/read", method = "post")] + async fn read_file(&self, payload: Json) -> OpenApiResult> { + let content = fs::read_file(payload.0.path, &self.ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(content)) + } + + #[oai(path = "/fs/write", method = "post")] + async fn write_file(&self, payload: Json) -> OpenApiResult> { + fs::write_file(payload.0.path, payload.0.content, &self.ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(true)) + } + + #[oai(path = "/fs/list", method = "post")] + async fn list_directory( + &self, + payload: Json, + ) -> OpenApiResult>> { + let entries = fs::list_directory(payload.0.path, &self.ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(entries)) + } } diff --git a/server/src/http/mod.rs b/server/src/http/mod.rs index c16053e..f23cbf6 100644 --- a/server/src/http/mod.rs +++ b/server/src/http/mod.rs @@ -5,17 +5,24 @@ pub mod context; pub mod fs; pub mod health; pub mod model; -pub mod payloads; + pub mod project; -pub mod rest; pub mod search; pub mod shell; pub mod ws; -use crate::http::context::AppContext; -use crate::http::rest::build_openapi_service; +use anthropic::AnthropicApi; +use chat::ChatApi; +use context::AppContext; +use fs::FsApi; +use model::ModelApi; use poem::EndpointExt; use poem::{Route, get}; +use poem_openapi::OpenApiService; +use project::ProjectApi; +use search::SearchApi; +use shell::ShellApi; +use std::sync::Arc; pub fn build_routes(ctx: AppContext) -> impl poem::Endpoint { let ctx_arc = std::sync::Arc::new(ctx); @@ -32,3 +39,45 @@ pub fn build_routes(ctx: AppContext) -> impl poem::Endpoint { .at("/*path", get(assets::embedded_file)) .data(ctx_arc) } + +type ApiTuple = ( + ProjectApi, + ModelApi, + AnthropicApi, + FsApi, + SearchApi, + ShellApi, + ChatApi, +); + +type ApiService = OpenApiService; + +pub fn build_openapi_service(ctx: Arc) -> (ApiService, ApiService) { + let api = ( + ProjectApi { ctx: ctx.clone() }, + ModelApi { ctx: ctx.clone() }, + AnthropicApi::new(ctx.clone()), + FsApi { ctx: ctx.clone() }, + SearchApi { ctx: ctx.clone() }, + ShellApi { ctx: ctx.clone() }, + ChatApi { ctx: ctx.clone() }, + ); + + let api_service = + OpenApiService::new(api, "Story Kit API", "1.0").server("http://127.0.0.1:3001/api"); + + let docs_api = ( + ProjectApi { ctx: ctx.clone() }, + ModelApi { ctx: ctx.clone() }, + AnthropicApi::new(ctx.clone()), + FsApi { ctx: ctx.clone() }, + SearchApi { ctx: ctx.clone() }, + ShellApi { ctx: ctx.clone() }, + ChatApi { ctx }, + ); + + let docs_service = + OpenApiService::new(docs_api, "Story Kit API", "1.0").server("http://127.0.0.1:3001/api"); + + (api_service, docs_service) +} diff --git a/server/src/http/model.rs b/server/src/http/model.rs index 8fa7272..f9e6eab 100644 --- a/server/src/http/model.rs +++ b/server/src/http/model.rs @@ -1,27 +1,41 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; -use crate::http::payloads::ModelPayload; use crate::io::fs; use crate::llm::chat; -use poem_openapi::{param::Query, payload::Json}; +use poem_openapi::{Object, OpenApi, param::Query, payload::Json}; +use serde::Deserialize; +use std::sync::Arc; -pub async fn get_model_preference(ctx: &AppContext) -> OpenApiResult>> { - let result = fs::get_model_preference(ctx.store.as_ref()).map_err(bad_request)?; - Ok(Json(result)) +#[derive(Deserialize, Object)] +struct ModelPayload { + model: String, } -pub async fn set_model_preference( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult> { - fs::set_model_preference(payload.0.model, ctx.store.as_ref()).map_err(bad_request)?; - Ok(Json(true)) +pub struct ModelApi { + pub ctx: Arc, } -pub async fn get_ollama_models( - base_url: Query>, -) -> OpenApiResult>> { - let models = chat::get_ollama_models(base_url.0) - .await - .map_err(bad_request)?; - Ok(Json(models)) +#[OpenApi] +impl ModelApi { + #[oai(path = "/model", method = "get")] + async fn get_model_preference(&self) -> OpenApiResult>> { + let result = fs::get_model_preference(self.ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(result)) + } + + #[oai(path = "/model", method = "post")] + async fn set_model_preference(&self, payload: Json) -> OpenApiResult> { + fs::set_model_preference(payload.0.model, self.ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(true)) + } + + #[oai(path = "/ollama/models", method = "get")] + async fn get_ollama_models( + &self, + base_url: Query>, + ) -> OpenApiResult>> { + let models = chat::get_ollama_models(base_url.0) + .await + .map_err(bad_request)?; + Ok(Json(models)) + } } diff --git a/server/src/http/payloads.rs b/server/src/http/payloads.rs deleted file mode 100644 index dda0bc3..0000000 --- a/server/src/http/payloads.rs +++ /dev/null @@ -1,39 +0,0 @@ -use poem_openapi::Object; -use serde::Deserialize; - -#[derive(Deserialize, Object)] -pub struct PathPayload { - pub path: String, -} - -#[derive(Deserialize, Object)] -pub struct ModelPayload { - pub model: String, -} - -#[derive(Deserialize, Object)] -pub struct ApiKeyPayload { - pub api_key: String, -} - -#[derive(Deserialize, Object)] -pub struct FilePathPayload { - pub path: String, -} - -#[derive(Deserialize, Object)] -pub struct WriteFilePayload { - pub path: String, - pub content: String, -} - -#[derive(Deserialize, Object)] -pub struct SearchPayload { - pub query: String, -} - -#[derive(Deserialize, Object)] -pub struct ExecShellPayload { - pub command: String, - pub args: Vec, -} diff --git a/server/src/http/project.rs b/server/src/http/project.rs index 291c41c..80e5721 100644 --- a/server/src/http/project.rs +++ b/server/src/http/project.rs @@ -1,24 +1,38 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; -use crate::http::payloads::PathPayload; use crate::io::fs; -use poem_openapi::payload::Json; +use poem_openapi::{Object, OpenApi, payload::Json}; +use serde::Deserialize; +use std::sync::Arc; -pub async fn get_current_project(ctx: &AppContext) -> OpenApiResult>> { - let result = fs::get_current_project(&ctx.state, ctx.store.as_ref()).map_err(bad_request)?; - Ok(Json(result)) +#[derive(Deserialize, Object)] +struct PathPayload { + path: String, } -pub async fn open_project( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult> { - let confirmed = fs::open_project(payload.0.path, &ctx.state, ctx.store.as_ref()) - .await - .map_err(bad_request)?; - Ok(Json(confirmed)) +pub struct ProjectApi { + pub ctx: Arc, } -pub async fn close_project(ctx: &AppContext) -> OpenApiResult> { - fs::close_project(&ctx.state, ctx.store.as_ref()).map_err(bad_request)?; - Ok(Json(true)) +#[OpenApi] +impl ProjectApi { + #[oai(path = "/project", method = "get")] + async fn get_current_project(&self) -> OpenApiResult>> { + let result = fs::get_current_project(&self.ctx.state, self.ctx.store.as_ref()) + .map_err(bad_request)?; + Ok(Json(result)) + } + + #[oai(path = "/project", method = "post")] + async fn open_project(&self, payload: Json) -> OpenApiResult> { + let confirmed = fs::open_project(payload.0.path, &self.ctx.state, self.ctx.store.as_ref()) + .await + .map_err(bad_request)?; + Ok(Json(confirmed)) + } + + #[oai(path = "/project", method = "delete")] + async fn close_project(&self) -> OpenApiResult> { + fs::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(bad_request)?; + Ok(Json(true)) + } } diff --git a/server/src/http/rest.rs b/server/src/http/rest.rs deleted file mode 100644 index c60b892..0000000 --- a/server/src/http/rest.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::http::context::{AppContext, OpenApiResult}; -use crate::http::payloads::{ - ApiKeyPayload, ExecShellPayload, FilePathPayload, ModelPayload, PathPayload, SearchPayload, - WriteFilePayload, -}; -use crate::http::{anthropic, chat as chat_http, fs as fs_http, model, project, search, shell}; -use poem_openapi::{OpenApi, OpenApiService, param::Query, payload::Json}; -use std::sync::Arc; - -pub struct Api { - ctx: Arc, -} - -#[OpenApi] -impl Api { - #[oai(path = "/project", method = "get")] - async fn get_current_project(&self) -> OpenApiResult>> { - let ctx = self.ctx.clone(); - project::get_current_project(&ctx).await - } - - #[oai(path = "/project", method = "post")] - async fn open_project(&self, payload: Json) -> OpenApiResult> { - let ctx = self.ctx.clone(); - project::open_project(payload, &ctx).await - } - - #[oai(path = "/project", method = "delete")] - async fn close_project(&self) -> OpenApiResult> { - let ctx = self.ctx.clone(); - project::close_project(&ctx).await - } - - #[oai(path = "/model", method = "get")] - async fn get_model_preference(&self) -> OpenApiResult>> { - let ctx = self.ctx.clone(); - model::get_model_preference(&ctx).await - } - - #[oai(path = "/model", method = "post")] - async fn set_model_preference(&self, payload: Json) -> OpenApiResult> { - let ctx = self.ctx.clone(); - model::set_model_preference(payload, &ctx).await - } - - #[oai(path = "/ollama/models", method = "get")] - async fn get_ollama_models( - &self, - base_url: Query>, - ) -> OpenApiResult>> { - model::get_ollama_models(base_url).await - } - - #[oai(path = "/anthropic/key/exists", method = "get")] - async fn get_anthropic_api_key_exists(&self) -> OpenApiResult> { - let ctx = self.ctx.clone(); - anthropic::get_anthropic_api_key_exists(&ctx).await - } - - #[oai(path = "/anthropic/key", method = "post")] - async fn set_anthropic_api_key( - &self, - payload: Json, - ) -> OpenApiResult> { - let ctx = self.ctx.clone(); - anthropic::set_anthropic_api_key(payload, &ctx).await - } - - #[oai(path = "/fs/read", method = "post")] - async fn read_file(&self, payload: Json) -> OpenApiResult> { - let ctx = self.ctx.clone(); - fs_http::read_file(payload, &ctx).await - } - - #[oai(path = "/fs/write", method = "post")] - async fn write_file(&self, payload: Json) -> OpenApiResult> { - let ctx = self.ctx.clone(); - fs_http::write_file(payload, &ctx).await - } - - #[oai(path = "/fs/list", method = "post")] - async fn list_directory( - &self, - payload: Json, - ) -> OpenApiResult>> { - let ctx = self.ctx.clone(); - fs_http::list_directory(payload, &ctx).await - } - - #[oai(path = "/fs/search", method = "post")] - async fn search_files( - &self, - payload: Json, - ) -> OpenApiResult>> { - let ctx = self.ctx.clone(); - search::search_files(payload, &ctx).await - } - - #[oai(path = "/shell/exec", method = "post")] - async fn exec_shell( - &self, - payload: Json, - ) -> OpenApiResult> { - let ctx = self.ctx.clone(); - shell::exec_shell(payload, &ctx).await - } - - #[oai(path = "/chat/cancel", method = "post")] - async fn cancel_chat(&self) -> OpenApiResult> { - let ctx = self.ctx.clone(); - chat_http::cancel_chat(&ctx).await - } -} - -pub fn build_openapi_service( - ctx: Arc, -) -> (OpenApiService, OpenApiService) { - let api_service = OpenApiService::new(Api { ctx: ctx.clone() }, "Story Kit API", "1.0") - .server("http://127.0.0.1:3001/api"); - - let docs_service = OpenApiService::new(Api { ctx }, "Story Kit API", "1.0") - .server("http://127.0.0.1:3001/api"); - - (api_service, docs_service) -} diff --git a/server/src/http/search.rs b/server/src/http/search.rs index a529e7b..c65c41c 100644 --- a/server/src/http/search.rs +++ b/server/src/http/search.rs @@ -1,13 +1,28 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; -use crate::http::payloads::SearchPayload; -use poem_openapi::payload::Json; +use poem_openapi::{Object, OpenApi, payload::Json}; +use serde::Deserialize; +use std::sync::Arc; -pub async fn search_files( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult>> { - let results = crate::io::search::search_files(payload.0.query, &ctx.state) - .await - .map_err(bad_request)?; - Ok(Json(results)) +pub struct SearchApi { + pub ctx: Arc, +} + +#[derive(Deserialize, Object)] +struct SearchPayload { + query: String, +} + +#[OpenApi] +impl SearchApi { + #[oai(path = "/fs/search", method = "post")] + async fn search_files( + &self, + payload: Json, + ) -> OpenApiResult>> { + let ctx = self.ctx.clone(); + let results = crate::io::search::search_files(payload.0.query, &ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(results)) + } } diff --git a/server/src/http/shell.rs b/server/src/http/shell.rs index 6ccfb14..2c6c1c0 100644 --- a/server/src/http/shell.rs +++ b/server/src/http/shell.rs @@ -1,13 +1,29 @@ use crate::http::context::{AppContext, OpenApiResult, bad_request}; -use crate::http::payloads::ExecShellPayload; -use poem_openapi::payload::Json; +use poem_openapi::{Object, OpenApi, payload::Json}; +use serde::Deserialize; +use std::sync::Arc; -pub async fn exec_shell( - payload: Json, - ctx: &AppContext, -) -> OpenApiResult> { - let output = crate::io::shell::exec_shell(payload.0.command, payload.0.args, &ctx.state) - .await - .map_err(bad_request)?; - Ok(Json(output)) +#[derive(Deserialize, Object)] +struct ExecShellPayload { + pub command: String, + pub args: Vec, +} + +pub struct ShellApi { + pub ctx: Arc, +} + +#[OpenApi] +impl ShellApi { + #[oai(path = "/shell/exec", method = "post")] + async fn exec_shell( + &self, + payload: Json, + ) -> OpenApiResult> { + let output = + crate::io::shell::exec_shell(payload.0.command, payload.0.args, &self.ctx.state) + .await + .map_err(bad_request)?; + Ok(Json(output)) + } }