Create / edit recipe form unification and improvements (#4693)
This commit is contained in:
@@ -1609,9 +1609,31 @@ impl Agent {
|
||||
extension_configs.len()
|
||||
);
|
||||
|
||||
let (title, description) =
|
||||
if let Ok(json_content) = serde_json::from_str::<Value>(&clean_content) {
|
||||
let title = json_content
|
||||
.get("title")
|
||||
.and_then(|t| t.as_str())
|
||||
.unwrap_or("Custom recipe from chat")
|
||||
.to_string();
|
||||
|
||||
let description = json_content
|
||||
.get("description")
|
||||
.and_then(|d| d.as_str())
|
||||
.unwrap_or("a custom recipe instance from this chat session")
|
||||
.to_string();
|
||||
|
||||
(title, description)
|
||||
} else {
|
||||
(
|
||||
"Custom recipe from chat".to_string(),
|
||||
"a custom recipe instance from this chat session".to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
let recipe = Recipe::builder()
|
||||
.title("Custom recipe from chat")
|
||||
.description("a custom recipe instance from this chat session")
|
||||
.title(title)
|
||||
.description(description)
|
||||
.instructions(instructions)
|
||||
.activities(activities)
|
||||
.extensions(extension_configs)
|
||||
|
||||
@@ -330,6 +330,7 @@ mod tests {
|
||||
extension_data: extension_data::ExtensionData::new(),
|
||||
conversation: Some(conversation),
|
||||
message_count,
|
||||
user_recipe_values: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
Based on our conversation so far, could you create:
|
||||
|
||||
1. A concise set of instructions (1-2 paragraphs) that describe what you've been helping with. Make the instructions generic, and higher-level so that can be re-used across various similar tasks. Pay special attention if any output styles or formats are requested (and make it clear), and note any non standard tools used or required.
|
||||
1. A concise title (5-10 words) that captures the main topic or task
|
||||
2. A brief description (1-2 sentences) that summarizes what this recipe helps with
|
||||
3. A concise set of instructions (1-2 paragraphs) that describe what you've been helping with. Make the instructions generic, and higher-level so that can be re-used across various similar tasks. Pay special attention if any output styles or formats are requested (and make it clear), and note any non standard tools used or required.
|
||||
4. A list of 3-5 example activities (as a few words each at most) that would be relevant to this topic
|
||||
|
||||
2. A list of 3-5 example activities (as a few words each at most) that would be relevant to this topic
|
||||
|
||||
Format your response in _VALID_ json, with one key being `instructions` which contains a string, and the other key `activities` as an array of strings.
|
||||
Format your response in _VALID_ json, with keys being `title`, `description`, `instructions` (string), and `activities` (array of strings).
|
||||
For example, perhaps we have been discussing fruit and you might write:
|
||||
|
||||
{
|
||||
"title": "Fruit Information Assistant",
|
||||
"description": "A recipe for finding and sharing information about different types of fruit.",
|
||||
"instructions": "Using web searches we find pictures of fruit, and always check what language to reply in.",
|
||||
"activities": [
|
||||
"Show pics of apples",
|
||||
|
||||
@@ -11,6 +11,7 @@ use rmcp::model::Role;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
@@ -18,7 +19,7 @@ use tokio::sync::OnceCell;
|
||||
use tracing::{info, warn};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
const CURRENT_SCHEMA_VERSION: i32 = 1;
|
||||
const CURRENT_SCHEMA_VERSION: i32 = 2;
|
||||
|
||||
static SESSION_STORAGE: OnceCell<Arc<SessionStorage>> = OnceCell::const_new();
|
||||
|
||||
@@ -39,6 +40,7 @@ pub struct Session {
|
||||
pub accumulated_output_tokens: Option<i32>,
|
||||
pub schedule_id: Option<String>,
|
||||
pub recipe: Option<Recipe>,
|
||||
pub user_recipe_values: Option<HashMap<String, String>>,
|
||||
pub conversation: Option<Conversation>,
|
||||
pub message_count: usize,
|
||||
}
|
||||
@@ -56,6 +58,7 @@ pub struct SessionUpdateBuilder {
|
||||
accumulated_output_tokens: Option<Option<i32>>,
|
||||
schedule_id: Option<Option<String>>,
|
||||
recipe: Option<Option<Recipe>>,
|
||||
user_recipe_values: Option<Option<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema, Debug)]
|
||||
@@ -82,6 +85,7 @@ impl SessionUpdateBuilder {
|
||||
accumulated_output_tokens: None,
|
||||
schedule_id: None,
|
||||
recipe: None,
|
||||
user_recipe_values: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +144,14 @@ impl SessionUpdateBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn user_recipe_values(
|
||||
mut self,
|
||||
user_recipe_values: Option<HashMap<String, String>>,
|
||||
) -> Self {
|
||||
self.user_recipe_values = Some(user_recipe_values);
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn apply(self) -> Result<()> {
|
||||
SessionManager::apply_update(self).await
|
||||
}
|
||||
@@ -265,6 +277,7 @@ impl Default for Session {
|
||||
accumulated_output_tokens: None,
|
||||
schedule_id: None,
|
||||
recipe: None,
|
||||
user_recipe_values: None,
|
||||
conversation: None,
|
||||
message_count: 0,
|
||||
}
|
||||
@@ -285,6 +298,10 @@ impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for Session {
|
||||
let recipe_json: Option<String> = row.try_get("recipe_json")?;
|
||||
let recipe = recipe_json.and_then(|json| serde_json::from_str(&json).ok());
|
||||
|
||||
let user_recipe_values_json: Option<String> = row.try_get("user_recipe_values_json")?;
|
||||
let user_recipe_values =
|
||||
user_recipe_values_json.and_then(|json| serde_json::from_str(&json).ok());
|
||||
|
||||
Ok(Session {
|
||||
id: row.try_get("id")?,
|
||||
working_dir: PathBuf::from(row.try_get::<String, _>("working_dir")?),
|
||||
@@ -301,6 +318,7 @@ impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for Session {
|
||||
accumulated_output_tokens: row.try_get("accumulated_output_tokens")?,
|
||||
schedule_id: row.try_get("schedule_id")?,
|
||||
recipe,
|
||||
user_recipe_values,
|
||||
conversation: None,
|
||||
message_count: row.try_get("message_count").unwrap_or(0) as usize,
|
||||
})
|
||||
@@ -386,7 +404,8 @@ impl SessionStorage {
|
||||
accumulated_input_tokens INTEGER,
|
||||
accumulated_output_tokens INTEGER,
|
||||
schedule_id TEXT,
|
||||
recipe_json TEXT
|
||||
recipe_json TEXT,
|
||||
user_recipe_values_json TEXT
|
||||
)
|
||||
"#,
|
||||
)
|
||||
@@ -472,14 +491,19 @@ impl SessionStorage {
|
||||
None => None,
|
||||
};
|
||||
|
||||
let user_recipe_values_json = match &session.user_recipe_values {
|
||||
Some(user_recipe_values) => Some(serde_json::to_string(user_recipe_values)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO sessions (
|
||||
id, description, working_dir, created_at, updated_at, extension_data,
|
||||
total_tokens, input_tokens, output_tokens,
|
||||
accumulated_total_tokens, accumulated_input_tokens, accumulated_output_tokens,
|
||||
schedule_id, recipe_json
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
schedule_id, recipe_json, user_recipe_values_json
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(&session.id)
|
||||
@@ -496,6 +520,7 @@ impl SessionStorage {
|
||||
.bind(session.accumulated_output_tokens)
|
||||
.bind(&session.schedule_id)
|
||||
.bind(recipe_json)
|
||||
.bind(user_recipe_values_json)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
@@ -572,6 +597,15 @@ impl SessionStorage {
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
2 => {
|
||||
sqlx::query(
|
||||
r#"
|
||||
ALTER TABLE sessions ADD COLUMN user_recipe_values_json TEXT
|
||||
"#,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
anyhow::bail!("Unknown migration version: {}", version);
|
||||
}
|
||||
@@ -612,7 +646,7 @@ impl SessionStorage {
|
||||
SELECT id, working_dir, description, created_at, updated_at, extension_data,
|
||||
total_tokens, input_tokens, output_tokens,
|
||||
accumulated_total_tokens, accumulated_input_tokens, accumulated_output_tokens,
|
||||
schedule_id, recipe_json
|
||||
schedule_id, recipe_json, user_recipe_values_json
|
||||
FROM sessions
|
||||
WHERE id = ?
|
||||
"#,
|
||||
@@ -669,6 +703,7 @@ impl SessionStorage {
|
||||
);
|
||||
add_update!(builder.schedule_id, "schedule_id");
|
||||
add_update!(builder.recipe, "recipe_json");
|
||||
add_update!(builder.user_recipe_values, "user_recipe_values_json");
|
||||
|
||||
if updates.is_empty() {
|
||||
return Ok(());
|
||||
@@ -715,6 +750,12 @@ impl SessionStorage {
|
||||
let recipe_json = recipe.map(|r| serde_json::to_string(&r)).transpose()?;
|
||||
q = q.bind(recipe_json);
|
||||
}
|
||||
if let Some(user_recipe_values) = builder.user_recipe_values {
|
||||
let user_recipe_values_json = user_recipe_values
|
||||
.map(|urv| serde_json::to_string(&urv))
|
||||
.transpose()?;
|
||||
q = q.bind(user_recipe_values_json);
|
||||
}
|
||||
|
||||
q = q.bind(&builder.session_id);
|
||||
q.execute(&self.pool).await?;
|
||||
@@ -805,7 +846,7 @@ impl SessionStorage {
|
||||
SELECT s.id, s.working_dir, s.description, s.created_at, s.updated_at, s.extension_data,
|
||||
s.total_tokens, s.input_tokens, s.output_tokens,
|
||||
s.accumulated_total_tokens, s.accumulated_input_tokens, s.accumulated_output_tokens,
|
||||
s.schedule_id, s.recipe_json,
|
||||
s.schedule_id, s.recipe_json, s.user_recipe_values_json,
|
||||
COUNT(m.id) as message_count
|
||||
FROM sessions s
|
||||
INNER JOIN messages m ON s.id = m.session_id
|
||||
|
||||
Reference in New Issue
Block a user