From 21a6d0d5e7fa6fd0f930032edf9e09f1fca28598 Mon Sep 17 00:00:00 2001 From: ScriptSmith Date: Tue, 17 Mar 2026 20:45:55 +1000 Subject: [PATCH 1/4] Show icon when anonymous user --- ui/src/components/UserMenu/UserMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/components/UserMenu/UserMenu.tsx b/ui/src/components/UserMenu/UserMenu.tsx index 3c2a3dc..69e9784 100644 --- a/ui/src/components/UserMenu/UserMenu.tsx +++ b/ui/src/components/UserMenu/UserMenu.tsx @@ -20,7 +20,7 @@ interface UserMenuProps { } export function UserMenu({ className }: UserMenuProps) { - const { user, logout, isAuthenticated } = useAuth(); + const { user, logout, isAuthenticated, method } = useAuth(); const { config } = useConfig(); const navigate = useNavigate(); const visibleNavItems = navItems.filter((item) => { @@ -29,6 +29,7 @@ export function UserMenu({ className }: UserMenuProps) { }); const showAdmin = config?.admin.enabled && hasAdminAccess(user); const allNavItems = showAdmin ? [...visibleNavItems, adminNavItem] : visibleNavItems; + const isAnonymous = method === "none"; if (!isAuthenticated) { return null; @@ -53,7 +54,7 @@ export function UserMenu({ className }: UserMenuProps) { )} aria-label="User menu" > - {initials || } + {isAnonymous ? : initials || } From 5fb268ef48c15784286b96d4e073d5b6f0b30e76 Mon Sep 17 00:00:00 2001 From: ScriptSmith Date: Wed, 18 Mar 2026 19:45:22 +1000 Subject: [PATCH 2/4] Improved prompt templates --- .../postgres/20250101000000_initial.sql | 20 +- .../sqlite/20250101000000_initial.sql | 14 +- src/config/limits.rs | 10 +- src/db/mod.rs | 18 +- src/db/postgres/mod.rs | 4 +- src/db/postgres/{prompts.rs => templates.rs} | 180 +- src/db/repos/mod.rs | 4 +- src/db/repos/prompts.rs | 49 - src/db/repos/templates.rs | 59 + src/db/sqlite/mod.rs | 4 +- src/db/sqlite/{prompts.rs => templates.rs} | 443 +++-- src/models/mod.rs | 4 +- src/models/{prompt.rs => template.rs} | 78 +- src/openapi.rs | 34 +- src/routes/admin/mod.rs | 269 +-- src/routes/admin/{prompts.rs => templates.rs} | 279 ++- src/services/mod.rs | 10 +- src/services/prompts.rs | 73 - src/services/templates.rs | 82 + ui/src/api/openapi.json | 1501 +++++++++-------- .../Admin/PromptFormModal/PromptFormModal.tsx | 232 +++ .../components/Admin/PromptFormModal/index.ts | 1 + ui/src/components/Admin/index.ts | 1 + ui/src/components/ChatInput/ChatInput.tsx | 10 +- ui/src/components/ChatView/ChatView.tsx | 3 +- .../ConversationSettingsModal.stories.tsx | 10 +- .../ConversationSettingsModal.tsx | 89 +- .../PromptFormModal.stories.tsx | 38 +- .../PromptFormModal/PromptFormModal.tsx | 50 +- .../PromptsButton/PromptsButton.stories.tsx | 29 +- .../PromptsButton/PromptsButton.tsx | 204 ++- ui/src/components/PromptsButton/index.ts | 2 +- .../TemplateVariableEditor.tsx | 158 ++ .../TemplateVariableForm.tsx | 66 + ui/src/hooks/useUserPrompts.ts | 120 +- ui/src/lib/templateVariables.ts | 52 + ui/src/pages/admin/OrganizationDetailPage.tsx | 88 + ui/src/pages/admin/ProjectDetailPage.tsx | 91 +- ui/src/pages/admin/TeamDetailPage.tsx | 100 +- ui/src/pages/admin/promptColumns.tsx | 69 + ui/src/pages/project/ProjectDetailPage.tsx | 18 +- ui/src/pages/project/TemplatesTab.tsx | 114 ++ 42 files changed, 3012 insertions(+), 1668 deletions(-) rename src/db/postgres/{prompts.rs => templates.rs} (66%) delete mode 100644 src/db/repos/prompts.rs create mode 100644 src/db/repos/templates.rs rename src/db/sqlite/{prompts.rs => templates.rs} (59%) rename src/models/{prompt.rs => template.rs} (56%) rename src/routes/admin/{prompts.rs => templates.rs} (59%) delete mode 100644 src/services/prompts.rs create mode 100644 src/services/templates.rs create mode 100644 ui/src/components/Admin/PromptFormModal/PromptFormModal.tsx create mode 100644 ui/src/components/Admin/PromptFormModal/index.ts create mode 100644 ui/src/components/TemplateVariableEditor/TemplateVariableEditor.tsx create mode 100644 ui/src/components/TemplateVariableForm/TemplateVariableForm.tsx create mode 100644 ui/src/lib/templateVariables.ts create mode 100644 ui/src/pages/admin/promptColumns.tsx create mode 100644 ui/src/pages/project/TemplatesTab.tsx diff --git a/migrations_sqlx/postgres/20250101000000_initial.sql b/migrations_sqlx/postgres/20250101000000_initial.sql index 1ecf6bb..09ed39f 100644 --- a/migrations_sqlx/postgres/20250101000000_initial.sql +++ b/migrations_sqlx/postgres/20250101000000_initial.sql @@ -1123,21 +1123,21 @@ END $$; -- without cross-database joins. See VectorStore trait for chunk operations. -- ====================================================================== --- Prompts +-- Templates -- ====================================================================== -- Reusable system prompt templates. DO $$ BEGIN - CREATE TYPE prompt_owner_type AS ENUM ('organization', 'team', 'project', 'user'); + CREATE TYPE template_owner_type AS ENUM ('organization', 'team', 'project', 'user'); EXCEPTION WHEN duplicate_object THEN null; END $$; -CREATE TABLE IF NOT EXISTS prompts ( +CREATE TABLE IF NOT EXISTS templates ( id UUID PRIMARY KEY NOT NULL, - -- Ownership (who can access this prompt) - owner_type prompt_owner_type NOT NULL, + -- Ownership (who can access this template) + owner_type template_owner_type NOT NULL, owner_id UUID NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, @@ -1152,13 +1152,13 @@ CREATE TABLE IF NOT EXISTS prompts ( UNIQUE(owner_type, owner_id, name) ); -CREATE INDEX IF NOT EXISTS idx_prompts_owner ON prompts(owner_type, owner_id); --- Partial index for non-deleted prompts (most queries filter by deleted_at IS NULL) -CREATE INDEX IF NOT EXISTS idx_prompts_owner_active ON prompts(owner_type, owner_id) WHERE deleted_at IS NULL; -CREATE INDEX IF NOT EXISTS idx_prompts_name ON prompts(name); +CREATE INDEX IF NOT EXISTS idx_templates_owner ON templates(owner_type, owner_id); +-- Partial index for non-deleted templates (most queries filter by deleted_at IS NULL) +CREATE INDEX IF NOT EXISTS idx_templates_owner_active ON templates(owner_type, owner_id) WHERE deleted_at IS NULL; +CREATE INDEX IF NOT EXISTS idx_templates_name ON templates(name); DO $$ BEGIN - CREATE TRIGGER update_prompts_updated_at BEFORE UPDATE ON prompts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + CREATE TRIGGER update_templates_updated_at BEFORE UPDATE ON templates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); EXCEPTION WHEN duplicate_object THEN null; END $$; diff --git a/migrations_sqlx/sqlite/20250101000000_initial.sql b/migrations_sqlx/sqlite/20250101000000_initial.sql index 0a602c2..c31b1de 100644 --- a/migrations_sqlx/sqlite/20250101000000_initial.sql +++ b/migrations_sqlx/sqlite/20250101000000_initial.sql @@ -916,14 +916,14 @@ CREATE INDEX IF NOT EXISTS idx_vector_store_files_deleted_at ON vector_store_fil -- without cross-database joins. See VectorStore trait for chunk operations. -- ====================================================================== --- Prompts +-- Templates -- ====================================================================== -- Reusable system prompt templates. -- owner_type: 'organization', 'team', 'project', 'user' -CREATE TABLE IF NOT EXISTS prompts ( +CREATE TABLE IF NOT EXISTS templates ( id TEXT PRIMARY KEY NOT NULL, - -- Ownership (who can access this prompt) + -- Ownership (who can access this template) owner_type TEXT NOT NULL CHECK (owner_type IN ('organization', 'team', 'project', 'user')), owner_id TEXT NOT NULL, name TEXT NOT NULL, @@ -939,10 +939,10 @@ CREATE TABLE IF NOT EXISTS prompts ( UNIQUE(owner_type, owner_id, name) ); -CREATE INDEX IF NOT EXISTS idx_prompts_owner ON prompts(owner_type, owner_id); --- Partial index for non-deleted prompts (most queries filter by deleted_at IS NULL) -CREATE INDEX IF NOT EXISTS idx_prompts_owner_active ON prompts(owner_type, owner_id) WHERE deleted_at IS NULL; -CREATE INDEX IF NOT EXISTS idx_prompts_name ON prompts(name); +CREATE INDEX IF NOT EXISTS idx_templates_owner ON templates(owner_type, owner_id); +-- Partial index for non-deleted templates (most queries filter by deleted_at IS NULL) +CREATE INDEX IF NOT EXISTS idx_templates_owner_active ON templates(owner_type, owner_id) WHERE deleted_at IS NULL; +CREATE INDEX IF NOT EXISTS idx_templates_name ON templates(name); -- ====================================================================== -- Service Accounts diff --git a/src/config/limits.rs b/src/config/limits.rs index 5868a0b..ba2cfe0 100644 --- a/src/config/limits.rs +++ b/src/config/limits.rs @@ -95,9 +95,9 @@ pub struct ResourceLimits { #[serde(default = "default_max_conversations_per_owner")] pub max_conversations_per_owner: u32, - /// Maximum prompts per owner (org/team/project/user). Default: 5,000. - #[serde(default = "default_max_prompts_per_owner")] - pub max_prompts_per_owner: u32, + /// Maximum templates per owner (org/team/project/user). Default: 5,000. + #[serde(default = "default_max_templates_per_owner")] + pub max_templates_per_owner: u32, /// Maximum domain verifications per SSO configuration. Default: 50. #[serde(default = "default_max_domains_per_sso_config")] @@ -146,7 +146,7 @@ impl Default for ResourceLimits { max_vector_stores_per_owner: default_max_vector_stores_per_owner(), max_files_per_vector_store: default_max_files_per_vector_store(), max_conversations_per_owner: default_max_conversations_per_owner(), - max_prompts_per_owner: default_max_prompts_per_owner(), + max_templates_per_owner: default_max_templates_per_owner(), max_domains_per_sso_config: default_max_domains_per_sso_config(), max_sso_group_mappings_per_org: default_max_sso_group_mappings_per_org(), max_members_per_org: default_max_members_per_org(), @@ -218,7 +218,7 @@ fn default_max_conversations_per_owner() -> u32 { 10_000 } -fn default_max_prompts_per_owner() -> u32 { +fn default_max_templates_per_owner() -> u32 { 5_000 } diff --git a/src/db/mod.rs b/src/db/mod.rs index 1e3c84a..4ad8c29 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -53,7 +53,7 @@ struct CachedRepos { vector_stores: Arc, files: Arc, teams: Arc, - prompts: Arc, + templates: Arc, #[cfg(feature = "sso")] sso_group_mappings: Arc, #[cfg(feature = "sso")] @@ -131,7 +131,7 @@ impl DbPool { vector_stores: Arc::new(sqlite::SqliteVectorStoresRepo::new(pool.clone())), files: Arc::new(sqlite::SqliteFilesRepo::new(pool.clone())), teams: Arc::new(sqlite::SqliteTeamRepo::new(pool.clone())), - prompts: Arc::new(sqlite::SqlitePromptRepo::new(pool.clone())), + templates: Arc::new(sqlite::SqliteTemplateRepo::new(pool.clone())), #[cfg(feature = "sso")] sso_group_mappings: Arc::new(sqlite::SqliteSsoGroupMappingRepo::new(pool.clone())), #[cfg(feature = "sso")] @@ -169,7 +169,7 @@ impl DbPool { vector_stores: Arc::new(sqlite::SqliteVectorStoresRepo::new(pool.clone())), files: Arc::new(sqlite::SqliteFilesRepo::new(pool.clone())), teams: Arc::new(sqlite::SqliteTeamRepo::new(pool.clone())), - prompts: Arc::new(sqlite::SqlitePromptRepo::new(pool.clone())), + templates: Arc::new(sqlite::SqliteTemplateRepo::new(pool.clone())), #[cfg(feature = "sso")] sso_group_mappings: unreachable!("SSO not supported in WASM builds"), #[cfg(feature = "sso")] @@ -244,7 +244,7 @@ impl DbPool { write_pool.clone(), read_pool.clone(), )), - prompts: Arc::new(postgres::PostgresPromptRepo::new( + templates: Arc::new(postgres::PostgresTemplateRepo::new( write_pool.clone(), read_pool.clone(), )), @@ -330,7 +330,7 @@ impl DbPool { vector_stores: Arc::new(sqlite::SqliteVectorStoresRepo::new(pool.clone())), files: Arc::new(sqlite::SqliteFilesRepo::new(pool.clone())), teams: Arc::new(sqlite::SqliteTeamRepo::new(pool.clone())), - prompts: Arc::new(sqlite::SqlitePromptRepo::new(pool.clone())), + templates: Arc::new(sqlite::SqliteTemplateRepo::new(pool.clone())), #[cfg(feature = "sso")] sso_group_mappings: Arc::new(sqlite::SqliteSsoGroupMappingRepo::new( pool.clone(), @@ -430,7 +430,7 @@ impl DbPool { write_pool.clone(), read_pool.clone(), )), - prompts: Arc::new(postgres::PostgresPromptRepo::new( + templates: Arc::new(postgres::PostgresTemplateRepo::new( write_pool.clone(), read_pool.clone(), )), @@ -581,9 +581,9 @@ impl DbPool { Arc::clone(&self.repos.teams) } - /// Get prompt repository - pub fn prompts(&self) -> Arc { - Arc::clone(&self.repos.prompts) + /// Get template repository + pub fn templates(&self) -> Arc { + Arc::clone(&self.repos.templates) } /// Get SSO group mapping repository diff --git a/src/db/postgres/mod.rs b/src/db/postgres/mod.rs index 11cfd33..4c6fc59 100644 --- a/src/db/postgres/mod.rs +++ b/src/db/postgres/mod.rs @@ -10,7 +10,6 @@ mod org_rbac_policies; mod org_sso_configs; mod organizations; mod projects; -mod prompts; mod providers; #[cfg(feature = "sso")] mod scim_configs; @@ -22,6 +21,7 @@ mod service_accounts; #[cfg(feature = "sso")] mod sso_group_mappings; mod teams; +mod templates; mod usage; mod users; mod vector_stores; @@ -38,7 +38,6 @@ pub use org_rbac_policies::PostgresOrgRbacPolicyRepo; pub use org_sso_configs::PostgresOrgSsoConfigRepo; pub use organizations::PostgresOrganizationRepo; pub use projects::PostgresProjectRepo; -pub use prompts::PostgresPromptRepo; pub use providers::PostgresDynamicProviderRepo; #[cfg(feature = "sso")] pub use scim_configs::PostgresOrgScimConfigRepo; @@ -50,6 +49,7 @@ pub use service_accounts::PostgresServiceAccountRepo; #[cfg(feature = "sso")] pub use sso_group_mappings::PostgresSsoGroupMappingRepo; pub use teams::PostgresTeamRepo; +pub use templates::PostgresTemplateRepo; pub use usage::PostgresUsageRepo; pub use users::PostgresUserRepo; pub use vector_stores::PostgresVectorStoresRepo; diff --git a/src/db/postgres/prompts.rs b/src/db/postgres/templates.rs similarity index 66% rename from src/db/postgres/prompts.rs rename to src/db/postgres/templates.rs index 4e407c4..81a2dc3 100644 --- a/src/db/postgres/prompts.rs +++ b/src/db/postgres/templates.rs @@ -8,19 +8,19 @@ use crate::{ db::{ error::{DbError, DbResult}, repos::{ - Cursor, CursorDirection, ListParams, ListResult, PageCursors, PromptRepo, + Cursor, CursorDirection, ListParams, ListResult, PageCursors, TemplateRepo, cursor_from_row, }, }, - models::{CreatePrompt, Prompt, PromptOwnerType, UpdatePrompt}, + models::{CreateTemplate, Template, TemplateOwnerType, UpdateTemplate}, }; -pub struct PostgresPromptRepo { +pub struct PostgresTemplateRepo { write_pool: PgPool, read_pool: PgPool, } -impl PostgresPromptRepo { +impl PostgresTemplateRepo { pub fn new(write_pool: PgPool, read_pool: Option) -> Self { let read_pool = read_pool.unwrap_or_else(|| write_pool.clone()); Self { @@ -29,10 +29,10 @@ impl PostgresPromptRepo { } } - /// Parse a Prompt from a database row. - fn parse_prompt(row: &sqlx::postgres::PgRow) -> DbResult { + /// Parse a Template from a database row. + fn parse_template(row: &sqlx::postgres::PgRow) -> DbResult