From 847571729b7e78fb0afed1e913c0f99d62a7f282 Mon Sep 17 00:00:00 2001 From: Per Stark Date: Tue, 4 Mar 2025 07:44:00 +0100 Subject: [PATCH] in progress, routers and main split up --- Cargo.lock | 179 ++- Cargo.toml | 64 +- assets/style.css | 1053 ----------------- crates/api-router/Cargo.toml | 18 + crates/api-router/src/api_state.rs | 33 + crates/api-router/src/lib.rs | 25 + .../api-router/src}/middleware_api_auth.rs | 6 +- .../api-router/src/routes}/ingress.rs | 15 +- crates/api-router/src/routes/mod.rs | 1 + crates/common/Cargo.toml | 47 + {src => crates/common/src}/error.rs | 0 .../src}/ingress/analysis/ingress_analyser.rs | 0 .../common/src}/ingress/analysis/mod.rs | 0 .../common/src}/ingress/analysis/prompt.rs | 0 .../ingress/analysis/types/graph_mapper.rs | 0 .../analysis/types/llm_analysis_result.rs | 0 .../common/src}/ingress/analysis/types/mod.rs | 0 .../common/src}/ingress/content_processor.rs | 0 .../common/src/ingress}/ingress_input.rs | 0 .../common/src/ingress}/ingress_object.rs | 0 .../common/src}/ingress/jobqueue.rs | 13 +- .../common/src/ingress}/mod.rs | 3 + .../common/src/ingress}/queue_task.rs | 2 +- {src => crates/common/src}/lib.rs | 0 {src => crates/common/src}/retrieval/graph.rs | 0 {src => crates/common/src}/retrieval/mod.rs | 0 .../common/src}/retrieval/query_helper.rs | 0 .../src}/retrieval/query_helper_prompt.rs | 0 .../common/src}/retrieval/vector.rs | 0 {src => crates/common/src}/storage/db.rs | 0 {src => crates/common/src}/storage/mod.rs | 0 .../common/src}/storage/types/analytics.rs | 0 .../common/src}/storage/types/conversation.rs | 0 .../common/src}/storage/types/file_info.rs | 0 .../common/src}/storage/types/job.rs | 2 +- .../src}/storage/types/knowledge_entity.rs | 0 .../storage/types/knowledge_relationship.rs | 0 .../common/src}/storage/types/message.rs | 0 .../common/src}/storage/types/mod.rs | 0 .../src}/storage/types/system_settings.rs | 0 .../common/src}/storage/types/text_chunk.rs | 0 .../common/src}/storage/types/text_content.rs | 0 .../common/src}/storage/types/user.rs | 0 {src => crates/common/src}/utils/config.rs | 0 {src => crates/common/src}/utils/embedding.rs | 0 {src => crates/common/src}/utils/mailer.rs | 0 {src => crates/common/src}/utils/mod.rs | 0 crates/html-router/Cargo.toml | 35 + .../html-router/src/html_state.rs | 42 +- .../mod.rs => crates/html-router/src/lib.rs | 46 +- .../html-router/src}/middleware_analytics.rs | 6 +- .../html-router/src/routes}/account.rs | 16 +- .../html-router/src/routes}/admin_panel.rs | 12 +- .../routes}/chat/message_response_stream.rs | 7 +- .../html-router/src/routes}/chat/mod.rs | 16 +- .../src/routes}/chat/references.rs | 7 +- .../html-router/src/routes}/content/mod.rs | 12 +- .../src/routes}/documentation/mod.rs | 19 +- .../html-router/src/routes}/gdpr.rs | 2 +- .../html-router/src/routes}/index.rs | 21 +- .../html-router/src/routes}/ingress_form.rs | 19 +- .../html-router/src/routes}/knowledge/mod.rs | 18 +- .../html-router/src/routes}/mod.rs | 4 +- .../html-router/src/routes}/search_result.rs | 11 +- .../html-router/src/routes}/signin.rs | 8 +- .../html-router/src/routes}/signout.rs | 2 +- .../html-router/src/routes}/signup.rs | 26 +- crates/main/Cargo.toml | 58 + crates/main/src/server.rs | 47 + {src/bin => crates/main/src}/worker.rs | 36 +- .../flake.nix | 57 - .../changes.md | 33 - page_base.html | 5 - postcss.config.js | 9 - src/bin/server.rs | 36 - src/ingress/mod.rs | 4 - src/server/routes/api/ingress_task.rs | 34 - src/server/routes/api/mod.rs | 4 - src/server/routes/api/query.rs | 34 - src/server/routes/api/queue_length.rs | 29 - 80 files changed, 599 insertions(+), 1577 deletions(-) create mode 100644 crates/api-router/Cargo.toml create mode 100644 crates/api-router/src/api_state.rs create mode 100644 crates/api-router/src/lib.rs rename {src/server => crates/api-router/src}/middleware_api_auth.rs (89%) rename {src/server/routes/api => crates/api-router/src/routes}/ingress.rs (91%) create mode 100644 crates/api-router/src/routes/mod.rs create mode 100644 crates/common/Cargo.toml rename {src => crates/common/src}/error.rs (100%) rename {src => crates/common/src}/ingress/analysis/ingress_analyser.rs (100%) rename {src => crates/common/src}/ingress/analysis/mod.rs (100%) rename {src => crates/common/src}/ingress/analysis/prompt.rs (100%) rename {src => crates/common/src}/ingress/analysis/types/graph_mapper.rs (100%) rename {src => crates/common/src}/ingress/analysis/types/llm_analysis_result.rs (100%) rename {src => crates/common/src}/ingress/analysis/types/mod.rs (100%) rename {src => crates/common/src}/ingress/content_processor.rs (100%) rename {src/ingress/types => crates/common/src/ingress}/ingress_input.rs (100%) rename {src/ingress/types => crates/common/src/ingress}/ingress_object.rs (100%) rename {src => crates/common/src}/ingress/jobqueue.rs (93%) rename {src/ingress/types => crates/common/src/ingress}/mod.rs (51%) rename {src/ingress/types => crates/common/src/ingress}/queue_task.rs (78%) rename {src => crates/common/src}/lib.rs (100%) rename {src => crates/common/src}/retrieval/graph.rs (100%) rename {src => crates/common/src}/retrieval/mod.rs (100%) rename {src => crates/common/src}/retrieval/query_helper.rs (100%) rename {src => crates/common/src}/retrieval/query_helper_prompt.rs (100%) rename {src => crates/common/src}/retrieval/vector.rs (100%) rename {src => crates/common/src}/storage/db.rs (100%) rename {src => crates/common/src}/storage/mod.rs (100%) rename {src => crates/common/src}/storage/types/analytics.rs (100%) rename {src => crates/common/src}/storage/types/conversation.rs (100%) rename {src => crates/common/src}/storage/types/file_info.rs (100%) rename {src => crates/common/src}/storage/types/job.rs (89%) rename {src => crates/common/src}/storage/types/knowledge_entity.rs (100%) rename {src => crates/common/src}/storage/types/knowledge_relationship.rs (100%) rename {src => crates/common/src}/storage/types/message.rs (100%) rename {src => crates/common/src}/storage/types/mod.rs (100%) rename {src => crates/common/src}/storage/types/system_settings.rs (100%) rename {src => crates/common/src}/storage/types/text_chunk.rs (100%) rename {src => crates/common/src}/storage/types/text_content.rs (100%) rename {src => crates/common/src}/storage/types/user.rs (100%) rename {src => crates/common/src}/utils/config.rs (100%) rename {src => crates/common/src}/utils/embedding.rs (100%) rename {src => crates/common/src}/utils/mailer.rs (100%) rename {src => crates/common/src}/utils/mod.rs (100%) create mode 100644 crates/html-router/Cargo.toml rename src/server/mod.rs => crates/html-router/src/html_state.rs (67%) rename src/server/routes/mod.rs => crates/html-router/src/lib.rs (80%) rename {src/server => crates/html-router/src}/middleware_analytics.rs (88%) rename {src/server/routes/html => crates/html-router/src/routes}/account.rs (93%) rename {src/server/routes/html => crates/html-router/src/routes}/admin_panel.rs (94%) rename {src/server/routes/html => crates/html-router/src/routes}/chat/message_response_stream.rs (99%) rename {src/server/routes/html => crates/html-router/src/routes}/chat/mod.rs (96%) rename {src/server/routes/html => crates/html-router/src/routes}/chat/references.rs (93%) rename {src/server/routes/html => crates/html-router/src/routes}/content/mod.rs (94%) rename {src/server/routes/html => crates/html-router/src/routes}/documentation/mod.rs (87%) rename {src/server/routes/html => crates/html-router/src/routes}/gdpr.rs (94%) rename {src/server/routes/html => crates/html-router/src/routes}/index.rs (96%) rename {src/server/routes/html => crates/html-router/src/routes}/ingress_form.rs (94%) rename {src/server/routes/html => crates/html-router/src/routes}/knowledge/mod.rs (97%) rename {src/server/routes/html => crates/html-router/src/routes}/mod.rs (95%) rename {src/server/routes/html => crates/html-router/src/routes}/search_result.rs (91%) rename {src/server/routes/html => crates/html-router/src/routes}/signin.rs (91%) rename {src/server/routes/html => crates/html-router/src/routes}/signout.rs (89%) rename {src/server/routes/html => crates/html-router/src/routes}/signup.rs (74%) create mode 100644 crates/main/Cargo.toml create mode 100644 crates/main/src/server.rs rename {src/bin => crates/main/src}/worker.rs (83%) delete mode 100644 data/182cb7b4-2286-4f40-9237-30b812d67696/flake.nix delete mode 100644 data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md delete mode 100644 page_base.html delete mode 100644 postcss.config.js delete mode 100644 src/bin/server.rs delete mode 100644 src/ingress/mod.rs delete mode 100644 src/server/routes/api/ingress_task.rs delete mode 100644 src/server/routes/api/mod.rs delete mode 100644 src/server/routes/api/query.rs delete mode 100644 src/server/routes/api/queue_length.rs diff --git a/Cargo.lock b/Cargo.lock index c2146ac..bd55943 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,21 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +[[package]] +name = "api-router" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axum_typed_multipart", + "common", + "futures", + "serde", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "approx" version = "0.4.0" @@ -1029,6 +1044,50 @@ dependencies = [ "inout", ] +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-openai", + "async-stream", + "axum", + "axum-htmx", + "axum_session", + "axum_session_auth", + "axum_session_surreal", + "axum_typed_multipart", + "chrono", + "chrono-tz", + "config", + "futures", + "json-stream-parser", + "lettre", + "mime", + "mime_guess", + "minijinja", + "minijinja-autoreload", + "minijinja-contrib", + "mockall", + "plotly", + "reqwest", + "scraper", + "serde", + "serde_json", + "sha2", + "surrealdb", + "tempfile", + "text-splitter", + "thiserror", + "tiktoken-rs", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1981,6 +2040,35 @@ dependencies = [ "windows", ] +[[package]] +name = "html-router" +version = "0.1.0" +dependencies = [ + "async-openai", + "async-stream", + "axum", + "axum-htmx", + "axum_session", + "axum_session_auth", + "axum_session_surreal", + "axum_typed_multipart", + "chrono-tz", + "common", + "futures", + "json-stream-parser", + "minijinja", + "minijinja-autoreload", + "minijinja-contrib", + "plotly", + "serde", + "serde_json", + "surrealdb", + "tempfile", + "tokio", + "tower-http", + "tracing", +] + [[package]] name = "html5ever" version = "0.27.0" @@ -2626,6 +2714,53 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "main" +version = "0.1.0" +dependencies = [ + "anyhow", + "api-router", + "async-openai", + "async-stream", + "axum", + "axum-htmx", + "axum_session", + "axum_session_auth", + "axum_session_surreal", + "axum_typed_multipart", + "chrono", + "chrono-tz", + "common", + "config", + "futures", + "html-router", + "json-stream-parser", + "lettre", + "mime", + "mime_guess", + "minijinja", + "minijinja-autoreload", + "minijinja-contrib", + "mockall", + "plotly", + "reqwest", + "scraper", + "serde", + "serde_json", + "sha2", + "surrealdb", + "tempfile", + "text-splitter", + "thiserror", + "tiktoken-rs", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + [[package]] name = "maplit" version = "1.0.2" @@ -6034,47 +6169,3 @@ dependencies = [ "quote", "syn 2.0.87", ] - -[[package]] -name = "zettle_db" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-openai", - "async-stream", - "axum", - "axum-htmx", - "axum_session", - "axum_session_auth", - "axum_session_surreal", - "axum_typed_multipart", - "chrono", - "chrono-tz", - "config", - "futures", - "json-stream-parser", - "lettre", - "mime", - "mime_guess", - "minijinja", - "minijinja-autoreload", - "minijinja-contrib", - "mockall", - "plotly", - "reqwest", - "scraper", - "serde", - "serde_json", - "sha2", - "surrealdb", - "tempfile", - "text-splitter", - "thiserror", - "tiktoken-rs", - "tokio", - "tower-http", - "tracing", - "tracing-subscriber", - "url", - "uuid", -] diff --git a/Cargo.toml b/Cargo.toml index acd27f6..d4b2c68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,54 +1,16 @@ -[package] -name = "zettle_db" -version = "0.1.0" -edition = "2021" +[workspace] +members = [ + "crates/main", + "crates/common", + "crates/api-router" +, "crates/html-router"] +resolver = "2" -[dependencies] -anyhow = "1.0.94" -async-openai = "0.24.1" -async-stream = "0.3.6" -axum = { version = "0.7.5", features = ["multipart", "macros"] } -axum-htmx = "0.6.0" -axum_session = "0.14.4" -axum_session_auth = "0.14.1" -axum_session_surreal = "0.2.1" -axum_typed_multipart = "0.12.1" -chrono = { version = "0.4.39", features = ["serde"] } -chrono-tz = "0.10.1" -config = "0.15.4" -futures = "0.3.31" -json-stream-parser = "0.1.4" -lettre = { version = "0.11.11", features = ["rustls-tls"] } -mime = "0.3.17" -mime_guess = "2.0.5" -minijinja = { version = "2.5.0", features = ["loader", "multi_template"] } -minijinja-autoreload = "2.5.0" -minijinja-contrib = { version = "2.6.0", features = ["datetime", "timezone"] } -mockall = "0.13.0" -plotly = "0.12.1" -reqwest = {version = "0.12.12", features = ["charset", "json"]} -scraper = "0.22.0" -serde = { version = "1.0.210", features = ["derive"] } -serde_json = "1.0.128" -sha2 = "0.10.8" -surrealdb = "2.0.4" -tempfile = "3.12.0" -text-splitter = "0.18.1" -thiserror = "1.0.63" -tiktoken-rs = "0.6.0" +[workspace.dependencies] tokio = { version = "1.40.0", features = ["full"] } -tower-http = { version = "0.6.2", features = ["fs"] } +serde = { version = "1.0.210", features = ["derive"] } +axum = { version = "0.7.5", features = ["multipart", "macros"] } +serde_json = "1.0.128" +thiserror = "1.0.63" +anyhow = "1.0.94" tracing = "0.1.40" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -url = { version = "2.5.2", features = ["serde"] } -uuid = { version = "1.10.0", features = ["v4", "serde"] } - - -[[bin]] -name = "server" -path = "src/bin/server.rs" - -[[bin]] -name = "worker" -path = "src/bin/worker.rs" - diff --git a/assets/style.css b/assets/style.css index 31b9fcb..ac6bf8e 100644 --- a/assets/style.css +++ b/assets/style.css @@ -942,91 +942,6 @@ inset: auto auto var(--tt-tail) 50%; } } - .dropdown { - position: relative; - display: inline-block; - position-area: var(--anchor-v, bottom) var(--anchor-h, span-right); - & > *:not(summary):focus { - outline: 2px solid transparent; - outline-offset: 2px; - } - .dropdown-content { - position: absolute; - } - &:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within) { - .dropdown-content { - display: none; - transform-origin: top; - opacity: 0%; - scale: 95%; - } - } - &[popover], .dropdown-content { - @starting-style { - scale: 95%; - } - @starting-style { - opacity: 0; - } - z-index: 999; - animation: dropdown 0.2s; - transition-property: opacity, scale, display; - transition-behavior: allow-discrete; - transition-duration: 0.2s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - &.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within { - [tabindex]:first-child { - pointer-events: none; - } - .dropdown-content { - opacity: 100%; - } - } - &.dropdown-hover:hover { - .dropdown-content { - opacity: 100%; - scale: 100%; - } - } - &:is(details) { - summary { - &::-webkit-details-marker { - display: none; - } - } - } - &.dropdown-open, &:focus, &:focus-within { - .dropdown-content { - scale: 100%; - } - } - &:where([popover]) { - background: transparent; - } - &[popover] { - position: fixed; - color: inherit; - @supports not (position-area: bottom) { - margin: auto; - &.dropdown-open:not(:popover-open) { - display: none; - transform-origin: top; - opacity: 0%; - scale: 95%; - } - &::backdrop { - background-color: color-mix(in oklab, black 30%, transparent); - } - } - &:not(.dropdown-open, :popover-open) { - display: none; - transform-origin: top; - opacity: 0%; - scale: 95%; - } - } - } .floating-label { position: relative; display: block; @@ -1074,39 +989,6 @@ font-size: 1.375rem; } } - .collapse-arrow { - > .collapse-title:after { - position: absolute; - display: block; - height: 0.5rem; - width: 0.5rem; - transform: translateY(-100%) rotate(45deg); - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 0.2s; - top: 1.9rem; - inset-inline-end: 1.4rem; - content: ""; - transform-origin: 75% 75%; - box-shadow: 2px 2px; - pointer-events: none; - } - } - .collapse-plus { - > .collapse-title:after { - position: absolute; - display: block; - height: 0.5rem; - width: 0.5rem; - transition-property: all; - transition-duration: 300ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - top: 0.9rem; - inset-inline-end: 1.4rem; - content: "+"; - pointer-events: none; - } - } .drawer-open { > .drawer-side { overflow-y: auto; @@ -1172,134 +1054,6 @@ mask-position: center; mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); } - .collapse { - &:not(td, tr, colgroup) { - visibility: visible; - } - position: relative; - display: grid; - overflow: hidden; - border-radius: var(--rounded-box, 1rem); - width: 100%; - grid-template-rows: max-content 0fr; - transition: grid-template-rows 0.2s; - > input:is([type="checkbox"], [type="radio"]) { - grid-column-start: 1; - grid-row-start: 1; - appearance: none; - opacity: 0; - z-index: 1; - width: 100%; - padding: 1rem; - padding-inline-end: 3rem; - min-height: 3.75rem; - transition: background-color 0.2s ease-out; - } - &:is([open], .collapse-open, :focus:not(.collapse-close)), - &:not(.collapse-close):has(> input:is([type="checkbox"], [type="radio"]):checked) { - grid-template-rows: max-content 1fr; - } - &:is([open], .collapse-open, :focus:not(.collapse-close)) > .collapse-content, - &:not(.collapse-close) - > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content) { - visibility: visible; - min-height: fit-content; - } - &, - &:has(.collapse-title, > input:is([type="checkbox"], [type="radio"])) { - &:focus-visible { - outline-color: var(--color-base-content); - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; - } - } - &:not(.collapse-open, .collapse-close) { - > input[type="checkbox"], - > input[type="radio"]:not(:checked), - > .collapse-title { - cursor: pointer; - } - } - &:focus:not(.collapse-open, .collapse-close, .collapse[open]) > .collapse-title { - cursor: unset; - } - &:is([open], .collapse-open, :focus:not(.collapse-close)) > :where(.collapse-content), - &:not(.collapse-close) - > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content) { - padding-bottom: 1rem; - transition: padding 0.2s ease-out, - background-color 0.2s ease-out; - } - &:is([open], .collapse-open) { - &.collapse-arrow { - > .collapse-title:after { - transform: translateY(-50%) rotate(225deg); - } - } - } - &.collapse-arrow:focus:not(.collapse-close) { - > .collapse-title:after { - transform: translateY(-50%) rotate(225deg); - } - } - &.collapse-arrow:not(.collapse-close) { - > input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after { - transform: translateY(-50%) rotate(225deg); - } - } - &[open] { - &.collapse-plus { - > .collapse-title:after { - content: "−"; - } - } - } - &.collapse-open { - &.collapse-plus { - > .collapse-title:after { - content: "−"; - } - } - } - &.collapse-plus:focus:not(.collapse-close) { - > .collapse-title:after { - content: "−"; - } - } - &.collapse-plus:not(.collapse-close) { - > input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after { - content: "−"; - } - } - &:is(details) { - width: 100%; - & summary { - position: relative; - display: block; - &::-webkit-details-marker { - display: none; - } - } - } - &:is(details) summary { - outline: none; - } - } - .collapse-content { - grid-column-start: 1; - grid-row-start: 1; - visibility: hidden; - grid-column-start: 1; - grid-row-start: 2; - min-height: 0; - transition: visibility 0.2s; - transition: padding 0.2s ease-out, - background-color 0.2s ease-out; - padding-left: 1rem; - padding-right: 1rem; - cursor: unset; - } .validator { &:user-valid, &:has(:user-valid) { &, &:focus, &:checked, &[aria-checked="true"], &:focus-within { @@ -1320,9 +1074,6 @@ visibility: hidden; } } - .collapse { - visibility: collapse; - } .visible { visibility: visible; } @@ -1709,83 +1460,6 @@ opacity: 20%; } } - .range { - appearance: none; - webkit-appearance: none; - --range-thumb: var(--color-base-100); - --range-thumb-size: calc(var(--size-selector, 0.25rem) * 6); - --range-progress: currentColor; - --range-fill: 1; - --range-p: 0.25rem; - --range-bg: color-mix(in oklab, currentColor 10%, transparent); - cursor: pointer; - overflow: hidden; - background-color: transparent; - vertical-align: middle; - width: clamp(3rem, 20rem, 100%); - border-radius: calc(var(--radius-selector) + var(--range-p) + 1px); - border: none; - height: var(--range-thumb-size); - [dir="rtl"] & { - --range-dir: -1; - } - &:focus { - outline: none; - } - &:focus-visible { - outline: 2px solid; - outline-offset: 2px; - } - &::-webkit-slider-runnable-track { - width: 100%; - background-color: var(--range-bg); - border-radius: var(--radius-selector); - height: calc(var(--range-thumb-size) * 0.5); - @media (forced-colors: active) { - border: 1px solid; - } - } - &::-webkit-slider-thumb { - position: relative; - box-sizing: border-box; - border-radius: calc(var(--radius-selector) + var(--range-p) + 1px); - background-color: currentColor; - height: var(--range-thumb-size); - width: var(--range-thumb-size); - border: var(--range-p) solid; - appearance: none; - webkit-appearance: none; - top: 50%; - color: var(--range-progress); - transform: translateY(-50%); - box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), transparent), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill)); - } - &::-moz-range-track { - width: 100%; - background-color: var(--range-bg); - border-radius: var(--radius-selector); - height: calc(var(--range-thumb-size) * 0.5); - @media (forced-colors: active) { - border: 1px solid; - } - } - &::-moz-range-thumb { - position: relative; - box-sizing: border-box; - border-radius: calc(var(--radius-selector) + var(--range-p) + 1px); - background-color: currentColor; - height: var(--range-thumb-size); - width: var(--range-thumb-size); - border: var(--range-p) solid; - top: 50%; - color: var(--range-progress); - box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), transparent), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill)); - } - &:disabled { - cursor: not-allowed; - opacity: 30%; - } - } .table { font-size: 0.875rem; position: relative; @@ -2114,31 +1788,6 @@ } } } - .indicator { - position: relative; - display: inline-flex; - width: max-content; - :where(.indicator-item) { - z-index: 1; - position: absolute; - white-space: nowrap; - top: var(--inidicator-t, 0); - bottom: var(--inidicator-b, auto); - left: var(--inidicator-s, auto); - right: var(--inidicator-e, 0); - translate: var(--inidicator-x, 50%) var(--indicator-y, -50%); - } - } - .collapse-title { - grid-column-start: 1; - grid-row-start: 1; - position: relative; - width: 100%; - padding: 1rem; - padding-inline-end: 3rem; - min-height: 3.75rem; - transition: background-color 0.2s ease-out; - } .stats { position: relative; display: inline-grid; @@ -2164,9 +1813,6 @@ .static { position: static; } - .sticky { - position: sticky; - } .inset-x-0 { inset-inline: calc(var(--spacing) * 0); } @@ -2221,9 +1867,6 @@ } } } - .\!top-2 { - top: calc(var(--spacing) * 2) !important; - } .\!top-2\.5 { top: calc(var(--spacing) * 2.5) !important; } @@ -2242,18 +1885,6 @@ .bottom-0 { bottom: calc(var(--spacing) * 0); } - .bottom-0\.5 { - bottom: calc(var(--spacing) * 0.5); - } - .bottom-1 { - bottom: calc(var(--spacing) * 1); - } - .bottom-2 { - bottom: calc(var(--spacing) * 2); - } - .bottom-4 { - bottom: calc(var(--spacing) * 4); - } .\!left-3 { left: calc(var(--spacing) * 3) !important; } @@ -2533,89 +2164,6 @@ cursor: not-allowed; } } - .isolate { - isolation: isolate; - } - .stack { - display: inline-grid; - grid-template-columns: 3px 4px 1fr 4px 3px; - grid-template-rows: 3px 4px 1fr 4px 3px; - & > * { - height: 100%; - width: 100%; - &:not(:nth-child(1)) { - width: 100%; - opacity: 70%; - } - &:nth-child(2) { - z-index: 2; - opacity: 90%; - } - &:nth-child(1) { - z-index: 3; - width: 100%; - } - } - &, &.stack-bottom { - > * { - grid-column: 3 / 4; - grid-row: 3 / 6; - &:nth-child(2) { - grid-column: 2 / 5; - grid-row: 2 / 5; - } - &:nth-child(1) { - grid-column: 1 / 6; - grid-row: 1 / 4; - } - } - } - &.stack-top { - > * { - grid-column: 3 / 4; - grid-row: 1 / 4; - &:nth-child(2) { - grid-column: 2 / 5; - grid-row: 2 / 5; - } - &:nth-child(1) { - grid-column: 1 / 6; - grid-row: 3 / 6; - } - } - } - &.stack-start { - > * { - grid-column: 1 / 4; - grid-row: 3 / 4; - &:nth-child(2) { - grid-column: 2 / 5; - grid-row: 2 / 5; - } - &:nth-child(1) { - grid-column: 3 / 6; - grid-row: 1 / 6; - } - } - } - &.stack-end { - > * { - grid-column: 3 / 6; - grid-row: 3 / 4; - &:nth-child(2) { - grid-column: 2 / 5; - grid-row: 2 / 5; - } - &:nth-child(1) { - grid-column: 1 / 4; - grid-row: 1 / 6; - } - } - } - } - .z-5 { - z-index: 5; - } .z-10 { z-index: 10; } @@ -2680,22 +2228,12 @@ grid-row-start: 1; min-width: calc(0.25rem * 0); } - .chat-image { - grid-row: span 2 / span 2; - align-self: flex-end; - } .chat-footer { grid-row-start: 3; display: flex; gap: calc(0.25rem * 1); font-size: 0.6875rem; } - .chat-header { - grid-row-start: 1; - display: flex; - gap: calc(0.25rem * 1); - font-size: 0.6875rem; - } .list-col-wrap { grid-row-start: 2; } @@ -2717,12 +2255,6 @@ max-width: 96rem; } } - .m-0 { - margin: calc(var(--spacing) * 0); - } - .m-9 { - margin: calc(var(--spacing) * 9); - } .filter { display: flex; flex-wrap: wrap; @@ -2790,16 +2322,6 @@ gap: calc(0.25rem * 4); } } - .input-lg { - --size: calc(var(--size-field, 0.25rem) * 12); - font-size: 1.125rem; - &[type="number"] { - &::-webkit-inner-spin-button { - margin-block: calc(0.25rem * -3); - margin-inline-end: calc(0.25rem * -3); - } - } - } .input-md { --size: calc(var(--size-field, 0.25rem) * 10); font-size: 0.875rem; @@ -2810,39 +2332,6 @@ } } } - .input-sm { - --size: calc(var(--size-field, 0.25rem) * 8); - font-size: 0.75rem; - &[type="number"] { - &::-webkit-inner-spin-button { - margin-block: calc(0.25rem * -2); - margin-inline-end: calc(0.25rem * -3); - } - } - } - .input-xl { - --size: calc(var(--size-field, 0.25rem) * 14); - font-size: 1.375rem; - &[type="number"] { - &::-webkit-inner-spin-button { - margin-block: calc(0.25rem * -4); - margin-inline-end: calc(0.25rem * -3); - } - } - } - .input-xs { - --size: calc(var(--size-field, 0.25rem) * 6); - font-size: 0.6875rem; - &[type="number"] { - &::-webkit-inner-spin-button { - margin-block: calc(0.25rem * -1); - margin-inline-end: calc(0.25rem * -3); - } - } - } - .my-0 { - margin-block: calc(var(--spacing) * 0); - } .my-2 { margin-block: calc(var(--spacing) * 2); } @@ -2912,12 +2401,6 @@ } } } - .join-item { - &:where(*:not(:first-child)) { - margin-inline-start: calc(var(--border, 1px) * -1); - margin-block-start: 0; - } - } .prose { color: var(--tw-prose-body); max-width: 90ch; @@ -3333,205 +2816,6 @@ margin-bottom: 0; } } - .prose-base { - font-size: 1rem; - line-height: 1.75; - :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - margin-bottom: 1.25em; - } - :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 1.25em; - line-height: 1.6; - margin-top: 1.2em; - margin-bottom: 1.2em; - } - :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.6em; - margin-bottom: 1.6em; - padding-inline-start: 1em; - } - :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 2.25em; - margin-top: 0; - margin-bottom: 0.8888889em; - line-height: 1.1111111; - } - :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 1.5em; - margin-top: 2em; - margin-bottom: 1em; - line-height: 1.3333333; - } - :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 1.25em; - margin-top: 1.6em; - margin-bottom: 0.6em; - line-height: 1.6; - } - :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.5em; - margin-bottom: 0.5em; - line-height: 1.5; - } - :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 2em; - margin-bottom: 2em; - } - :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 2em; - margin-bottom: 2em; - } - :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - margin-bottom: 0; - } - :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 2em; - margin-bottom: 2em; - } - :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.875em; - border-radius: 0.3125rem; - padding-top: 0.1875em; - padding-inline-end: 0.375em; - padding-bottom: 0.1875em; - padding-inline-start: 0.375em; - } - :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.875em; - } - :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.875em; - } - :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.9em; - } - :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.875em; - line-height: 1.7142857; - margin-top: 1.7142857em; - margin-bottom: 1.7142857em; - border-radius: 0.375rem; - padding-top: 0.8571429em; - padding-inline-end: 1.1428571em; - padding-bottom: 0.8571429em; - padding-inline-start: 1.1428571em; - } - :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - margin-bottom: 1.25em; - padding-inline-start: 1.625em; - } - :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - margin-bottom: 1.25em; - padding-inline-start: 1.625em; - } - :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0.5em; - margin-bottom: 0.5em; - } - :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-start: 0.375em; - } - :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-start: 0.375em; - } - :where(.prose-base > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0.75em; - margin-bottom: 0.75em; - } - :where(.prose-base > ul > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - } - :where(.prose-base > ul > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-bottom: 1.25em; - } - :where(.prose-base > ol > li > p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - } - :where(.prose-base > ol > li > p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-bottom: 1.25em; - } - :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0.75em; - margin-bottom: 0.75em; - } - :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - margin-bottom: 1.25em; - } - :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 1.25em; - } - :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0.5em; - padding-inline-start: 1.625em; - } - :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 3em; - margin-bottom: 3em; - } - :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - } - :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - } - :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - } - :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - } - :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.875em; - line-height: 1.7142857; - } - :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-end: 0.5714286em; - padding-bottom: 0.5714286em; - padding-inline-start: 0.5714286em; - } - :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-start: 0; - } - :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-end: 0; - } - :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-top: 0.5714286em; - padding-inline-end: 0.5714286em; - padding-bottom: 0.5714286em; - padding-inline-start: 0.5714286em; - } - :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-start: 0; - } - :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - padding-inline-end: 0; - } - :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 2em; - margin-bottom: 2em; - } - :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - margin-bottom: 0; - } - :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - font-size: 0.875em; - line-height: 1.4285714; - margin-top: 0.8571429em; - } - :where(.prose-base > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-top: 0; - } - :where(.prose-base > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { - margin-bottom: 0; - } - } .prose-sm { font-size: 0.875rem; line-height: 1.7142857; @@ -3773,9 +3057,6 @@ color: var(--color-base-content); font-weight: 600; } - .\!mb-0 { - margin-bottom: calc(var(--spacing) * 0) !important; - } .mb-2 { margin-bottom: calc(var(--spacing) * 2); } @@ -3886,22 +3167,6 @@ background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), transparent ); box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), transparent); } - .kbd { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-field); - background-color: var(--color-base-200); - vertical-align: middle; - padding-left: 0.5em; - padding-right: 0.5em; - border: var(--border) solid color-mix(in srgb, var(--color-base-content) 20%, transparent); - border-bottom: calc(var(--border) + 1px) solid color-mix(in srgb, var(--color-base-content) 20%, transparent); - --size: calc(var(--size-selector, 0.25rem) * 6); - font-size: 0.875rem; - height: var(--size); - min-width: var(--size); - } .footer { display: grid; width: 100%; @@ -4090,13 +3355,6 @@ column-gap: calc(0.25rem * 3); padding-block: calc(0.25rem * 1); } - .mask { - display: inline-block; - vertical-align: middle; - mask-size: contain; - mask-repeat: no-repeat; - mask-position: center; - } .block { display: block; } @@ -4112,21 +3370,6 @@ .hidden { display: none; } - .inline { - display: inline; - } - .inline-block { - display: inline-block; - } - .inline-flex { - display: inline-flex; - } - .inline-grid { - display: inline-grid; - } - .list-item { - display: list-item; - } .table { display: table; } @@ -4159,63 +3402,15 @@ .h-32 { height: calc(var(--spacing) * 32); } - .h-100 { - height: calc(var(--spacing) * 100); - } - .h-\[calc\(100vh-48px\)\] { - height: calc(100vh - 48px); - } - .h-\[calc\(100vh-52px\)\] { - height: calc(100vh - 52px); - } - .h-\[calc\(100vh-54px\)\] { - height: calc(100vh - 54px); - } - .h-\[calc\(100vh-60px\)\] { - height: calc(100vh - 60px); - } .h-\[calc\(100vh-65px\)\] { height: calc(100vh - 65px); } - .h-\[calc\(100vh-80px\)\] { - height: calc(100vh - 80px); - } - .h-\[calc\(100vh-120px\)\] { - height: calc(100vh - 120px); - } - .h-\[calc\(100vh-140px\)\] { - height: calc(100vh - 140px); - } - .h-\[calc\(100vh-160px\)\] { - height: calc(100vh - 160px); - } - .h-\[calc\(100vh-170px\)\] { - height: calc(100vh - 170px); - } .h-\[calc\(100vh-175px\)\] { height: calc(100vh - 175px); } - .h-\[calc\(100vh-180px\)\] { - height: calc(100vh - 180px); - } - .h-\[calc\(100vh-200px\)\] { - height: calc(100vh - 200px); - } - .h-\[calc\(100vh-240px\)\] { - height: calc(100vh - 240px); - } - .h-full { - height: 100%; - } - .max-h-full { - max-height: 100%; - } .min-h-\[100dvh\] { min-height: 100dvh; } - .min-h-full { - min-height: 100%; - } .min-h-screen { min-height: 100vh; } @@ -4246,15 +3441,9 @@ .max-w-3xl { max-width: var(--container-3xl); } - .max-w-\[10ch\] { - max-width: 10ch; - } .max-w-\[20ch\] { max-width: 20ch; } - .max-w-\[50px\] { - max-width: 50px; - } .max-w-\[90\%\] { max-width: 90%; } @@ -4273,24 +3462,15 @@ .flex-1 { flex: 1; } - .flex-shrink { - flex-shrink: 1; - } .flex-shrink-0 { flex-shrink: 0; } - .shrink { - flex-shrink: 1; - } .flex-grow { flex-grow: 1; } .grow { flex-grow: 1; } - .border-collapse { - border-collapse: collapse; - } .scale-75 { --tw-scale-x: 75%; --tw-scale-y: 75%; @@ -4321,9 +3501,6 @@ } } } - .transform { - transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y); - } .skeleton { border-radius: var(--radius-box); background-color: var(--color-base-300); @@ -4461,9 +3638,6 @@ .overflow-hidden { overflow: hidden; } - .overflow-visible { - overflow: visible; - } .overflow-x-auto { overflow-x: auto; } @@ -4516,18 +3690,9 @@ color: var(--color-success-content); --alert-color: var(--color-success); } - .border-base-300 { - border-color: var(--color-base-300); - } - .border-base-content { - border-color: var(--color-base-content); - } .border-base-content\/5 { border-color: color-mix(in oklab, var(--color-base-content) 5%, transparent); } - .border-gray-200 { - border-color: var(--color-gray-200); - } .border-neutral-700 { border-color: var(--color-neutral-700); } @@ -4540,12 +3705,6 @@ .bg-base-200 { background-color: var(--color-base-200); } - .bg-gray-50 { - background-color: var(--color-gray-50); - } - .bg-gray-200 { - background-color: var(--color-gray-200); - } .bg-neutral-800 { background-color: var(--color-neutral-800); } @@ -4591,24 +3750,6 @@ .p-4 { padding: calc(var(--spacing) * 4); } - .p-5 { - padding: calc(var(--spacing) * 5); - } - .p-7 { - padding: calc(var(--spacing) * 7); - } - .menu-title { - padding-inline: calc(0.25rem * 3); - padding-block: calc(0.25rem * 2); - color: color-mix(in oklab, var(--color-base-content) 40%, transparent); - font-size: 0.875rem; - font-weight: 600; - } - .badge-sm { - --size: calc(var(--size-selector, 0.25rem) * 5); - font-size: 0.75rem; - padding-inline: calc(0.25rem * 2.5 - var(--border)); - } .badge-xs { --size: calc(var(--size-selector, 0.25rem) * 4); font-size: 0.625rem; @@ -4632,24 +3773,12 @@ .px-1 { padding-inline: calc(var(--spacing) * 1); } - .px-2 { - padding-inline: calc(var(--spacing) * 2); - } .px-4 { padding-inline: calc(var(--spacing) * 4); } - .py-0 { - padding-block: calc(var(--spacing) * 0); - } - .py-1 { - padding-block: calc(var(--spacing) * 1); - } .py-4 { padding-block: calc(var(--spacing) * 4); } - .pt-1 { - padding-top: calc(var(--spacing) * 1); - } .pt-10 { padding-top: calc(var(--spacing) * 10); } @@ -4662,30 +3791,15 @@ .pr-12 { padding-right: calc(var(--spacing) * 12); } - .\!pb-0 { - padding-bottom: calc(var(--spacing) * 0) !important; - } .pb-0 { padding-bottom: calc(var(--spacing) * 0); } .pb-10 { padding-bottom: calc(var(--spacing) * 10); } - .pb-24 { - padding-bottom: calc(var(--spacing) * 24); - } - .pb-32 { - padding-bottom: calc(var(--spacing) * 32); - } - .pl-2 { - padding-left: calc(var(--spacing) * 2); - } .pl-3 { padding-left: calc(var(--spacing) * 3); } - .pl-4 { - padding-left: calc(var(--spacing) * 4); - } .text-center { text-align: center; } @@ -4749,10 +3863,6 @@ --tw-font-weight: var(--font-weight-light); font-weight: var(--font-weight-light); } - .font-medium { - --tw-font-weight: var(--font-weight-medium); - font-weight: var(--font-weight-medium); - } .font-semibold { --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); @@ -4761,9 +3871,6 @@ --tw-tracking: var(--tracking-wide); letter-spacing: var(--tracking-wide); } - .text-wrap { - text-wrap: wrap; - } .break-words { overflow-wrap: break-word; } @@ -4802,15 +3909,9 @@ .text-error { color: var(--color-error); } - .text-gray-400 { - color: var(--color-gray-400); - } .text-gray-500 { color: var(--color-gray-500); } - .text-gray-700 { - color: var(--color-gray-700); - } .text-primary { color: var(--color-primary); } @@ -4835,29 +3936,6 @@ .uppercase { text-transform: uppercase; } - .italic { - font-style: italic; - } - .btn-link { - text-decoration-line: underline; - outline-color: currentColor; - --btn-border: transparent; - --btn-bg: transparent; - --btn-fg: var(--color-primary); - --btn-noise: none; - --btn-shadow: ""; - &:is(.btn-active, :hover, :active:focus, :focus-visible) { - text-decoration-line: underline; - --btn-border: transparent; - --btn-bg: transparent; - } - } - .underline { - text-decoration-line: underline; - } - .opacity-0 { - opacity: 0%; - } .opacity-50 { opacity: 50%; } @@ -4867,14 +3945,6 @@ .opacity-70 { opacity: 70%; } - .swap-active { - .swap-off { - opacity: 0%; - } - .swap-on { - opacity: 100%; - } - } .shadow { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -4887,10 +3957,6 @@ --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .outline { - outline-style: var(--tw-outline-style); - outline-width: 1px; - } .btn-ghost { &:not(.btn-active, :hover, :active:focus, :focus-visible) { --btn-shadow: ""; @@ -4906,10 +3972,6 @@ .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } - .backdrop-filter { - -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - } .transition { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); @@ -4924,14 +3986,6 @@ --tw-ease: var(--ease-in); transition-timing-function: var(--ease-in); } - .ease-in-out { - --tw-ease: var(--ease-in-out); - transition-timing-function: var(--ease-in-out); - } - .ease-out { - --tw-ease: var(--ease-out); - transition-timing-function: var(--ease-out); - } .btn-accent { --btn-color: var(--color-accent); --btn-fg: var(--color-accent-content); @@ -4980,13 +4034,6 @@ --tw-prose-td-borders: color-mix(in oklab, var(--color-base-content) 20%, transparent); } } - .hover\:bg-gray-200 { - &:hover { - @media (hover: hover) { - background-color: var(--color-gray-200); - } - } - } .hover\:text-blue-700 { &:hover { @media (hover: hover) { @@ -5045,21 +4092,11 @@ padding-inline: calc(var(--spacing) * 0); } } - .sm\:pb-0 { - @media (width >= 40rem) { - padding-bottom: calc(var(--spacing) * 0); - } - } .sm\:pb-4 { @media (width >= 40rem) { padding-bottom: calc(var(--spacing) * 4); } } - .sm\:pb-32 { - @media (width >= 40rem) { - padding-bottom: calc(var(--spacing) * 32); - } - } .sm\:text-6xl { @media (width >= 40rem) { font-size: var(--text-6xl); @@ -5335,15 +4372,6 @@ display: none; } } - .dark\:hover\:bg-gray-700 { - @media (prefers-color-scheme: dark) { - &:hover { - @media (hover: hover) { - background-color: var(--color-gray-700); - } - } - } - } .prose-h1\:mb-2 { & :is(:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *))) { margin-bottom: calc(var(--spacing) * 2); @@ -5821,11 +4849,6 @@ video { --tw-scale-x: 1; --tw-scale-y: 1; --tw-scale-z: 1; - --tw-rotate-x: rotateX(0); - --tw-rotate-y: rotateY(0); - --tw-rotate-z: rotateZ(0); - --tw-skew-x: skewX(0); - --tw-skew-y: skewY(0); --tw-space-y-reverse: 0; --tw-space-x-reverse: 0; --tw-border-style: solid; @@ -5852,7 +4875,6 @@ video { --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; - --tw-outline-style: solid; --tw-blur: initial; --tw-brightness: initial; --tw-contrast: initial; @@ -5862,15 +4884,6 @@ video { --tw-opacity: initial; --tw-saturate: initial; --tw-sepia: initial; - --tw-backdrop-blur: initial; - --tw-backdrop-brightness: initial; - --tw-backdrop-contrast: initial; - --tw-backdrop-grayscale: initial; - --tw-backdrop-hue-rotate: initial; - --tw-backdrop-invert: initial; - --tw-backdrop-opacity: initial; - --tw-backdrop-saturate: initial; - --tw-backdrop-sepia: initial; --tw-ease: initial; --tw-content: ""; } @@ -5917,31 +4930,6 @@ video { inherits: false; initial-value: 1; } -@property --tw-rotate-x { - syntax: "*"; - inherits: false; - initial-value: rotateX(0); -} -@property --tw-rotate-y { - syntax: "*"; - inherits: false; - initial-value: rotateY(0); -} -@property --tw-rotate-z { - syntax: "*"; - inherits: false; - initial-value: rotateZ(0); -} -@property --tw-skew-x { - syntax: "*"; - inherits: false; - initial-value: skewX(0); -} -@property --tw-skew-y { - syntax: "*"; - inherits: false; - initial-value: skewY(0); -} @property --tw-space-y-reverse { syntax: "*"; inherits: false; @@ -6062,11 +5050,6 @@ video { inherits: false; initial-value: 0 0 #0000; } -@property --tw-outline-style { - syntax: "*"; - inherits: false; - initial-value: solid; -} @property --tw-blur { syntax: "*"; inherits: false; @@ -6103,42 +5086,6 @@ video { syntax: "*"; inherits: false; } -@property --tw-backdrop-blur { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-brightness { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-contrast { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-grayscale { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-hue-rotate { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-invert { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-opacity { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-saturate { - syntax: "*"; - inherits: false; -} -@property --tw-backdrop-sepia { - syntax: "*"; - inherits: false; -} @property --tw-ease { syntax: "*"; inherits: false; diff --git a/crates/api-router/Cargo.toml b/crates/api-router/Cargo.toml new file mode 100644 index 0000000..6ae0a93 --- /dev/null +++ b/crates/api-router/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "api-router" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Workspace dependencies +tokio = { workspace = true } +serde = { workspace = true } +axum = { workspace = true } +tracing = { workspace = true } +anyhow = { workspace = true } + +tempfile = "3.12.0" +futures = "0.3.31" +axum_typed_multipart = "0.12.1" + +common = { path = "../common" } diff --git a/crates/api-router/src/api_state.rs b/crates/api-router/src/api_state.rs new file mode 100644 index 0000000..098843e --- /dev/null +++ b/crates/api-router/src/api_state.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use common::{ingress::jobqueue::JobQueue, storage::db::SurrealDbClient, utils::config::AppConfig}; + +#[derive(Clone)] +pub struct ApiState { + pub surreal_db_client: Arc, + pub job_queue: Arc, +} + +impl ApiState { + pub async fn new(config: &AppConfig) -> Result> { + let surreal_db_client = Arc::new( + SurrealDbClient::new( + &config.surrealdb_address, + &config.surrealdb_username, + &config.surrealdb_password, + &config.surrealdb_namespace, + &config.surrealdb_database, + ) + .await?, + ); + + surreal_db_client.ensure_initialized().await?; + + let app_state = ApiState { + surreal_db_client: surreal_db_client.clone(), + job_queue: Arc::new(JobQueue::new(surreal_db_client)), + }; + + Ok(app_state) + } +} diff --git a/crates/api-router/src/lib.rs b/crates/api-router/src/lib.rs new file mode 100644 index 0000000..c88a3e7 --- /dev/null +++ b/crates/api-router/src/lib.rs @@ -0,0 +1,25 @@ +use api_state::ApiState; +use axum::{ + extract::{DefaultBodyLimit, FromRef}, + middleware::from_fn_with_state, + routing::post, + Router, +}; +use middleware_api_auth::api_auth; +use routes::ingress::ingress_data; + +pub mod api_state; +mod middleware_api_auth; +mod routes; + +/// Router for API functionality, version 1 +pub fn api_routes_v1(app_state: &ApiState) -> Router +where + S: Clone + Send + Sync + 'static, + ApiState: FromRef, +{ + Router::new() + .route("/ingress", post(ingress_data)) + .layer(DefaultBodyLimit::max(1024 * 1024 * 1024)) + .route_layer(from_fn_with_state(app_state.clone(), api_auth)) +} diff --git a/src/server/middleware_api_auth.rs b/crates/api-router/src/middleware_api_auth.rs similarity index 89% rename from src/server/middleware_api_auth.rs rename to crates/api-router/src/middleware_api_auth.rs index 6f05823..f7cac7e 100644 --- a/src/server/middleware_api_auth.rs +++ b/crates/api-router/src/middleware_api_auth.rs @@ -4,12 +4,12 @@ use axum::{ response::Response, }; -use crate::{error::ApiError, storage::types::user::User}; +use common::{error::ApiError, storage::types::user::User}; -use super::AppState; +use crate::api_state::ApiState; pub async fn api_auth( - State(state): State, + State(state): State, mut request: Request, next: Next, ) -> Result { diff --git a/src/server/routes/api/ingress.rs b/crates/api-router/src/routes/ingress.rs similarity index 91% rename from src/server/routes/api/ingress.rs rename to crates/api-router/src/routes/ingress.rs index 080cfe3..f671ab0 100644 --- a/src/server/routes/api/ingress.rs +++ b/crates/api-router/src/routes/ingress.rs @@ -1,15 +1,16 @@ -use crate::{ - error::{ApiError, AppError}, - ingress::types::ingress_input::{create_ingress_objects, IngressInput}, - server::AppState, - storage::types::{file_info::FileInfo, user::User}, -}; use axum::{extract::State, http::StatusCode, response::IntoResponse, Extension}; use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart}; +use common::{ + error::{ApiError, AppError}, + ingress::ingress_input::{create_ingress_objects, IngressInput}, + storage::types::{file_info::FileInfo, user::User}, +}; use futures::{future::try_join_all, TryFutureExt}; use tempfile::NamedTempFile; use tracing::{debug, info}; +use crate::api_state::ApiState; + #[derive(Debug, TryFromMultipart)] pub struct IngressParams { pub content: Option, @@ -21,7 +22,7 @@ pub struct IngressParams { } pub async fn ingress_data( - State(state): State, + State(state): State, Extension(user): Extension, TypedMultipart(input): TypedMultipart, ) -> Result { diff --git a/crates/api-router/src/routes/mod.rs b/crates/api-router/src/routes/mod.rs new file mode 100644 index 0000000..d8d3864 --- /dev/null +++ b/crates/api-router/src/routes/mod.rs @@ -0,0 +1 @@ +pub mod ingress; diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml new file mode 100644 index 0000000..d0aaf3d --- /dev/null +++ b/crates/common/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Workspace dependencies +tokio = { workspace = true } +serde = { workspace = true } +axum = { workspace = true } +tracing = { workspace = true } +anyhow = { workspace = true } +thiserror = { workspace = true } +serde_json = { workspace = true } + +async-openai = "0.24.1" +async-stream = "0.3.6" +axum-htmx = "0.6.0" +axum_session = "0.14.4" +axum_session_auth = "0.14.1" +axum_session_surreal = "0.2.1" +axum_typed_multipart = "0.12.1" +chrono = { version = "0.4.39", features = ["serde"] } +chrono-tz = "0.10.1" +config = "0.15.4" +futures = "0.3.31" +json-stream-parser = "0.1.4" +lettre = { version = "0.11.11", features = ["rustls-tls"] } +mime = "0.3.17" +mime_guess = "2.0.5" +minijinja = { version = "2.5.0", features = ["loader", "multi_template"] } +minijinja-autoreload = "2.5.0" +minijinja-contrib = { version = "2.6.0", features = ["datetime", "timezone"] } +mockall = "0.13.0" +plotly = "0.12.1" +reqwest = {version = "0.12.12", features = ["charset", "json"]} +scraper = "0.22.0" +sha2 = "0.10.8" +surrealdb = "2.0.4" +tempfile = "3.12.0" +text-splitter = "0.18.1" +tiktoken-rs = "0.6.0" +tower-http = { version = "0.6.2", features = ["fs"] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +url = { version = "2.5.2", features = ["serde"] } +uuid = { version = "1.10.0", features = ["v4", "serde"] } + diff --git a/src/error.rs b/crates/common/src/error.rs similarity index 100% rename from src/error.rs rename to crates/common/src/error.rs diff --git a/src/ingress/analysis/ingress_analyser.rs b/crates/common/src/ingress/analysis/ingress_analyser.rs similarity index 100% rename from src/ingress/analysis/ingress_analyser.rs rename to crates/common/src/ingress/analysis/ingress_analyser.rs diff --git a/src/ingress/analysis/mod.rs b/crates/common/src/ingress/analysis/mod.rs similarity index 100% rename from src/ingress/analysis/mod.rs rename to crates/common/src/ingress/analysis/mod.rs diff --git a/src/ingress/analysis/prompt.rs b/crates/common/src/ingress/analysis/prompt.rs similarity index 100% rename from src/ingress/analysis/prompt.rs rename to crates/common/src/ingress/analysis/prompt.rs diff --git a/src/ingress/analysis/types/graph_mapper.rs b/crates/common/src/ingress/analysis/types/graph_mapper.rs similarity index 100% rename from src/ingress/analysis/types/graph_mapper.rs rename to crates/common/src/ingress/analysis/types/graph_mapper.rs diff --git a/src/ingress/analysis/types/llm_analysis_result.rs b/crates/common/src/ingress/analysis/types/llm_analysis_result.rs similarity index 100% rename from src/ingress/analysis/types/llm_analysis_result.rs rename to crates/common/src/ingress/analysis/types/llm_analysis_result.rs diff --git a/src/ingress/analysis/types/mod.rs b/crates/common/src/ingress/analysis/types/mod.rs similarity index 100% rename from src/ingress/analysis/types/mod.rs rename to crates/common/src/ingress/analysis/types/mod.rs diff --git a/src/ingress/content_processor.rs b/crates/common/src/ingress/content_processor.rs similarity index 100% rename from src/ingress/content_processor.rs rename to crates/common/src/ingress/content_processor.rs diff --git a/src/ingress/types/ingress_input.rs b/crates/common/src/ingress/ingress_input.rs similarity index 100% rename from src/ingress/types/ingress_input.rs rename to crates/common/src/ingress/ingress_input.rs diff --git a/src/ingress/types/ingress_object.rs b/crates/common/src/ingress/ingress_object.rs similarity index 100% rename from src/ingress/types/ingress_object.rs rename to crates/common/src/ingress/ingress_object.rs diff --git a/src/ingress/jobqueue.rs b/crates/common/src/ingress/jobqueue.rs similarity index 93% rename from src/ingress/jobqueue.rs rename to crates/common/src/ingress/jobqueue.rs index ad2fba4..22a57e1 100644 --- a/src/ingress/jobqueue.rs +++ b/crates/common/src/ingress/jobqueue.rs @@ -15,21 +15,17 @@ use crate::{ }, }; -use super::{content_processor::ContentProcessor, types::ingress_object::IngressObject}; +use super::{content_processor::ContentProcessor, ingress_object::IngressObject}; pub struct JobQueue { pub db: Arc, - pub openai_client: Arc>, } pub const MAX_ATTEMPTS: u32 = 3; impl JobQueue { - pub fn new( - db: Arc, - openai_client: Arc>, - ) -> Self { - Self { db, openai_client } + pub fn new(db: Arc) -> Self { + Self { db } } /// Creates a new job and stores it in the database @@ -147,6 +143,7 @@ impl JobQueue { &self, job: Job, processor: &ContentProcessor, + openai_client: Arc>, ) -> Result<(), AppError> { let current_attempts = match job.status { JobStatus::InProgress { attempts, .. } => attempts + 1, @@ -163,7 +160,7 @@ impl JobQueue { ) .await?; - let text_content = job.content.to_text_content(&self.openai_client).await?; + let text_content = job.content.to_text_content(&openai_client).await?; match processor.process(&text_content).await { Ok(_) => { diff --git a/src/ingress/types/mod.rs b/crates/common/src/ingress/mod.rs similarity index 51% rename from src/ingress/types/mod.rs rename to crates/common/src/ingress/mod.rs index a4cfc3c..fea78f0 100644 --- a/src/ingress/types/mod.rs +++ b/crates/common/src/ingress/mod.rs @@ -1,3 +1,6 @@ +pub mod analysis; +pub mod content_processor; pub mod ingress_input; pub mod ingress_object; +pub mod jobqueue; pub mod queue_task; diff --git a/src/ingress/types/queue_task.rs b/crates/common/src/ingress/queue_task.rs similarity index 78% rename from src/ingress/types/queue_task.rs rename to crates/common/src/ingress/queue_task.rs index d4d73b0..79ee882 100644 --- a/src/ingress/types/queue_task.rs +++ b/crates/common/src/ingress/queue_task.rs @@ -1,4 +1,4 @@ -use crate::ingress::types::ingress_object::IngressObject; +use crate::ingress::ingress_object::IngressObject; use serde::Serialize; #[derive(Serialize)] diff --git a/src/lib.rs b/crates/common/src/lib.rs similarity index 100% rename from src/lib.rs rename to crates/common/src/lib.rs diff --git a/src/retrieval/graph.rs b/crates/common/src/retrieval/graph.rs similarity index 100% rename from src/retrieval/graph.rs rename to crates/common/src/retrieval/graph.rs diff --git a/src/retrieval/mod.rs b/crates/common/src/retrieval/mod.rs similarity index 100% rename from src/retrieval/mod.rs rename to crates/common/src/retrieval/mod.rs diff --git a/src/retrieval/query_helper.rs b/crates/common/src/retrieval/query_helper.rs similarity index 100% rename from src/retrieval/query_helper.rs rename to crates/common/src/retrieval/query_helper.rs diff --git a/src/retrieval/query_helper_prompt.rs b/crates/common/src/retrieval/query_helper_prompt.rs similarity index 100% rename from src/retrieval/query_helper_prompt.rs rename to crates/common/src/retrieval/query_helper_prompt.rs diff --git a/src/retrieval/vector.rs b/crates/common/src/retrieval/vector.rs similarity index 100% rename from src/retrieval/vector.rs rename to crates/common/src/retrieval/vector.rs diff --git a/src/storage/db.rs b/crates/common/src/storage/db.rs similarity index 100% rename from src/storage/db.rs rename to crates/common/src/storage/db.rs diff --git a/src/storage/mod.rs b/crates/common/src/storage/mod.rs similarity index 100% rename from src/storage/mod.rs rename to crates/common/src/storage/mod.rs diff --git a/src/storage/types/analytics.rs b/crates/common/src/storage/types/analytics.rs similarity index 100% rename from src/storage/types/analytics.rs rename to crates/common/src/storage/types/analytics.rs diff --git a/src/storage/types/conversation.rs b/crates/common/src/storage/types/conversation.rs similarity index 100% rename from src/storage/types/conversation.rs rename to crates/common/src/storage/types/conversation.rs diff --git a/src/storage/types/file_info.rs b/crates/common/src/storage/types/file_info.rs similarity index 100% rename from src/storage/types/file_info.rs rename to crates/common/src/storage/types/file_info.rs diff --git a/src/storage/types/job.rs b/crates/common/src/storage/types/job.rs similarity index 89% rename from src/storage/types/job.rs rename to crates/common/src/storage/types/job.rs index eaab851..510cf20 100644 --- a/src/storage/types/job.rs +++ b/crates/common/src/storage/types/job.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use crate::{ingress::types::ingress_object::IngressObject, stored_object}; +use crate::{ingress::ingress_object::IngressObject, stored_object}; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum JobStatus { diff --git a/src/storage/types/knowledge_entity.rs b/crates/common/src/storage/types/knowledge_entity.rs similarity index 100% rename from src/storage/types/knowledge_entity.rs rename to crates/common/src/storage/types/knowledge_entity.rs diff --git a/src/storage/types/knowledge_relationship.rs b/crates/common/src/storage/types/knowledge_relationship.rs similarity index 100% rename from src/storage/types/knowledge_relationship.rs rename to crates/common/src/storage/types/knowledge_relationship.rs diff --git a/src/storage/types/message.rs b/crates/common/src/storage/types/message.rs similarity index 100% rename from src/storage/types/message.rs rename to crates/common/src/storage/types/message.rs diff --git a/src/storage/types/mod.rs b/crates/common/src/storage/types/mod.rs similarity index 100% rename from src/storage/types/mod.rs rename to crates/common/src/storage/types/mod.rs diff --git a/src/storage/types/system_settings.rs b/crates/common/src/storage/types/system_settings.rs similarity index 100% rename from src/storage/types/system_settings.rs rename to crates/common/src/storage/types/system_settings.rs diff --git a/src/storage/types/text_chunk.rs b/crates/common/src/storage/types/text_chunk.rs similarity index 100% rename from src/storage/types/text_chunk.rs rename to crates/common/src/storage/types/text_chunk.rs diff --git a/src/storage/types/text_content.rs b/crates/common/src/storage/types/text_content.rs similarity index 100% rename from src/storage/types/text_content.rs rename to crates/common/src/storage/types/text_content.rs diff --git a/src/storage/types/user.rs b/crates/common/src/storage/types/user.rs similarity index 100% rename from src/storage/types/user.rs rename to crates/common/src/storage/types/user.rs diff --git a/src/utils/config.rs b/crates/common/src/utils/config.rs similarity index 100% rename from src/utils/config.rs rename to crates/common/src/utils/config.rs diff --git a/src/utils/embedding.rs b/crates/common/src/utils/embedding.rs similarity index 100% rename from src/utils/embedding.rs rename to crates/common/src/utils/embedding.rs diff --git a/src/utils/mailer.rs b/crates/common/src/utils/mailer.rs similarity index 100% rename from src/utils/mailer.rs rename to crates/common/src/utils/mailer.rs diff --git a/src/utils/mod.rs b/crates/common/src/utils/mod.rs similarity index 100% rename from src/utils/mod.rs rename to crates/common/src/utils/mod.rs diff --git a/crates/html-router/Cargo.toml b/crates/html-router/Cargo.toml new file mode 100644 index 0000000..697b1d9 --- /dev/null +++ b/crates/html-router/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "html-router" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Workspace dependencies +tokio = { workspace = true } +serde = { workspace = true } +axum = { workspace = true } +tracing = { workspace = true } +serde_json = { workspace = true } + + +axum-htmx = "0.6.0" +axum_session = "0.14.4" +axum_session_auth = "0.14.1" +axum_session_surreal = "0.2.1" +axum_typed_multipart = "0.12.1" +futures = "0.3.31" +tempfile = "3.12.0" +async-stream = "0.3.6" +json-stream-parser = "0.1.4" +minijinja = { version = "2.5.0", features = ["loader", "multi_template"] } +minijinja-autoreload = "2.5.0" +minijinja-contrib = { version = "2.6.0", features = ["datetime", "timezone"] } +plotly = "0.12.1" +surrealdb = "2.0.4" +tower-http = { version = "0.6.2", features = ["fs"] } +chrono-tz = "0.10.1" +async-openai = "0.24.1" + + + +common = { path = "../common" } diff --git a/src/server/mod.rs b/crates/html-router/src/html_state.rs similarity index 67% rename from src/server/mod.rs rename to crates/html-router/src/html_state.rs index 378aca3..25d4d7d 100644 --- a/src/server/mod.rs +++ b/crates/html-router/src/html_state.rs @@ -1,21 +1,17 @@ -use crate::ingress::jobqueue::JobQueue; -use crate::storage::db::SurrealDbClient; -use crate::utils::config::AppConfig; -use crate::utils::mailer::Mailer; use axum_session::SessionStore; use axum_session_surreal::SessionSurrealPool; +use common::ingress::jobqueue::JobQueue; +use common::storage::db::SurrealDbClient; +use common::utils::config::AppConfig; +use common::utils::mailer::Mailer; use minijinja::{path_loader, Environment}; use minijinja_autoreload::AutoReloader; use std::path::PathBuf; use std::sync::Arc; use surrealdb::engine::any::Any; -pub mod middleware_analytics; -pub mod middleware_api_auth; -pub mod routes; - #[derive(Clone)] -pub struct AppState { +pub struct HtmlState { pub surreal_db_client: Arc, pub openai_client: Arc>, pub templates: Arc, @@ -24,10 +20,10 @@ pub struct AppState { pub session_store: Arc>>, } -impl AppState { +impl HtmlState { pub async fn new(config: &AppConfig) -> Result> { let reloader = AutoReloader::new(move |notifier| { - let template_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates"); + let template_path = get_templates_dir(); let mut env = Environment::new(); env.set_loader(path_loader(&template_path)); @@ -54,7 +50,7 @@ impl AppState { let session_store = Arc::new(surreal_db_client.create_session_store().await?); - let app_state = AppState { + let app_state = HtmlState { surreal_db_client: surreal_db_client.clone(), templates: Arc::new(reloader), openai_client: openai_client.clone(), @@ -63,10 +59,30 @@ impl AppState { &config.smtp_relayer, &config.smtp_password, )?), - job_queue: Arc::new(JobQueue::new(surreal_db_client, openai_client)), + job_queue: Arc::new(JobQueue::new(surreal_db_client)), session_store, }; Ok(app_state) } } + +pub fn get_workspace_root() -> PathBuf { + // Starts from CARGO_MANIFEST_DIR (e.g., /project/crates/html-router/) + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + // Navigate up to /path/to/project/crates + let crates_dir = manifest_dir + .parent() + .expect("Failed to find parent of manifest directory"); + + // Navigate up to workspace root + crates_dir + .parent() + .expect("Failed to find workspace root") + .to_path_buf() +} + +pub fn get_templates_dir() -> PathBuf { + get_workspace_root().join("templates") +} diff --git a/src/server/routes/mod.rs b/crates/html-router/src/lib.rs similarity index 80% rename from src/server/routes/mod.rs rename to crates/html-router/src/lib.rs index 8a3e8d6..4154d7a 100644 --- a/src/server/routes/mod.rs +++ b/crates/html-router/src/lib.rs @@ -1,19 +1,20 @@ -use api::{ - ingress::ingress_data, - ingress_task::{delete_queue_task, get_queue_tasks}, - query::query_handler, - queue_length::queue_length_handler, -}; +pub mod html_state; +mod middleware_analytics; +mod routes; + use axum::{ - extract::DefaultBodyLimit, + extract::FromRef, middleware::from_fn_with_state, routing::{delete, get, patch, post}, Router, }; -use axum_session::{SessionLayer, SessionStore}; +use axum_session::SessionLayer; use axum_session_auth::{AuthConfig, AuthSessionLayer}; use axum_session_surreal::SessionSurrealPool; -use html::{ +use common::storage::types::user::User; +use html_state::HtmlState; +use middleware_analytics::analytics_middleware; +use routes::{ account::{delete_account, set_api_key, show_account_page, update_timezone}, admin_panel::{show_admin_panel, toggle_registration_status}, chat::{ @@ -40,29 +41,12 @@ use html::{ use surrealdb::{engine::any::Any, Surreal}; use tower_http::services::ServeDir; -use crate::storage::types::user::User; - -use super::{middleware_analytics::analytics_middleware, middleware_api_auth::api_auth, AppState}; - -pub mod api; -pub mod html; - -/// Router for API functionality, version 1 -pub fn api_routes_v1(app_state: &AppState) -> Router { - Router::new() - // Ingress routes - .route("/ingress", post(ingress_data)) - .route("/message_count", get(queue_length_handler)) - .route("/queue", get(get_queue_tasks)) - .route("/queue/:delivery_tag", delete(delete_queue_task)) - .layer(DefaultBodyLimit::max(1024 * 1024 * 1024)) - // Query routes - .route("/query", post(query_handler)) - .route_layer(from_fn_with_state(app_state.clone(), api_auth)) -} - /// Router for HTML endpoints -pub fn html_routes(app_state: &AppState) -> Router { +pub fn html_routes(app_state: &HtmlState) -> Router +where + S: Clone + Send + Sync + 'static, + HtmlState: FromRef, +{ Router::new() .route("/", get(index_handler)) .route("/gdpr/accept", post(accept_gdpr)) diff --git a/src/server/middleware_analytics.rs b/crates/html-router/src/middleware_analytics.rs similarity index 88% rename from src/server/middleware_analytics.rs rename to crates/html-router/src/middleware_analytics.rs index fda992d..a76bd08 100644 --- a/src/server/middleware_analytics.rs +++ b/crates/html-router/src/middleware_analytics.rs @@ -6,12 +6,12 @@ use axum::{ use axum_session_surreal::SessionSurrealPool; use surrealdb::engine::any::Any; -use crate::storage::types::analytics::Analytics; +use common::storage::types::analytics::Analytics; -use super::AppState; +use crate::html_state::HtmlState; pub async fn analytics_middleware( - State(state): State, + State(state): State, session: axum_session::Session>, request: Request, next: Next, diff --git a/src/server/routes/html/account.rs b/crates/html-router/src/routes/account.rs similarity index 93% rename from src/server/routes/html/account.rs rename to crates/html-router/src/routes/account.rs index 7ed09ee..1c5e0ae 100644 --- a/src/server/routes/html/account.rs +++ b/crates/html-router/src/routes/account.rs @@ -10,14 +10,14 @@ use axum_session_surreal::SessionSurrealPool; use chrono_tz::TZ_VARIANTS; use surrealdb::{engine::any::Any, Surreal}; -use crate::{ +use common::{ error::{AppError, HtmlError}, - page_data, - server::{routes::html::render_template, AppState}, storage::{db::delete_item, types::user::User}, }; -use super::render_block; +use crate::{html_state::HtmlState, page_data}; + +use super::{render_block, render_template}; page_data!(AccountData, "auth/account_settings.html", { user: User, @@ -25,7 +25,7 @@ page_data!(AccountData, "auth/account_settings.html", { }); pub async fn show_account_page( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { // Early return if the user is not authenticated @@ -46,7 +46,7 @@ pub async fn show_account_page( } pub async fn set_api_key( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { // Early return if the user is not authenticated @@ -83,7 +83,7 @@ pub async fn set_api_key( } pub async fn delete_account( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { // Early return if the user is not authenticated @@ -109,7 +109,7 @@ pub struct UpdateTimezoneForm { } pub async fn update_timezone( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { diff --git a/src/server/routes/html/admin_panel.rs b/crates/html-router/src/routes/admin_panel.rs similarity index 94% rename from src/server/routes/html/admin_panel.rs rename to crates/html-router/src/routes/admin_panel.rs index a79b3af..67d996b 100644 --- a/src/server/routes/html/admin_panel.rs +++ b/crates/html-router/src/routes/admin_panel.rs @@ -7,14 +7,14 @@ use axum_session_auth::AuthSession; use axum_session_surreal::SessionSurrealPool; use surrealdb::{engine::any::Any, Surreal}; -use crate::{ +use common::{ error::HtmlError, - page_data, - server::{routes::html::render_template, AppState}, storage::types::{analytics::Analytics, system_settings::SystemSettings, user::User}, }; -use super::render_block; +use crate::{html_state::HtmlState, page_data}; + +use super::{render_block, render_template}; page_data!(AdminPanelData, "auth/admin_panel.html", { user: User, @@ -24,7 +24,7 @@ page_data!(AdminPanelData, "auth/admin_panel.html", { }); pub async fn show_admin_panel( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { // Early return if the user is not authenticated and admin @@ -82,7 +82,7 @@ pub struct RegistrationToggleData { } pub async fn toggle_registration_status( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(input): Form, ) -> Result { diff --git a/src/server/routes/html/chat/message_response_stream.rs b/crates/html-router/src/routes/chat/message_response_stream.rs similarity index 99% rename from src/server/routes/html/chat/message_response_stream.rs rename to crates/html-router/src/routes/chat/message_response_stream.rs index 8f5b123..1e89881 100644 --- a/src/server/routes/html/chat/message_response_stream.rs +++ b/crates/html-router/src/routes/chat/message_response_stream.rs @@ -21,14 +21,13 @@ use surrealdb::{engine::any::Any, Surreal}; use tokio::sync::{mpsc::channel, Mutex}; use tracing::{error, info}; -use crate::{ +use common::{ retrieval::{ combined_knowledge_entity_retrieval, query_helper::{ create_chat_request, create_user_message, format_entities_json, LLMResponseFormat, }, }, - server::{routes::html::render_template, AppState}, storage::{ db::{get_item, store_item, SurrealDbClient}, types::{ @@ -38,6 +37,8 @@ use crate::{ }, }; +use crate::{html_state::HtmlState, routes::render_template}; + // Error handling function fn create_error_stream( message: impl Into, @@ -87,7 +88,7 @@ pub struct QueryParams { } pub async fn get_response_stream( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Query(params): Query, ) -> Sse> + Send>>> { diff --git a/src/server/routes/html/chat/mod.rs b/crates/html-router/src/routes/chat/mod.rs similarity index 96% rename from src/server/routes/html/chat/mod.rs rename to crates/html-router/src/routes/chat/mod.rs index 5b78c15..906fd3b 100644 --- a/src/server/routes/html/chat/mod.rs +++ b/crates/html-router/src/routes/chat/mod.rs @@ -12,10 +12,8 @@ use axum_session_surreal::SessionSurrealPool; use surrealdb::{engine::any::Any, Surreal}; use tracing::info; -use crate::{ +use common::{ error::{AppError, HtmlError}, - page_data, - server::{routes::html::render_template, AppState}, storage::{ db::{get_item, store_item}, types::{ @@ -26,6 +24,8 @@ use crate::{ }, }; +use crate::{html_state::HtmlState, page_data, routes::render_template}; + // Update your ChatStartParams struct to properly deserialize the references #[derive(Debug, Deserialize)] pub struct ChatStartParams { @@ -52,7 +52,7 @@ page_data!(ChatData, "chat/base.html", { }); pub async fn show_initialized_chat( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { @@ -116,7 +116,7 @@ pub async fn show_initialized_chat( } pub async fn show_chat_base( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { info!("Displaying empty chat start"); @@ -151,7 +151,7 @@ pub struct NewMessageForm { pub async fn show_existing_chat( Path(conversation_id): Path, - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { info!("Displaying initialized chat with id: {}", conversation_id); @@ -189,7 +189,7 @@ pub async fn show_existing_chat( pub async fn new_user_message( Path(conversation_id): Path, - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { @@ -241,7 +241,7 @@ pub async fn new_user_message( } pub async fn new_chat_user_message( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { diff --git a/src/server/routes/html/chat/references.rs b/crates/html-router/src/routes/chat/references.rs similarity index 93% rename from src/server/routes/html/chat/references.rs rename to crates/html-router/src/routes/chat/references.rs index ee54d07..15b4ded 100644 --- a/src/server/routes/html/chat/references.rs +++ b/crates/html-router/src/routes/chat/references.rs @@ -8,17 +8,18 @@ use serde::Serialize; use surrealdb::{engine::any::Any, Surreal}; use tracing::info; -use crate::{ +use common::{ error::{AppError, HtmlError}, - server::{routes::html::render_template, AppState}, storage::{ db::get_item, types::{knowledge_entity::KnowledgeEntity, user::User}, }, }; +use crate::{html_state::HtmlState, routes::render_template}; + pub async fn show_reference_tooltip( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(reference_id): Path, ) -> Result { diff --git a/src/server/routes/html/content/mod.rs b/crates/html-router/src/routes/content/mod.rs similarity index 94% rename from src/server/routes/html/content/mod.rs rename to crates/html-router/src/routes/content/mod.rs index baace96..831c721 100644 --- a/src/server/routes/html/content/mod.rs +++ b/crates/html-router/src/routes/content/mod.rs @@ -6,13 +6,13 @@ use axum_session_auth::AuthSession; use axum_session_surreal::SessionSurrealPool; use surrealdb::{engine::any::Any, Surreal}; -use crate::{ +use common::{ error::HtmlError, - page_data, - server::AppState, storage::types::{text_content::TextContent, user::User}, }; +use crate::{html_state::HtmlState, page_data}; + use super::render_template; page_data!(ContentPageData, "content/base.html", { @@ -21,7 +21,7 @@ page_data!(ContentPageData, "content/base.html", { }); pub async fn show_content_page( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { // Early return if the user is not authenticated @@ -53,7 +53,7 @@ pub struct TextContentEditModal { } pub async fn show_text_content_edit_form( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { @@ -77,7 +77,7 @@ pub async fn show_text_content_edit_form( } pub async fn patch_text_content( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { diff --git a/src/server/routes/html/documentation/mod.rs b/crates/html-router/src/routes/documentation/mod.rs similarity index 87% rename from src/server/routes/html/documentation/mod.rs rename to crates/html-router/src/routes/documentation/mod.rs index 96fe9b9..5b246c1 100644 --- a/src/server/routes/html/documentation/mod.rs +++ b/crates/html-router/src/routes/documentation/mod.rs @@ -3,12 +3,11 @@ use axum_session_auth::AuthSession; use axum_session_surreal::SessionSurrealPool; use surrealdb::{engine::any::Any, Surreal}; -use crate::{ - error::HtmlError, - page_data, - server::{routes::html::render_template, AppState}, - storage::types::user::User, -}; +use common::{error::HtmlError, storage::types::user::User}; + +use crate::{html_state::HtmlState, page_data}; + +use super::render_template; page_data!(DocumentationData, "do_not_use_this", { user: Option, @@ -16,7 +15,7 @@ page_data!(DocumentationData, "do_not_use_this", { }); pub async fn show_privacy_policy( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { let output = render_template( @@ -32,7 +31,7 @@ pub async fn show_privacy_policy( } pub async fn show_get_started( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { let output = render_template( @@ -47,7 +46,7 @@ pub async fn show_get_started( Ok(output.into_response()) } pub async fn show_mobile_friendly( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { let output = render_template( @@ -63,7 +62,7 @@ pub async fn show_mobile_friendly( } pub async fn show_documentation_index( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { let output = render_template( diff --git a/src/server/routes/html/gdpr.rs b/crates/html-router/src/routes/gdpr.rs similarity index 94% rename from src/server/routes/html/gdpr.rs rename to crates/html-router/src/routes/gdpr.rs index 03a5a68..253b831 100644 --- a/src/server/routes/html/gdpr.rs +++ b/crates/html-router/src/routes/gdpr.rs @@ -3,7 +3,7 @@ use axum_session::Session; use axum_session_surreal::SessionSurrealPool; use surrealdb::engine::any::Any; -use crate::error::HtmlError; +use common::error::HtmlError; pub async fn accept_gdpr( session: Session>, diff --git a/src/server/routes/html/index.rs b/crates/html-router/src/routes/index.rs similarity index 96% rename from src/server/routes/html/index.rs rename to crates/html-router/src/routes/index.rs index 27d5e8f..eebc070 100644 --- a/src/server/routes/html/index.rs +++ b/crates/html-router/src/routes/index.rs @@ -9,13 +9,8 @@ use surrealdb::{engine::any::Any, Surreal}; use tokio::join; use tracing::info; -use crate::{ +use common::{ error::{AppError, HtmlError}, - page_data, - server::{ - routes::html::{render_block, render_template}, - AppState, - }, storage::{ db::{delete_item, get_item}, types::{ @@ -26,6 +21,10 @@ use crate::{ }, }; +use crate::{html_state::HtmlState, page_data, routes::render_template}; + +use super::render_block; + page_data!(IndexData, "index/index.html", { gdpr_accepted: bool, user: Option, @@ -34,7 +33,7 @@ page_data!(IndexData, "index/index.html", { }); pub async fn index_handler( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, session: Session>, ) -> Result { @@ -92,7 +91,7 @@ pub struct LatestTextContentData { } pub async fn delete_text_content( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { @@ -153,7 +152,7 @@ pub async fn delete_text_content( // Helper function to get and validate text content async fn get_and_validate_text_content( - state: &AppState, + state: &HtmlState, id: &str, user: &User, ) -> Result { @@ -184,7 +183,7 @@ pub struct ActiveJobsData { } pub async fn delete_job( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { @@ -219,7 +218,7 @@ pub async fn delete_job( } pub async fn show_active_jobs( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { let user = match auth.current_user { diff --git a/src/server/routes/html/ingress_form.rs b/crates/html-router/src/routes/ingress_form.rs similarity index 94% rename from src/server/routes/html/ingress_form.rs rename to crates/html-router/src/routes/ingress_form.rs index b9fbf77..1bdd60f 100644 --- a/src/server/routes/html/ingress_form.rs +++ b/crates/html-router/src/routes/ingress_form.rs @@ -10,17 +10,18 @@ use surrealdb::{engine::any::Any, Surreal}; use tempfile::NamedTempFile; use tracing::info; -use crate::{ +use common::{ error::{AppError, HtmlError, IntoHtmlError}, - ingress::types::ingress_input::{create_ingress_objects, IngressInput}, - page_data, - server::{ - routes::html::{index::ActiveJobsData, render_block}, - AppState, - }, + ingress::ingress_input::{create_ingress_objects, IngressInput}, storage::types::{file_info::FileInfo, user::User}, }; +use crate::{ + html_state::HtmlState, + page_data, + routes::{index::ActiveJobsData, render_block}, +}; + use super::render_template; #[derive(Serialize)] @@ -29,7 +30,7 @@ pub struct ShowIngressFormData { } pub async fn show_ingress_form( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { if !auth.is_authenticated() { @@ -80,7 +81,7 @@ page_data!(IngressFormData, "ingress_form.html", { }); pub async fn process_ingress_form( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, TypedMultipart(input): TypedMultipart, ) -> Result { diff --git a/src/server/routes/html/knowledge/mod.rs b/crates/html-router/src/routes/knowledge/mod.rs similarity index 97% rename from src/server/routes/html/knowledge/mod.rs rename to crates/html-router/src/routes/knowledge/mod.rs index 10ac6a9..b82b872 100644 --- a/src/server/routes/html/knowledge/mod.rs +++ b/crates/html-router/src/routes/knowledge/mod.rs @@ -13,10 +13,8 @@ use plotly::{ use surrealdb::{engine::any::Any, Surreal}; use tracing::info; -use crate::{ +use common::{ error::{AppError, HtmlError}, - page_data, - server::{routes::html::render_template, AppState}, storage::{ db::delete_item, types::{ @@ -27,6 +25,8 @@ use crate::{ }, }; +use crate::{html_state::HtmlState, page_data, routes::render_template}; + page_data!(KnowledgeBaseData, "knowledge/base.html", { entities: Vec, relationships: Vec, @@ -35,7 +35,7 @@ page_data!(KnowledgeBaseData, "knowledge/base.html", { }); pub async fn show_knowledge_page( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, ) -> Result { // Early return if the user is not authenticated @@ -152,7 +152,7 @@ pub struct EntityData { } pub async fn show_edit_knowledge_entity_form( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { @@ -201,7 +201,7 @@ pub struct PatchKnowledgeEntityParams { } pub async fn patch_knowledge_entity( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { @@ -246,7 +246,7 @@ pub async fn patch_knowledge_entity( } pub async fn delete_knowledge_entity( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { @@ -288,7 +288,7 @@ pub struct RelationshipTableData { } pub async fn delete_knowledge_relationship( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Path(id): Path, ) -> Result { @@ -333,7 +333,7 @@ pub struct SaveKnowledgeRelationshipInput { } pub async fn save_knowledge_relationship( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { diff --git a/src/server/routes/html/mod.rs b/crates/html-router/src/routes/mod.rs similarity index 95% rename from src/server/routes/html/mod.rs rename to crates/html-router/src/routes/mod.rs index beedc9f..8a27a41 100644 --- a/src/server/routes/html/mod.rs +++ b/crates/html-router/src/routes/mod.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use axum::response::Html; use minijinja_autoreload::AutoReloader; -use crate::error::{HtmlError, IntoHtmlError}; +use common::error::{HtmlError, IntoHtmlError}; pub mod account; pub mod admin_panel; @@ -75,7 +75,7 @@ where macro_rules! page_data { ($name:ident, $template_name:expr, {$($(#[$attr:meta])* $field:ident: $ty:ty),*$(,)?}) => { use serde::{Serialize, Deserialize}; - use $crate::server::routes::html::PageData; + use $crate::routes::PageData; #[derive(Debug, Deserialize, Serialize)] pub struct $name { diff --git a/src/server/routes/html/search_result.rs b/crates/html-router/src/routes/search_result.rs similarity index 91% rename from src/server/routes/html/search_result.rs rename to crates/html-router/src/routes/search_result.rs index 2bf54d9..8625715 100644 --- a/src/server/routes/html/search_result.rs +++ b/crates/html-router/src/routes/search_result.rs @@ -8,12 +8,9 @@ use serde::{Deserialize, Serialize}; use surrealdb::{engine::any::Any, Surreal}; use tracing::info; -use crate::{ - error::HtmlError, - retrieval::query_helper::get_answer_with_references, - server::{routes::html::render_template, AppState}, - storage::types::user::User, -}; +use common::{error::HtmlError, storage::types::user::User}; + +use crate::{html_state::HtmlState, routes::render_template}; #[derive(Deserialize)] pub struct SearchParams { query: String, @@ -27,7 +24,7 @@ pub struct AnswerData { } pub async fn search_result_handler( - State(state): State, + State(state): State, Query(query): Query, auth: AuthSession, Surreal>, ) -> Result { diff --git a/src/server/routes/html/signin.rs b/crates/html-router/src/routes/signin.rs similarity index 91% rename from src/server/routes/html/signin.rs rename to crates/html-router/src/routes/signin.rs index 9cb501c..ca6b3ed 100644 --- a/src/server/routes/html/signin.rs +++ b/crates/html-router/src/routes/signin.rs @@ -9,7 +9,9 @@ use axum_session_auth::AuthSession; use axum_session_surreal::SessionSurrealPool; use surrealdb::{engine::any::Any, Surreal}; -use crate::{error::HtmlError, page_data, server::AppState, storage::types::user::User}; +use common::{error::HtmlError, storage::types::user::User}; + +use crate::{html_state::HtmlState, page_data}; use super::{render_block, render_template}; @@ -23,7 +25,7 @@ pub struct SignupParams { page_data!(ShowSignInForm, "auth/signin_form.html", {}); pub async fn show_signin_form( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, HxBoosted(boosted): HxBoosted, ) -> Result { @@ -48,7 +50,7 @@ pub async fn show_signin_form( } pub async fn authenticate_user( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { diff --git a/src/server/routes/html/signout.rs b/crates/html-router/src/routes/signout.rs similarity index 89% rename from src/server/routes/html/signout.rs rename to crates/html-router/src/routes/signout.rs index 4bbc35b..147bd94 100644 --- a/src/server/routes/html/signout.rs +++ b/crates/html-router/src/routes/signout.rs @@ -3,7 +3,7 @@ use axum_session_auth::AuthSession; use axum_session_surreal::SessionSurrealPool; use surrealdb::{engine::any::Any, Surreal}; -use crate::{error::ApiError, storage::types::user::User}; +use common::{error::ApiError, storage::types::user::User}; pub async fn sign_out_user( auth: AuthSession, Surreal>, diff --git a/src/server/routes/html/signup.rs b/crates/html-router/src/routes/signup.rs similarity index 74% rename from src/server/routes/html/signup.rs rename to crates/html-router/src/routes/signup.rs index f5abfa4..cf851c1 100644 --- a/src/server/routes/html/signup.rs +++ b/crates/html-router/src/routes/signup.rs @@ -10,7 +10,9 @@ use axum_session_surreal::SessionSurrealPool; use serde::{Deserialize, Serialize}; use surrealdb::{engine::any::Any, Surreal}; -use crate::{error::HtmlError, server::AppState, storage::types::user::User}; +use common::{error::HtmlError, storage::types::user::User}; + +use crate::html_state::HtmlState; use super::{render_block, render_template}; @@ -21,13 +23,8 @@ pub struct SignupParams { pub timezone: String, } -#[derive(Serialize)] -struct PageData { - // name: String, -} - pub async fn show_signup_form( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, HxBoosted(boosted): HxBoosted, ) -> Result { @@ -35,24 +32,15 @@ pub async fn show_signup_form( return Ok(Redirect::to("/").into_response()); } let output = match boosted { - true => render_block( - "auth/signup_form.html", - "body", - PageData {}, - state.templates.clone(), - )?, - false => render_template( - "auth/signup_form.html", - PageData {}, - state.templates.clone(), - )?, + true => render_block("auth/signup_form.html", "body", {}, state.templates.clone())?, + false => render_template("auth/signup_form.html", {}, state.templates.clone())?, }; Ok(output.into_response()) } pub async fn process_signup_and_show_verification( - State(state): State, + State(state): State, auth: AuthSession, Surreal>, Form(form): Form, ) -> Result { diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml new file mode 100644 index 0000000..c683be2 --- /dev/null +++ b/crates/main/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "main" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +anyhow = { workspace = true } +tracing = { workspace = true } +axum = { workspace = true } + +async-openai = "0.24.1" +async-stream = "0.3.6" +axum-htmx = "0.6.0" +axum_session = "0.14.4" +axum_session_auth = "0.14.1" +axum_session_surreal = "0.2.1" +axum_typed_multipart = "0.12.1" +chrono = { version = "0.4.39", features = ["serde"] } +chrono-tz = "0.10.1" +config = "0.15.4" +futures = "0.3.31" +json-stream-parser = "0.1.4" +lettre = { version = "0.11.11", features = ["rustls-tls"] } +mime = "0.3.17" +mime_guess = "2.0.5" +minijinja = { version = "2.5.0", features = ["loader", "multi_template"] } +minijinja-autoreload = "2.5.0" +minijinja-contrib = { version = "2.6.0", features = ["datetime", "timezone"] } +mockall = "0.13.0" +plotly = "0.12.1" +reqwest = {version = "0.12.12", features = ["charset", "json"]} +scraper = "0.22.0" +sha2 = "0.10.8" +surrealdb = "2.0.4" +tempfile = "3.12.0" +text-splitter = "0.18.1" +tiktoken-rs = "0.6.0" +tower-http = { version = "0.6.2", features = ["fs"] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +url = { version = "2.5.2", features = ["serde"] } +uuid = { version = "1.10.0", features = ["v4", "serde"] } + +# Reference to api-router +api-router = { path = "../api-router" } +html-router = { path = "../html-router" } +common = { path = "../common" } + +[[bin]] +name = "server" +path = "src/server.rs" + +[[bin]] +name = "worker" +path = "src/worker.rs" diff --git a/crates/main/src/server.rs b/crates/main/src/server.rs new file mode 100644 index 0000000..7baa6db --- /dev/null +++ b/crates/main/src/server.rs @@ -0,0 +1,47 @@ +use api_router::{api_routes_v1, api_state::ApiState}; +use axum::{extract::FromRef, Router}; +use common::utils::config::get_config; +use html_router::{html_routes, html_state::HtmlState}; +use tracing::info; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +#[tokio::main(flavor = "multi_thread", worker_threads = 2)] +async fn main() -> Result<(), Box> { + // Set up tracing + tracing_subscriber::registry() + .with(fmt::layer()) + .with(EnvFilter::from_default_env()) + .try_init() + .ok(); + + // Get config + let config = get_config()?; + + // Set up router states + let html_state = HtmlState::new(&config).await?; + let api_state = ApiState { + surreal_db_client: html_state.surreal_db_client.clone(), + job_queue: html_state.job_queue.clone(), + }; + + // Create Axum router + let app = Router::new() + .nest("/api/v1", api_routes_v1(&api_state)) + .nest("/", html_routes(&html_state)) + .with_state(AppState { + api_state, + html_state, + }); + + info!("Listening on 0.0.0.0:3000"); + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + axum::serve(listener, app).await?; + + Ok(()) +} + +#[derive(Clone, FromRef)] +struct AppState { + api_state: ApiState, + html_state: HtmlState, +} diff --git a/src/bin/worker.rs b/crates/main/src/worker.rs similarity index 83% rename from src/bin/worker.rs rename to crates/main/src/worker.rs index ab214bd..88225a3 100644 --- a/src/bin/worker.rs +++ b/crates/main/src/worker.rs @@ -1,10 +1,6 @@ use std::sync::Arc; -use futures::StreamExt; -use surrealdb::Action; -use tracing::{error, info}; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use zettle_db::{ +use common::{ ingress::{ content_processor::ContentProcessor, jobqueue::{JobQueue, MAX_ATTEMPTS}, @@ -15,6 +11,10 @@ use zettle_db::{ }, utils::config::get_config, }; +use futures::StreamExt; +use surrealdb::Action; +use tracing::{error, info}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -40,9 +40,9 @@ async fn main() -> Result<(), Box> { let openai_client = Arc::new(async_openai::Client::new()); - let job_queue = JobQueue::new(surreal_db_client.clone(), openai_client.clone()); + let job_queue = JobQueue::new(surreal_db_client.clone()); - let content_processor = ContentProcessor::new(surreal_db_client, openai_client).await?; + let content_processor = ContentProcessor::new(surreal_db_client, openai_client.clone()).await?; loop { // First, check for any unfinished jobs @@ -52,7 +52,9 @@ async fn main() -> Result<(), Box> { info!("Found {} unfinished jobs", unfinished_jobs.len()); for job in unfinished_jobs { - job_queue.process_job(job, &content_processor).await?; + job_queue + .process_job(job, &content_processor, openai_client.clone()) + .await?; } } @@ -68,7 +70,11 @@ async fn main() -> Result<(), Box> { match notification.action { Action::Create => { if let Err(e) = job_queue - .process_job(notification.data, &content_processor) + .process_job( + notification.data, + &content_processor, + openai_client.clone(), + ) .await { error!("Error processing job: {}", e); @@ -95,7 +101,11 @@ async fn main() -> Result<(), Box> { JobStatus::Error(_) if attempts < MAX_ATTEMPTS => { // This is a retry after an error if let Err(e) = job_queue - .process_job(current_job, &content_processor) + .process_job( + current_job, + &content_processor, + openai_client.clone(), + ) .await { error!("Error processing job retry: {}", e); @@ -114,7 +124,11 @@ async fn main() -> Result<(), Box> { JobStatus::Created => { // Shouldn't happen with Update action, but process if it does if let Err(e) = job_queue - .process_job(notification.data, &content_processor) + .process_job( + notification.data, + &content_processor, + openai_client.clone(), + ) .await { error!("Error processing job: {}", e); diff --git a/data/182cb7b4-2286-4f40-9237-30b812d67696/flake.nix b/data/182cb7b4-2286-4f40-9237-30b812d67696/flake.nix deleted file mode 100644 index 9e220a0..0000000 --- a/data/182cb7b4-2286-4f40-9237-30b812d67696/flake.nix +++ /dev/null @@ -1,57 +0,0 @@ -{ - inputs = { - nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling"; - systems.url = "github:nix-systems/default"; - devenv.url = "github:cachix/devenv"; - devenv.inputs.nixpkgs.follows = "nixpkgs"; - }; - - nixConfig = { - extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; - extra-substituters = "https://devenv.cachix.org"; - }; - - outputs = { - self, - nixpkgs, - devenv, - systems, - ... - } @ inputs: let - forEachSystem = nixpkgs.lib.genAttrs (import systems); - in { - packages = forEachSystem (system: { - devenv-up = self.devShells.${system}.default.config.procfileScript; - }); - - devShells = - forEachSystem - (system: let - pkgs = nixpkgs.legacyPackages.${system}; - in { - default = devenv.lib.mkShell { - inherit inputs pkgs; - modules = [ - { - # https://devenv.sh/reference/options/ - enterShell = '' - echo "run devenv up -d to start and monitor services" - ''; - - languages.rust.enable = true; - - services = { - redis = { - enable = true; - }; - rabbitmq = { - enable = true; - plugins = ["tracing"]; - }; - }; - } - ]; - }; - }); - }; -} diff --git a/data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md b/data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md deleted file mode 100644 index 5a76017..0000000 --- a/data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md +++ /dev/null @@ -1,33 +0,0 @@ -Your proposed structure for the API sounds solid and modular, making it easier to manage files and their relationships with other data. Here’s a breakdown of how this can work and how it could be used with an iOS shortcut or a custom application: - -API Structure -File Management Endpoints: - -POST /file: Accepts a file upload and returns a unique identifier (ID) for that file. -PUT /file/{id}: Updates the metadata of the file identified by {id}. -DELETE /file/{id}: Deletes the file and its associated metadata from the database. -Data Ingress Endpoint: - -POST /ingress: Accepts a JSON body containing references (IDs) to files and other necessary data, linking them in the database as needed. -Using with iOS Shortcuts -You can create shortcuts to interact with your API endpoints without the need for a full-fledged application. Here's how: - -File Upload Shortcut: - -Use the "Get File" action to select a file from the device. -Use the "Post" action to send a multipart/form-data request to the /file endpoint. -Parse the response to get the returned file ID. -Data Ingress Shortcut: - -Use the "Ask for Input" action to gather the necessary fields for the ingress (like instructions, category, etc.) and the file ID(s). -Use another "Post" action to send this data to the /ingress endpoint as JSON. -Developing a CLI Tool -A CLI tool could also be developed for easier interaction with your API. This tool could: - -Upload Files: Handle file uploads and return file IDs. -Link Data: Accept user input for instructions, category, and linked file IDs, then submit this data to the /ingress endpoint. -Additional Considerations -Error Handling: Ensure that both the upload and ingress endpoints handle errors gracefully and provide meaningful messages. -Documentation: Create clear API documentation to guide users in constructing requests correctly, whether they are using a shortcut, CLI, or custom application. -Authentication: Consider adding authentication to your API endpoints for security, especially if sensitive data is being handled. -This approach gives you the flexibility to support various clients, streamline interactions, and keep the server-side implementation clean and manageable. Would you like more specifics on any part of this setup? diff --git a/page_base.html b/page_base.html deleted file mode 100644 index 44254c0..0000000 --- a/page_base.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends 'body_base.html' %} -{% block main %} -
-
-{% endblock %} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 44e09ba..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - plugins: [ - require('tailwindcss'), - require('autoprefixer'), - require('cssnano')({ - preset: 'default', // Minify CSS - }), - ], -}; diff --git a/src/bin/server.rs b/src/bin/server.rs deleted file mode 100644 index 49ad488..0000000 --- a/src/bin/server.rs +++ /dev/null @@ -1,36 +0,0 @@ -use axum::Router; -use tracing::info; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use zettle_db::{ - server::{ - routes::{api_routes_v1, html_routes}, - AppState, - }, - utils::config::get_config, -}; - -#[tokio::main(flavor = "multi_thread", worker_threads = 2)] -async fn main() -> Result<(), Box> { - // Set up tracing - tracing_subscriber::registry() - .with(fmt::layer()) - .with(EnvFilter::from_default_env()) - .try_init() - .ok(); - - let config = get_config()?; - - let app_state = AppState::new(&config).await?; - - // Create Axum router - let app = Router::new() - .nest("/api/v1", api_routes_v1(&app_state)) - .nest("/", html_routes(&app_state)) - .with_state(app_state); - - info!("Listening on 0.0.0.0:3000"); - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; - axum::serve(listener, app).await?; - - Ok(()) -} diff --git a/src/ingress/mod.rs b/src/ingress/mod.rs deleted file mode 100644 index 988d96a..0000000 --- a/src/ingress/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod analysis; -pub mod content_processor; -pub mod jobqueue; -pub mod types; diff --git a/src/server/routes/api/ingress_task.rs b/src/server/routes/api/ingress_task.rs deleted file mode 100644 index 136ecba..0000000 --- a/src/server/routes/api/ingress_task.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{error::ApiError, server::AppState, storage::types::user::User}; -use axum::{ - extract::{Path, State}, - http::StatusCode, - response::IntoResponse, - Extension, Json, -}; - -pub async fn get_queue_tasks( - State(state): State, - Extension(user): Extension, -) -> Result { - let user_tasks = state - .job_queue - .get_user_jobs(&user.id) - .await - .map_err(ApiError::from)?; - - Ok(Json(user_tasks)) -} - -pub async fn delete_queue_task( - State(state): State, - Extension(user): Extension, - Path(id): Path, -) -> Result { - state - .job_queue - .delete_job(&id, &user.id) - .await - .map_err(ApiError::from)?; - - Ok(StatusCode::OK) -} diff --git a/src/server/routes/api/mod.rs b/src/server/routes/api/mod.rs deleted file mode 100644 index c7e4e96..0000000 --- a/src/server/routes/api/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod ingress; -pub mod ingress_task; -pub mod query; -pub mod queue_length; diff --git a/src/server/routes/api/query.rs b/src/server/routes/api/query.rs deleted file mode 100644 index 5adaf0f..0000000 --- a/src/server/routes/api/query.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{ - error::ApiError, retrieval::query_helper::get_answer_with_references, server::AppState, - storage::types::user::User, -}; -use axum::{extract::State, response::IntoResponse, Extension, Json}; -use serde::Deserialize; -use tracing::info; - -#[derive(Debug, Deserialize)] -pub struct QueryInput { - query: String, -} - -pub async fn query_handler( - State(state): State, - Extension(user): Extension, - Json(query): Json, -) -> Result { - info!("Received input: {:?}", query); - info!("{:?}", user); - - let answer = get_answer_with_references( - &state.surreal_db_client, - &state.openai_client, - &query.query, - &user.id, - ) - .await?; - - Ok( - Json(serde_json::json!({"answer": answer.content, "references": answer.references})) - .into_response(), - ) -} diff --git a/src/server/routes/api/queue_length.rs b/src/server/routes/api/queue_length.rs deleted file mode 100644 index eb1e897..0000000 --- a/src/server/routes/api/queue_length.rs +++ /dev/null @@ -1,29 +0,0 @@ -use axum::{extract::State, http::StatusCode, response::IntoResponse}; -use tracing::info; - -use crate::{ - error::{ApiError, AppError}, - server::AppState, - storage::{db::get_all_stored_items, types::job::Job}, -}; - -pub async fn queue_length_handler( - State(state): State, -) -> Result { - info!("Getting queue length"); - - let queue_length = get_all_stored_items::(&state.surreal_db_client) - .await - .map_err(AppError::from)? - .len(); - - info!("Queue length: {}", queue_length); - - state - .mailer - .send_email_verification("per@starks.cloud", "1001010", &state.templates) - .map_err(AppError::from)?; - - // Return the queue length with a 200 OK status - Ok((StatusCode::OK, queue_length.to_string())) -}