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 {
let owner_type_str: String = row.get("owner_type");
- let owner_type: PromptOwnerType = owner_type_str
+ let owner_type: TemplateOwnerType = owner_type_str
.parse()
.map_err(|e: String| DbError::Internal(e))?;
@@ -42,7 +42,7 @@ impl PostgresPromptRepo {
.transpose()
.map_err(|e| DbError::Internal(format!("Failed to parse metadata: {}", e)))?;
- Ok(Prompt {
+ Ok(Template {
id: row.get("id"),
owner_type,
owner_id: row.get("owner_id"),
@@ -58,13 +58,13 @@ impl PostgresPromptRepo {
/// Helper method for cursor-based pagination.
async fn list_with_cursor(
&self,
- owner_type: PromptOwnerType,
+ owner_type: TemplateOwnerType,
owner_id: Uuid,
params: &ListParams,
cursor: &Cursor,
fetch_limit: i64,
limit: i64,
- ) -> DbResult> {
+ ) -> DbResult> {
let (comparison, order, should_reverse) =
params.sort_order.cursor_query_params(params.direction);
@@ -77,7 +77,7 @@ impl PostgresPromptRepo {
let query = format!(
r#"
SELECT id, owner_type::TEXT, owner_id, name, description, content, metadata, created_at, updated_at
- FROM prompts
+ FROM templates
WHERE owner_type = $1 AND owner_id = $2 AND ROW(created_at, id) {} ROW($3, $4)
{}
ORDER BY created_at {}, id {}
@@ -96,10 +96,10 @@ impl PostgresPromptRepo {
.await?;
let has_more = rows.len() as i64 > limit;
- let mut items: Vec = rows
+ let mut items: Vec = rows
.iter()
.take(limit as usize)
- .map(Self::parse_prompt)
+ .map(Self::parse_template)
.collect::>>()?;
if should_reverse {
@@ -117,8 +117,8 @@ impl PostgresPromptRepo {
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-impl PromptRepo for PostgresPromptRepo {
- async fn create(&self, input: CreatePrompt) -> DbResult {
+impl TemplateRepo for PostgresTemplateRepo {
+ async fn create(&self, input: CreateTemplate) -> DbResult {
let id = Uuid::new_v4();
let owner_type = input.owner.owner_type();
let owner_id = input.owner.owner_id();
@@ -132,7 +132,7 @@ impl PromptRepo for PostgresPromptRepo {
let row = sqlx::query(
r#"
- INSERT INTO prompts (id, owner_type, owner_id, name, description, content, metadata)
+ INSERT INTO templates (id, owner_type, owner_id, name, description, content, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, owner_type::TEXT, owner_id, name, description, content, metadata, created_at, updated_at
"#,
@@ -149,21 +149,21 @@ impl PromptRepo for PostgresPromptRepo {
.map_err(|e| match e {
sqlx::Error::Database(db_err) if db_err.is_unique_violation() => {
DbError::Conflict(format!(
- "Prompt with name '{}' already exists for this owner",
+ "Template with name '{}' already exists for this owner",
input.name
))
}
_ => DbError::from(e),
})?;
- Self::parse_prompt(&row)
+ Self::parse_template(&row)
}
- async fn get_by_id(&self, id: Uuid) -> DbResult
-
- {/* Template Selector */}
-
-
-
-
-
- {prompts.length === 0 ? (
-
- No templates available
-
- ) : (
- prompts.map((prompt) => (
- handleApplyPrompt(prompt)}
- className="flex flex-col items-start gap-0.5"
- >
- {prompt.name}
- {prompt.description && (
-
- {prompt.description}
-
- )}
-
- ))
- )}
-
-
-
- {/* Save as Template Button */}
-
-
+