Skip to content
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

feat(page): page and section backend #3666

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ollama-api-bindings/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
let available = self.model_available(&model).await?;

let allow_pull = std::env::var_os(ALLOW_PULL_ENV)
.map(|x| x == "1" || x.to_ascii_lowercase() == "y" || x.to_ascii_lowercase() == "yes")
.map(|x| x == "1" || x.eq_ignore_ascii_case("y") || x.eq_ignore_ascii_case("yes"))

Check warning on line 94 in crates/ollama-api-bindings/src/model.rs

View check run for this annotation

Codecov / codecov/patch

crates/ollama-api-bindings/src/model.rs#L94

Added line #L94 was not covered by tests
.unwrap_or(false);

match (available, allow_pull) {
Expand Down
2 changes: 1 addition & 1 deletion crates/tabby-git/src/grep/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ fn read_lines(content: &[u8]) -> anyhow::Result<Vec<GrepLine>> {
let mut byte_offset = 0;
for line in line_reader {
let line = line? + "\n";
let bytes_length = line.as_bytes().len();
let bytes_length = line.len();
lines.push(GrepLine {
line: GrepTextOrBase64::Text(line),
byte_offset,
Expand Down
2 changes: 2 additions & 0 deletions ee/tabby-db/migrations/0042_page-section.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP TABLE page_sections;
DROP TABLE pages;
27 changes: 27 additions & 0 deletions ee/tabby-db/migrations/0042_page-section.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
CREATE TABLE pages(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,

-- The user who created the page
author_id INTEGER NOT NULL,

title TEXT,
content TEXT,

created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')),
updated_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')),

FOREIGN KEY(author_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE page_sections(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL,

title TEXT NOT NULL,
content TEXT NOT NULL,

created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')),
updated_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')),

FOREIGN KEY(page_id) REFERENCES pages(id) ON DELETE CASCADE
);
Binary file modified ee/tabby-db/schema.sqlite
Binary file not shown.
19 changes: 19 additions & 0 deletions ee/tabby-db/schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,22 @@ CREATE TABLE ldap_credential(
created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')),
updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now'))
);
CREATE TABLE pages(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
-- The user who created the page
author_id INTEGER NOT NULL,
title TEXT,
content TEXT,
created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')),
updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')),
FOREIGN KEY(author_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE page_sections(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')),
updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')),
FOREIGN KEY(page_id) REFERENCES pages(id) ON DELETE CASCADE
);
1,272 changes: 672 additions & 600 deletions ee/tabby-db/schema/schema.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions ee/tabby-db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub use job_runs::JobRunDAO;
pub use ldap_credential::LdapCredentialDAO;
pub use notifications::NotificationDAO;
pub use oauth_credential::OAuthCredentialDAO;
pub use pages::{PageDAO, PageSectionDAO};
pub use provided_repositories::ProvidedRepositoryDAO;
pub use repositories::RepositoryDAO;
pub use server_setting::ServerSettingDAO;
Expand Down Expand Up @@ -38,6 +39,7 @@ mod ldap_credential;
mod migration_tests;
mod notifications;
mod oauth_credential;
mod pages;
mod password_reset;
mod provided_repositories;
mod refresh_tokens;
Expand Down
206 changes: 206 additions & 0 deletions ee/tabby-db/src/pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use sqlx::{query, query_as, FromRow};
use tabby_db_macros::query_paged_as;

use crate::DbConn;

#[derive(FromRow)]
pub struct PageDAO {
pub id: i64,
pub author_id: i64,
pub title: Option<String>,
pub content: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

#[derive(sqlx::FromRow)]
pub struct PageSectionDAO {
pub id: i64,
pub page_id: i64,

pub title: String,
pub content: String,

pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

impl DbConn {
pub async fn create_page(&self, author_id: i64) -> Result<i64> {
let res = query!("INSERT INTO pages(author_id) VALUES (?)", author_id,)
.execute(&self.pool)
.await?;

Check warning on line 34 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L31-L34

Added lines #L31 - L34 were not covered by tests

Ok(res.last_insert_rowid())
}

Check warning on line 37 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L36-L37

Added lines #L36 - L37 were not covered by tests

pub async fn update_page_title(&self, page_id: i64, title: &str) -> Result<()> {
query!(
"UPDATE pages SET title = ?, updated_at = DATETIME('now') WHERE id = ?",
title,
page_id
)
.execute(&self.pool)
.await?;

Check warning on line 46 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L39-L46

Added lines #L39 - L46 were not covered by tests

Ok(())
}

Check warning on line 49 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L48-L49

Added lines #L48 - L49 were not covered by tests

pub async fn update_page_content(&self, page_id: i64, content: &str) -> Result<()> {
query!(
"UPDATE pages SET content = ?, updated_at = DATETIME('now') WHERE id = ?",
content,
page_id
)
.execute(&self.pool)
.await?;

Check warning on line 58 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L51-L58

Added lines #L51 - L58 were not covered by tests

Ok(())
}

Check warning on line 61 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L60-L61

Added lines #L60 - L61 were not covered by tests

pub async fn list_pages(
&self,
ids: Option<&[i64]>,
limit: Option<usize>,
skip_id: Option<i32>,
backwards: bool,
) -> Result<Vec<PageDAO>> {
let condition = match ids {
Some(ids) => format!(
"id IN ({})",
ids.iter()
.map(|id| id.to_string())
.collect::<Vec<String>>()
.join(",")
),
None => "1 = 1".to_string(),

Check warning on line 78 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L63-L78

Added lines #L63 - L78 were not covered by tests
};

let pages = query_paged_as!(
PageDAO,
"pages",
[
"id",
"author_id",
"title",
"content",
"created_at" as "created_at: DateTime<Utc>",
"updated_at" as "updated_at: DateTime<Utc>"
],
limit,
skip_id,
backwards,
Some(condition)
)
.fetch_all(&self.pool)
.await?;

Check warning on line 98 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L81-L98

Added lines #L81 - L98 were not covered by tests

Ok(pages)
}

Check warning on line 101 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L100-L101

Added lines #L100 - L101 were not covered by tests

pub async fn delete_page(&self, id: i64) -> Result<()> {
query!("DELETE FROM pages WHERE id = ?", id,)
.execute(&self.pool)
.await?;

Check warning on line 106 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L103-L106

Added lines #L103 - L106 were not covered by tests

Ok(())
}

Check warning on line 109 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L108-L109

Added lines #L108 - L109 were not covered by tests

pub async fn get_page(&self, id: i64) -> Result<Option<PageDAO>> {
let page = query_as!(
PageDAO,
r#"SELECT
id,
author_id,
title,
content,
created_at as "created_at: DateTime<Utc>",
updated_at as "updated_at: DateTime<Utc>"
FROM pages
WHERE id = ?"#,
id
)
.fetch_optional(&self.pool)
.await?;

Check warning on line 126 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L111-L126

Added lines #L111 - L126 were not covered by tests

Ok(page)
}

Check warning on line 129 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L128-L129

Added lines #L128 - L129 were not covered by tests

pub async fn list_page_sections(
&self,
page_id: i64,
limit: Option<usize>,
skip_id: Option<i32>,
backwards: bool,
) -> Result<Vec<PageSectionDAO>> {
let condition = format!("page_id = {}", page_id);
let sections = query_paged_as!(
PageSectionDAO,
"page_sections",
[
"id",
"page_id",
"title",
"content",
"created_at" as "created_at: DateTime<Utc>",
"updated_at" as "updated_at: DateTime<Utc>"
],
limit,
skip_id,
backwards,
Some(condition)
)
.fetch_all(&self.pool)
.await?;

Check warning on line 156 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L131-L156

Added lines #L131 - L156 were not covered by tests

Ok(sections)
}

Check warning on line 159 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L158-L159

Added lines #L158 - L159 were not covered by tests

pub async fn get_page_section(&self, id: i64) -> Result<Option<PageSectionDAO>> {
let section = query_as!(
PageSectionDAO,
r#"SELECT
id,
page_id,
title,
content,
created_at as "created_at: DateTime<Utc>",
updated_at as "updated_at: DateTime<Utc>"
FROM page_sections
WHERE id = ?"#,
id
)
.fetch_optional(&self.pool)
.await?;

Check warning on line 176 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L161-L176

Added lines #L161 - L176 were not covered by tests

Ok(section)
}

Check warning on line 179 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L178-L179

Added lines #L178 - L179 were not covered by tests

pub async fn create_page_section(
&self,
page_id: i64,
title: &str,
content: &str,
) -> Result<i64> {
let res = query!(
"INSERT INTO page_sections(page_id, title, content) VALUES (?, ?, ?)",
page_id,
title,
content
)
.execute(&self.pool)
.await?;

Check warning on line 194 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L181-L194

Added lines #L181 - L194 were not covered by tests

Ok(res.last_insert_rowid())
}

Check warning on line 197 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L196-L197

Added lines #L196 - L197 were not covered by tests

pub async fn delete_page_section(&self, id: i64) -> Result<()> {
query!("DELETE FROM page_sections WHERE id = ?", id,)
.execute(&self.pool)
.await?;

Check warning on line 202 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L199-L202

Added lines #L199 - L202 were not covered by tests

Ok(())
}

Check warning on line 205 in ee/tabby-db/src/pages.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-db/src/pages.rs#L204-L205

Added lines #L204 - L205 were not covered by tests
}
64 changes: 64 additions & 0 deletions ee/tabby-schema/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ enum Role {
ASSISTANT
}

input AddPageSectionInput {
pageId: ID!
title: String!
}

input CodeQueryInput {
filepath: String
language: String
Expand Down Expand Up @@ -652,6 +657,24 @@ type Mutation {
"Turn on persisted status for a thread."
setThreadPersisted(threadId: ID!): Boolean!
updateThreadMessage(input: UpdateMessageInput!): Boolean!
"""
Utilize an existing thread and its messages to create a page.
This will automatically generate the page title and a summary of the content.

Every two messages will be converted into a page section.
The user's message will serve as the title of the section.
The assistant's message will become the content of the section.
"""
convertThreadToPage(threadId: ID!): ID!
"delete a page and all its sections."
deletePage(id: ID!): Boolean!
"""
Creates a new page section.
Only the title is required; the answer will be generated as the content.
"""
addPageSection(input: AddPageSectionInput!): ID!
"delete a single page section."
deletePageSection(sectionId: ID!): Boolean!
createCustomDocument(input: CreateCustomDocumentInput!): ID!
deleteCustomDocument(id: ID!): Boolean!
setPresetDocumentActive(input: SetPresetDocumentActiveInput!): Boolean!
Expand Down Expand Up @@ -682,6 +705,25 @@ type OAuthCredential {
updatedAt: DateTime!
}

type Page {
id: ID!
authorId: ID!
title: String
content: String
createdAt: DateTime!
updatedAt: DateTime!
}

type PageConnection {
edges: [PageEdge!]!
pageInfo: PageInfo!
}

type PageEdge {
node: Page!
cursor: String!
}

type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
Expand Down Expand Up @@ -788,6 +830,9 @@ type Query {
Thread is public within an instance, so no need to check for ownership.
"""
threadMessages(threadId: ID!, after: String, before: String, first: Int, last: Int): MessageConnection!
"Read pages by page IDs."
pages(ids: [ID!], after: String, before: String, first: Int, last: Int): PageConnection!
pageSections(pageId: ID!, after: String, before: String, first: Int, last: Int): SectionConnection!
customWebDocuments(ids: [ID!], after: String, before: String, first: Int, last: Int): CustomDocumentConnection!
presetWebDocuments(ids: [ID!], after: String, before: String, first: Int, last: Int, isActive: Boolean): PresetDocumentConnection!
"List user groups."
Expand Down Expand Up @@ -834,6 +879,25 @@ type RepositoryGrepOutput {
elapsedMs: Int!
}

type Section {
id: ID!
pageId: ID!
title: String!
content: String!
createdAt: DateTime!
updatedAt: DateTime!
}

type SectionConnection {
edges: [SectionEdge!]!
pageInfo: PageInfo!
}

type SectionEdge {
node: Section!
cursor: String!
}

type SecuritySetting {
allowedRegisterDomainList: [String!]!
disableClientSideTelemetry: Boolean!
Expand Down
Loading
Loading