cognee_database/ops/
tutorial_seeder.rs1use include_dir::{Dir, include_dir};
7use serde_json::{Value, json};
8use tracing::instrument;
9use uuid::{Uuid, uuid};
10
11use crate::traits::NotebookDb;
12use crate::types::DatabaseError;
13
14static TUTORIALS_DIR: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/assets/notebooks/tutorials");
17
18pub const TUTORIAL_BASICS_ID: Uuid = uuid!("c29dfdef-70d8-5c6d-8968-ed7f019ab20b");
22
23pub const TUTORIAL_PYTHON_DEV_ID: Uuid = uuid!("057cf04b-ab12-5052-84d9-492203097a56");
25
26fn extract_markdown_heading(content: &str) -> Option<&str> {
29 for line in content.lines() {
30 let trimmed = line.trim();
31 if let Some(rest) = trimmed.strip_prefix("### ") {
32 return Some(rest.trim());
33 }
34 if let Some(rest) = trimmed.strip_prefix("## ") {
35 return Some(rest.trim());
36 }
37 if let Some(rest) = trimmed.strip_prefix("# ") {
38 return Some(rest.trim());
39 }
40 }
41 None
42}
43
44fn parse_cell_index(name: &str) -> i64 {
45 let stem = name
46 .strip_suffix(".md")
47 .or_else(|| name.strip_suffix(".py"))
48 .unwrap_or(name);
49 stem.strip_prefix("cell-")
50 .and_then(|s| s.parse().ok())
51 .unwrap_or(-1)
52}
53
54fn build_cells(tutorial_dir: &include_dir::Dir<'_>) -> Value {
55 let mut entries: Vec<(i64, Value)> = tutorial_dir
56 .files()
57 .filter_map(|f| {
58 let name = f.path().file_name()?.to_str()?;
59 if !name.starts_with("cell-") {
60 return None;
61 }
62 let content = f.contents_utf8()?;
63 let idx = parse_cell_index(name);
64 let (cell_type, cell_name) = if name.ends_with(".md") {
65 let heading = extract_markdown_heading(content).unwrap_or(name);
66 ("markdown", heading.to_owned())
67 } else if name.ends_with(".py") {
68 ("code", "Code Cell".to_owned())
69 } else {
70 return None;
71 };
72
73 let cell = json!({
74 "id": Uuid::new_v4().to_string(),
75 "type": cell_type,
76 "name": cell_name,
77 "content": content,
78 });
79 Some((idx, cell))
80 })
81 .collect();
82
83 entries.sort_by_key(|(idx, _)| *idx);
84 Value::Array(entries.into_iter().map(|(_, v)| v).collect())
85}
86
87struct TutorialSpec {
90 id: Uuid,
91 name: &'static str,
92 dir_name: &'static str,
93}
94
95const TUTORIALS: &[TutorialSpec] = &[
96 TutorialSpec {
97 id: TUTORIAL_BASICS_ID,
98 name: "Cognee Basics - tutorial 🧠",
99 dir_name: "cognee-basics",
100 },
101 TutorialSpec {
102 id: TUTORIAL_PYTHON_DEV_ID,
103 name: "Python Development with Cognee - tutorial 🧠",
104 dir_name: "python-development-with-cognee",
105 },
106];
107
108#[instrument(
116 name = "cognee.db.relational.tutorial_seeder.seed_tutorials_if_first_call",
117 level = "info",
118 skip_all,
119 err
120)]
121pub async fn seed_tutorials_if_first_call(
122 db: &dyn NotebookDb,
123 user_id: Uuid,
124) -> Result<(), DatabaseError> {
125 for spec in TUTORIALS {
126 if db.get_by_id_and_owner(spec.id, user_id).await?.is_some() {
127 continue;
128 }
129
130 let cells = match TUTORIALS_DIR.get_dir(spec.dir_name) {
131 Some(dir) => build_cells(dir),
132 None => {
133 tracing::warn!(
134 "Tutorial directory '{}' not found in bundled assets",
135 spec.dir_name
136 );
137 json!([])
138 }
139 };
140
141 db.create_seeded(spec.id, user_id, spec.name.to_owned(), cells, false)
142 .await?;
143 }
144 Ok(())
145}