-
Notifications
You must be signed in to change notification settings - Fork 18
(feat) Add initial documentation and prompt management API #40
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
b98f410
a323c7d
3936f44
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,21 @@ | ||
| -- Add prompt_directories table | ||
| CREATE TABLE prompt_directories ( | ||
| id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| name TEXT NOT NULL, | ||
| parent_id INTEGER REFERENCES prompt_directories(id) ON DELETE SET NULL, | ||
| created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
| ); | ||
|
|
||
| -- Add prompt_components table | ||
| CREATE TABLE prompt_components ( | ||
| id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| name TEXT NOT NULL, | ||
| content TEXT NOT NULL, | ||
| description TEXT, | ||
| created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
| ); | ||
|
|
||
| -- Add directory_id to prompts table | ||
| ALTER TABLE prompts ADD COLUMN directory_id INTEGER REFERENCES prompt_directories(id) ON DELETE SET NULL; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -381,3 +381,109 @@ impl IntoResponse for CompletionResponse { | |
| } | ||
| } | ||
| } | ||
|
|
||
| // --- Prompt Directory API --- | ||
| use crate::db::models::{PromptDirectory, PromptComponent}; | ||
| use axum::extract::Query; | ||
| use serde::Deserialize; | ||
|
|
||
| #[derive(Deserialize)] | ||
| pub struct DirectoryQuery { | ||
| pub parent_id: Option<i64>, | ||
| } | ||
|
|
||
| pub async fn create_directory( | ||
| State(state): State<AppState>, | ||
| Json(payload): Json<PromptDirectory>, | ||
| ) -> Result<Json<i64>, AppError> { | ||
| let id = state.db.prompt.create_directory(&payload.name, payload.parent_id).await?; | ||
| Ok(Json(id)) | ||
| } | ||
|
|
||
| pub async fn get_directory( | ||
| Path(id): Path<i64>, | ||
| State(state): State<AppState>, | ||
| ) -> Result<Json<Option<PromptDirectory>>, AppError> { | ||
| let dir = state.db.prompt.get_directory(id).await?; | ||
| Ok(Json(dir)) | ||
| } | ||
|
|
||
| pub async fn list_directories( | ||
| State(state): State<AppState>, | ||
| Query(query): Query<DirectoryQuery>, | ||
| ) -> Result<Json<Vec<PromptDirectory>>, AppError> { | ||
| let dirs = state.db.prompt.list_directories(query.parent_id).await?; | ||
| Ok(Json(dirs)) | ||
| } | ||
|
|
||
| pub async fn update_directory( | ||
| Path(id): Path<i64>, | ||
| State(state): State<AppState>, | ||
| Json(payload): Json<PromptDirectory>, | ||
| ) -> Result<Json<bool>, AppError> { | ||
| let updated = state.db.prompt.update_directory(id, &payload.name, payload.parent_id).await?; | ||
| Ok(Json(updated)) | ||
| } | ||
|
|
||
| pub async fn delete_directory( | ||
| Path(id): Path<i64>, | ||
| State(state): State<AppState>, | ||
| ) -> Result<Json<bool>, AppError> { | ||
| let deleted = state.db.prompt.delete_directory(id).await?; | ||
| Ok(Json(deleted)) | ||
| } | ||
|
|
||
| // --- Prompt Component API --- | ||
|
||
| #[derive(Deserialize)] | ||
| pub struct CreateComponentRequest { | ||
| pub name: String, | ||
| pub content: String, | ||
| pub description: Option<String>, | ||
| } | ||
|
|
||
| pub async fn create_component( | ||
| State(state): State<AppState>, | ||
| Json(payload): Json<CreateComponentRequest>, | ||
| ) -> Result<Json<i64>, AppError> { | ||
| let id = state.db.prompt.create_component(&payload.name, &payload.content, payload.description.as_deref()).await?; | ||
| Ok(Json(id)) | ||
| } | ||
|
|
||
| pub async fn get_component( | ||
| Path(id): Path<i64>, | ||
| State(state): State<AppState>, | ||
| ) -> Result<Json<Option<PromptComponent>>, AppError> { | ||
| let comp = state.db.prompt.get_component(id).await?; | ||
| Ok(Json(comp)) | ||
| } | ||
|
|
||
| pub async fn list_components( | ||
| State(state): State<AppState>, | ||
| ) -> Result<Json<Vec<PromptComponent>>, AppError> { | ||
| let comps = state.db.prompt.list_components().await?; | ||
| Ok(Json(comps)) | ||
| } | ||
|
|
||
| #[derive(Deserialize)] | ||
| pub struct UpdateComponentRequest { | ||
| pub name: String, | ||
| pub content: String, | ||
| pub description: Option<String>, | ||
| } | ||
|
|
||
| pub async fn update_component( | ||
| Path(id): Path<i64>, | ||
| State(state): State<AppState>, | ||
| Json(payload): Json<UpdateComponentRequest>, | ||
| ) -> Result<Json<bool>, AppError> { | ||
| let updated = state.db.prompt.update_component(id, &payload.name, &payload.content, payload.description.as_deref()).await?; | ||
| Ok(Json(updated)) | ||
| } | ||
|
|
||
| pub async fn delete_component( | ||
| Path(id): Path<i64>, | ||
| State(state): State<AppState>, | ||
| ) -> Result<Json<bool>, AppError> { | ||
| let deleted = state.db.prompt.delete_component(id).await?; | ||
| Ok(Json(deleted)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -557,6 +557,152 @@ impl PromptRepository { | |
| // Return the updated prompt | ||
| self.get_prompt(prompt_id).await | ||
| } | ||
|
|
||
| // --- Prompt Directory CRUD --- | ||
| pub async fn create_directory(&self, name: &str, parent_id: Option<i64>) -> Result<i64> { | ||
| let rec = sqlx::query!( | ||
| r#"INSERT INTO prompt_directory (name, parent_id) VALUES (?, ?)"#, | ||
| name, | ||
| parent_id | ||
| ) | ||
| .execute(&self.pool) | ||
| .await?; | ||
| Ok(rec.last_insert_rowid()) | ||
| } | ||
|
|
||
| pub async fn get_directory(&self, id: i64) -> Result<Option<PromptDirectory>> { | ||
| let rec = sqlx::query_as!(PromptDirectory, | ||
| r#"SELECT id, name, parent_id, created_at, updated_at FROM prompt_directory WHERE id = ?"#, | ||
| id | ||
| ) | ||
| .fetch_optional(&self.pool) | ||
| .await?; | ||
| Ok(rec) | ||
| } | ||
|
|
||
| pub async fn list_directories(&self, parent_id: Option<i64>) -> Result<Vec<PromptDirectory>> { | ||
| let recs = sqlx::query_as!(PromptDirectory, | ||
| r#"SELECT id, name, parent_id, created_at, updated_at FROM prompt_directory WHERE parent_id IS ?"#, | ||
| parent_id | ||
| ) | ||
| .fetch_all(&self.pool) | ||
| .await?; | ||
| Ok(recs) | ||
| } | ||
|
|
||
| pub async fn update_directory(&self, id: i64, name: &str, parent_id: Option<i64>) -> Result<bool> { | ||
| let rows = sqlx::query!( | ||
| r#"UPDATE prompt_directory SET name = ?, parent_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"#, | ||
| name, parent_id, id | ||
| ) | ||
| .execute(&self.pool) | ||
| .await? | ||
| .rows_affected(); | ||
| Ok(rows > 0) | ||
| } | ||
|
|
||
| pub async fn delete_directory(&self, id: i64) -> Result<bool> { | ||
| let rows = sqlx::query!( | ||
| r#"DELETE FROM prompt_directory WHERE id = ?"#, | ||
| id | ||
| ) | ||
| .execute(&self.pool) | ||
| .await? | ||
| .rows_affected(); | ||
| Ok(rows > 0) | ||
| } | ||
|
|
||
| // --- Prompt Component CRUD --- | ||
| pub async fn create_component(&self, name: &str, content: &str, description: Option<&str>) -> Result<i64> { | ||
|
||
| let rec = sqlx::query!( | ||
| r#"INSERT INTO prompt_component (name, content, description) VALUES (?, ?, ?)"#, | ||
| name, content, description | ||
| ) | ||
| .execute(&self.pool) | ||
| .await?; | ||
| Ok(rec.last_insert_rowid()) | ||
| } | ||
|
|
||
| pub async fn get_component(&self, id: i64) -> Result<Option<PromptComponent>> { | ||
| let rec = sqlx::query_as!(PromptComponent, | ||
| r#"SELECT id, name, content, description, created_at, updated_at FROM prompt_component WHERE id = ?"#, | ||
| id | ||
| ) | ||
| .fetch_optional(&self.pool) | ||
| .await?; | ||
| Ok(rec) | ||
| } | ||
|
|
||
| pub async fn list_components(&self) -> Result<Vec<PromptComponent>> { | ||
| let recs = sqlx::query_as!(PromptComponent, | ||
| r#"SELECT id, name, content, description, created_at, updated_at FROM prompt_component ORDER BY created_at DESC"# | ||
| ) | ||
| .fetch_all(&self.pool) | ||
| .await?; | ||
| Ok(recs) | ||
| } | ||
|
|
||
| pub async fn update_component(&self, id: i64, name: &str, content: &str, description: Option<&str>) -> Result<bool> { | ||
| let rows = sqlx::query!( | ||
| r#"UPDATE prompt_component SET name = ?, content = ?, description = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"#, | ||
| name, content, description, id | ||
| ) | ||
| .execute(&self.pool) | ||
| .await? | ||
| .rows_affected(); | ||
| Ok(rows > 0) | ||
| } | ||
|
|
||
| pub async fn delete_component(&self, id: i64) -> Result<bool> { | ||
| let rows = sqlx::query!( | ||
| r#"DELETE FROM prompt_component WHERE id = ?"#, | ||
| id | ||
| ) | ||
| .execute(&self.pool) | ||
| .await? | ||
| .rows_affected(); | ||
| Ok(rows > 0) | ||
| } | ||
|
|
||
| // Utility: Recursively resolve {{component:component_name}} in prompt content | ||
| pub async fn resolve_components_in_text(&self, text: &str) -> Result<String> { | ||
| use regex::Regex; | ||
| let re = Regex::new(r"\{\{component:([a-zA-Z0-9_\- ]+)}}")?; | ||
| let mut resolved = text.to_string(); | ||
| let mut changed = true; | ||
| while changed { | ||
| changed = false; | ||
| let mut new_text = resolved.clone(); | ||
| for cap in re.captures_iter(&resolved) { | ||
| let comp_name = &cap[1]; | ||
| let comp = sqlx::query!( | ||
| r#"SELECT content FROM prompt_component WHERE name = ? LIMIT 1"#, | ||
| comp_name | ||
| ) | ||
| .fetch_optional(&self.pool) | ||
| .await?; | ||
| if let Some(row) = comp { | ||
| let comp_content = row.content; | ||
| new_text = new_text.replace(&cap[0], &comp_content); | ||
| changed = true; | ||
| } | ||
| } | ||
| resolved = new_text; | ||
| } | ||
| Ok(resolved) | ||
| } | ||
|
|
||
| // Wrap get_prompt to resolve components in system/user fields | ||
| pub async fn get_prompt_with_components(&self, id: i64) -> Result<PromptRowWithModel> { | ||
| let mut prompt = self.get_prompt(id).await?; | ||
| if let Some(system) = &prompt.system { | ||
| prompt.system = Some(self.resolve_components_in_text(system).await?); | ||
| } | ||
| if let Some(user) = &prompt.user { | ||
| prompt.user = Some(self.resolve_components_in_text(user).await?); | ||
| } | ||
| Ok(prompt) | ||
| } | ||
| } | ||
|
|
||
| fn generate_diff(text1: &str, text2: &str) -> String { | ||
|
|
@@ -573,4 +719,3 @@ fn generate_diff(text1: &str, text2: &str) -> String { | |
|
|
||
| diff_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.
This statement is invalid,
promptis the name of the table notprompts