diff --git a/framework/configstore/migrations.go b/framework/configstore/migrations.go
index bced2ec3f5..42ca3f3c8d 100644
--- a/framework/configstore/migrations.go
+++ b/framework/configstore/migrations.go
@@ -359,6 +359,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error {
if err := migrationAddRoutingChainMaxDepthColumn(ctx, db); err != nil {
return err
}
+ if err := migrationAddPromptVariablesColumns(ctx, db); err != nil {
+ return err
+ }
if err := migrationAddModelCapabilityColumns(ctx, db); err != nil {
return err
}
@@ -5466,6 +5469,50 @@ func migrationAddOpenAIConfigJSONColumn(ctx context.Context, db *gorm.DB) error
return nil
}
+// migrationAddPromptVariablesColumns adds variables_json column to prompt_sessions and prompt_versions
+func migrationAddPromptVariablesColumns(ctx context.Context, db *gorm.DB) error {
+ m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
+ ID: "add_prompt_variables_columns",
+ Migrate: func(tx *gorm.DB) error {
+ tx = tx.WithContext(ctx)
+ migrator := tx.Migrator()
+
+ if !migrator.HasColumn(&tables.TablePromptSession{}, "variables_json") {
+ if err := migrator.AddColumn(&tables.TablePromptSession{}, "VariablesJSON"); err != nil {
+ return fmt.Errorf("failed to add variables_json column to prompt_sessions: %w", err)
+ }
+ }
+
+ if !migrator.HasColumn(&tables.TablePromptVersion{}, "variables_json") {
+ if err := migrator.AddColumn(&tables.TablePromptVersion{}, "VariablesJSON"); err != nil {
+ return fmt.Errorf("failed to add variables_json column to prompt_versions: %w", err)
+ }
+ }
+
+ return nil
+ },
+ Rollback: func(tx *gorm.DB) error {
+ tx = tx.WithContext(ctx)
+ migrator := tx.Migrator()
+ if migrator.HasColumn(&tables.TablePromptSession{}, "variables_json") {
+ if err := migrator.DropColumn(&tables.TablePromptSession{}, "variables_json"); err != nil {
+ return err
+ }
+ }
+ if migrator.HasColumn(&tables.TablePromptVersion{}, "variables_json") {
+ if err := migrator.DropColumn(&tables.TablePromptVersion{}, "variables_json"); err != nil {
+ return err
+ }
+ }
+ return nil
+ },
+ }})
+ if err := m.Migrate(); err != nil {
+ return fmt.Errorf("error while running add_prompt_variables_columns migration: %s", err.Error())
+ }
+ return nil
+}
+
// migrationAddKeyBlacklistedModelsJSONColumn adds blacklisted_models_json to config_keys
// for per-key model deny lists (JSON array of model ids, default []).
func migrationAddKeyBlacklistedModelsJSONColumn(ctx context.Context, db *gorm.DB) error {
diff --git a/framework/configstore/tables/promptSessions.go b/framework/configstore/tables/promptSessions.go
index df96db09aa..4618704ebf 100644
--- a/framework/configstore/tables/promptSessions.go
+++ b/framework/configstore/tables/promptSessions.go
@@ -22,6 +22,8 @@ type TablePromptSession struct {
ModelParams ModelParams `gorm:"-" json:"model_params"`
Provider string `gorm:"type:varchar(100)" json:"provider"`
Model string `gorm:"type:varchar(100)" json:"model"`
+ VariablesJSON *string `gorm:"type:text;column:variables_json" json:"-"`
+ Variables PromptVariables `gorm:"-" json:"variables,omitempty"` // {key: value} map for Jinja2 variables
CreatedAt time.Time `gorm:"not null" json:"created_at"`
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
@@ -40,6 +42,17 @@ func (s *TablePromptSession) BeforeSave(tx *gorm.DB) error {
}
paramsStr := string(data)
s.ModelParamsJSON = ¶msStr
+
+ if s.Variables != nil {
+ varsData, err := json.Marshal(s.Variables)
+ if err != nil {
+ return err
+ }
+ varsStr := string(varsData)
+ s.VariablesJSON = &varsStr
+ } else {
+ s.VariablesJSON = nil
+ }
return nil
}
@@ -52,6 +65,15 @@ func (s *TablePromptSession) AfterFind(tx *gorm.DB) error {
return err
}
}
+ if s.VariablesJSON != nil && *s.VariablesJSON != "" {
+ var vars PromptVariables
+ if err := json.Unmarshal([]byte(*s.VariablesJSON), &vars); err != nil {
+ return err
+ }
+ s.Variables = vars
+ } else {
+ s.Variables = nil
+ }
return nil
}
diff --git a/framework/configstore/tables/promptVersions.go b/framework/configstore/tables/promptVersions.go
index 1703be58f3..ca9e41e039 100644
--- a/framework/configstore/tables/promptVersions.go
+++ b/framework/configstore/tables/promptVersions.go
@@ -12,17 +12,19 @@ import (
// TablePromptVersion represents an immutable version of a prompt
// Once created, a version cannot be modified - to make changes, create a new version
type TablePromptVersion struct {
- ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
- PromptID string `gorm:"type:varchar(36);not null;index;uniqueIndex:idx_prompt_version" json:"prompt_id"`
- Prompt *TablePrompt `gorm:"foreignKey:PromptID" json:"prompt,omitempty"`
- VersionNumber int `gorm:"not null;uniqueIndex:idx_prompt_version" json:"version_number"`
- CommitMessage string `gorm:"type:text" json:"commit_message"`
- ModelParamsJSON *string `gorm:"type:text;column:model_params_json" json:"-"`
- ModelParams ModelParams `gorm:"-" json:"model_params"`
- Provider string `gorm:"type:varchar(100)" json:"provider"`
- Model string `gorm:"type:varchar(100)" json:"model"`
- IsLatest bool `gorm:"not null;default:false" json:"is_latest"`
- CreatedAt time.Time `gorm:"not null" json:"created_at"`
+ ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
+ PromptID string `gorm:"type:varchar(36);not null;index;uniqueIndex:idx_prompt_version" json:"prompt_id"`
+ Prompt *TablePrompt `gorm:"foreignKey:PromptID" json:"prompt,omitempty"`
+ VersionNumber int `gorm:"not null;uniqueIndex:idx_prompt_version" json:"version_number"`
+ CommitMessage string `gorm:"type:text" json:"commit_message"`
+ ModelParamsJSON *string `gorm:"type:text;column:model_params_json" json:"-"`
+ ModelParams ModelParams `gorm:"-" json:"model_params"`
+ Provider string `gorm:"type:varchar(100)" json:"provider"`
+ Model string `gorm:"type:varchar(100)" json:"model"`
+ VariablesJSON *string `gorm:"type:text;column:variables_json" json:"-"`
+ Variables PromptVariables `gorm:"-" json:"variables,omitempty"` // {key: value} map for Jinja2 variables
+ IsLatest bool `gorm:"not null;default:false" json:"is_latest"`
+ CreatedAt time.Time `gorm:"not null" json:"created_at"`
// No UpdatedAt - versions are immutable
// Relationships
@@ -36,6 +38,10 @@ func (TablePromptVersion) TableName() string { return "prompt_versions" }
// so that any provider-specific params (response_format, seed, logprobs, etc.) are preserved.
type ModelParams map[string]interface{}
+// PromptVariables represents a map of Jinja2 variable names to their values.
+// Sessions store full {key: value} pairs; versions store {key: ""} (keys only).
+type PromptVariables map[string]string
+
// BeforeSave GORM hook to serialize JSON fields
func (v *TablePromptVersion) BeforeSave(tx *gorm.DB) error {
if v.ModelParams != nil {
@@ -46,6 +52,14 @@ func (v *TablePromptVersion) BeforeSave(tx *gorm.DB) error {
paramsStr := string(data)
v.ModelParamsJSON = ¶msStr
}
+ if v.Variables != nil {
+ varsData, err := json.Marshal(v.Variables)
+ if err != nil {
+ return err
+ }
+ varsStr := string(varsData)
+ v.VariablesJSON = &varsStr
+ }
return nil
}
@@ -58,6 +72,11 @@ func (v *TablePromptVersion) AfterFind(tx *gorm.DB) error {
return err
}
}
+ if v.VariablesJSON != nil && *v.VariablesJSON != "" {
+ if err := json.Unmarshal([]byte(*v.VariablesJSON), &v.Variables); err != nil {
+ return err
+ }
+ }
return nil
}
diff --git a/framework/routing/routing.go b/framework/routing/routing.go
new file mode 100644
index 0000000000..c29dd25673
--- /dev/null
+++ b/framework/routing/routing.go
@@ -0,0 +1,66 @@
+package routing
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// headerKeyPattern matches header map access patterns like headers["X-Api-Key"] or headers['X-Api-Key']
+var headerKeyPattern = regexp.MustCompile(`headers\[["']([^"']+)["']\]`)
+
+// headerInPattern matches "in headers" membership test patterns like "X-Api-Key" in headers or 'X-Api-Key' in headers
+var headerInPattern = regexp.MustCompile(`["']([^"']+)["']\s+in\s+headers`)
+
+// paramKeyPattern matches param map access patterns like params["Region"] or params['Region']
+var paramKeyPattern = regexp.MustCompile(`params\[["']([^"']+)["']\]`)
+
+// paramInPattern matches "in params" membership test patterns like "Region" in params or 'Region' in params
+var paramInPattern = regexp.MustCompile(`["']([^"']+)["']\s+in\s+params`)
+
+// normalizeMapKeysInCEL lowercases header and param keys in CEL expressions
+// so that headers["X-Api-Key"] becomes headers["x-api-key"], "X-Api-Key" in headers becomes "x-api-key" in headers,
+// params["Region"] becomes params["region"], and "Region" in params becomes "region" in params.
+// This ensures CEL expressions match against the normalized (lowercase) map keys at runtime.
+func NormalizeMapKeysInCEL(expr string) string {
+ toLower := func(match string) string {
+ return strings.ToLower(match)
+ }
+ // Normalize bracket access
+ expr = headerKeyPattern.ReplaceAllStringFunc(expr, toLower)
+ expr = paramKeyPattern.ReplaceAllStringFunc(expr, toLower)
+ // Normalize "in" membership test
+ expr = headerInPattern.ReplaceAllStringFunc(expr, toLower)
+ expr = paramInPattern.ReplaceAllStringFunc(expr, toLower)
+ return expr
+}
+
+// validateCELExpression performs basic validation on CEL expression format
+func ValidateCELExpression(expr string) error {
+ normalized := strings.TrimSpace(expr)
+ if normalized == "" || normalized == "true" || normalized == "false" {
+ return nil // Empty, true, or false are valid
+ }
+
+ // List of allowed operators and keywords
+ validPatterns := []string{
+ "==", "!=", "&&", "||", ">", "<", ">=", "<=",
+ "in ", "matches ", ".startsWith(", ".contains(", ".endsWith(",
+ "[", "]", "(", ")", "!",
+ }
+
+ // Check if expression contains at least one valid operator
+ hasPattern := false
+ for _, pattern := range validPatterns {
+ if strings.Contains(normalized, pattern) {
+ hasPattern = true
+ break
+ }
+ }
+
+ if !hasPattern {
+ return fmt.Errorf("expression must contain at least one operator: %s", expr)
+ }
+
+ return nil
+}
diff --git a/plugins/governance/routing.go b/plugins/governance/routing.go
index b32044be02..8e20d5ac48 100644
--- a/plugins/governance/routing.go
+++ b/plugins/governance/routing.go
@@ -3,7 +3,6 @@ package governance
import (
"fmt"
"math/rand/v2"
- "regexp"
"strings"
"github.com/google/cel-go/cel"
@@ -14,18 +13,6 @@ import (
// DefaultRoutingChainMaxDepth is the default maximum depth for routing rule chain evaluation.
const DefaultRoutingChainMaxDepth = 10
-// headerKeyPattern matches header map access patterns like headers["X-Api-Key"] or headers['X-Api-Key']
-var headerKeyPattern = regexp.MustCompile(`headers\[["']([^"']+)["']\]`)
-
-// headerInPattern matches "in headers" membership test patterns like "X-Api-Key" in headers or 'X-Api-Key' in headers
-var headerInPattern = regexp.MustCompile(`["']([^"']+)["']\s+in\s+headers`)
-
-// paramKeyPattern matches param map access patterns like params["Region"] or params['Region']
-var paramKeyPattern = regexp.MustCompile(`params\[["']([^"']+)["']\]`)
-
-// paramInPattern matches "in params" membership test patterns like "Region" in params or 'Region' in params
-var paramInPattern = regexp.MustCompile(`["']([^"']+)["']\s+in\s+params`)
-
// ScopeLevel represents a level in the scope precedence hierarchy
type ScopeLevel struct {
ScopeName string // "virtual_key", "team", "customer", or "global"
@@ -482,52 +469,6 @@ func scopeChainToStrings(chain []ScopeLevel) []string {
return scopes
}
-// validateCELExpression performs basic validation on CEL expression format
-func validateCELExpression(expr string) error {
- if expr == "" || expr == "true" || expr == "false" {
- return nil // Empty, true, or false are valid
- }
-
- // List of allowed operators and keywords
- validPatterns := []string{
- "==", "!=", "&&", "||", ">", "<", ">=", "<=",
- "in ", "matches ", ".startsWith(", ".contains(", ".endsWith(",
- "[", "]", "(", ")", "!",
- }
-
- // Check if expression contains at least one valid operator
- hasPattern := false
- for _, pattern := range validPatterns {
- if strings.Contains(expr, pattern) {
- hasPattern = true
- break
- }
- }
-
- if !hasPattern {
- return fmt.Errorf("expression must contain at least one operator: %s", expr)
- }
-
- return nil
-}
-
-// normalizeMapKeysInCEL lowercases header and param keys in CEL expressions
-// so that headers["X-Api-Key"] becomes headers["x-api-key"], "X-Api-Key" in headers becomes "x-api-key" in headers,
-// params["Region"] becomes params["region"], and "Region" in params becomes "region" in params.
-// This ensures CEL expressions match against the normalized (lowercase) map keys at runtime.
-func normalizeMapKeysInCEL(expr string) string {
- toLower := func(match string) string {
- return strings.ToLower(match)
- }
- // Normalize bracket access
- expr = headerKeyPattern.ReplaceAllStringFunc(expr, toLower)
- expr = paramKeyPattern.ReplaceAllStringFunc(expr, toLower)
- // Normalize "in" membership test
- expr = headerInPattern.ReplaceAllStringFunc(expr, toLower)
- expr = paramInPattern.ReplaceAllStringFunc(expr, toLower)
- return expr
-}
-
// createCELEnvironment creates a new CEL environment for routing rules
func createCELEnvironment() (*cel.Env, error) {
return cel.NewEnv(
diff --git a/plugins/governance/routing_test.go b/plugins/governance/routing_test.go
index 02aaa6a59a..21e8f7469d 100644
--- a/plugins/governance/routing_test.go
+++ b/plugins/governance/routing_test.go
@@ -11,6 +11,7 @@ import (
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/configstore"
configstoreTables "github.com/maximhq/bifrost/framework/configstore/tables"
+ "github.com/maximhq/bifrost/framework/routing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -1159,7 +1160,7 @@ func TestValidateCELExpression_Valid(t *testing.T) {
}
for _, expr := range tests {
- err := validateCELExpression(expr)
+ err := routing.ValidateCELExpression(expr)
assert.NoError(t, err, "expression should be valid: %s", expr)
}
}
@@ -1173,7 +1174,7 @@ func TestValidateCELExpression_Invalid(t *testing.T) {
}
for _, expr := range tests {
- err := validateCELExpression(expr)
+ err := routing.ValidateCELExpression(expr)
assert.Error(t, err, "expression should be invalid: %s", expr)
}
}
@@ -1733,7 +1734,7 @@ func TestNormalizeMapKeysInCEL(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result := normalizeMapKeysInCEL(tt.input)
+ result := routing.NormalizeMapKeysInCEL(tt.input)
assert.Equal(t, tt.expected, result)
})
}
diff --git a/plugins/governance/store.go b/plugins/governance/store.go
index 4775c71bd4..495f18944d 100644
--- a/plugins/governance/store.go
+++ b/plugins/governance/store.go
@@ -14,6 +14,7 @@ import (
"github.com/maximhq/bifrost/framework/configstore"
configstoreTables "github.com/maximhq/bifrost/framework/configstore/tables"
"github.com/maximhq/bifrost/framework/modelcatalog"
+ "github.com/maximhq/bifrost/framework/routing"
"gorm.io/gorm"
)
@@ -3451,10 +3452,10 @@ func (gs *LocalGovernanceStore) GetRoutingProgram(rule *configstoreTables.TableR
}
// Normalize header and param keys to lowercase so CEL expressions match normalized map keys
- expr = normalizeMapKeysInCEL(expr)
+ expr = routing.NormalizeMapKeysInCEL(expr)
// Validate expression format
- if err := validateCELExpression(expr); err != nil {
+ if err := routing.ValidateCELExpression(expr); err != nil {
return nil, fmt.Errorf("invalid CEL expression: %w", err)
}
diff --git a/transports/bifrost-http/handlers/prompts.go b/transports/bifrost-http/handlers/prompts.go
index e5b96f0c38..c5e5737f0f 100644
--- a/transports/bifrost-http/handlers/prompts.go
+++ b/transports/bifrost-http/handlers/prompts.go
@@ -137,25 +137,28 @@ type CreateVersionRequest struct {
ModelParams tables.ModelParams `json:"model_params"`
Provider string `json:"provider"`
Model string `json:"model"`
+ Variables tables.PromptVariables `json:"variables,omitempty"`
}
// CreateSessionRequest represents the request body for creating a session
type CreateSessionRequest struct {
- Name string `json:"name"`
- VersionID *uint `json:"version_id,omitempty"`
- Messages []tables.PromptMessage `json:"messages,omitempty"`
- ModelParams tables.ModelParams `json:"model_params"`
- Provider string `json:"provider"`
- Model string `json:"model"`
+ Name string `json:"name"`
+ VersionID *uint `json:"version_id,omitempty"`
+ Messages []tables.PromptMessage `json:"messages,omitempty"`
+ ModelParams tables.ModelParams `json:"model_params"`
+ Provider string `json:"provider"`
+ Model string `json:"model"`
+ Variables tables.PromptVariables `json:"variables,omitempty"`
}
// UpdateSessionRequest represents the request body for updating a session
type UpdateSessionRequest struct {
- Name string `json:"name"`
- Messages []tables.PromptMessage `json:"messages"`
- ModelParams tables.ModelParams `json:"model_params"`
- Provider string `json:"provider"`
- Model string `json:"model"`
+ Name string `json:"name"`
+ Messages []tables.PromptMessage `json:"messages"`
+ ModelParams tables.ModelParams `json:"model_params"`
+ Provider string `json:"provider"`
+ Model string `json:"model"`
+ Variables tables.PromptVariables `json:"variables,omitempty"`
}
// RenameSessionRequest represents the request body for renaming a session
@@ -631,12 +634,22 @@ func (h *PromptsHandler) createVersion(ctx *fasthttp.RequestCtx) {
})
}
+ // Strip variable values — versions store keys only; values live in sessions
+ var versionVars tables.PromptVariables
+ if len(req.Variables) > 0 {
+ versionVars = make(tables.PromptVariables, len(req.Variables))
+ for key := range req.Variables {
+ versionVars[key] = ""
+ }
+ }
+
version := &tables.TablePromptVersion{
PromptID: promptID,
CommitMessage: req.CommitMessage,
ModelParams: req.ModelParams,
Provider: req.Provider,
Model: req.Model,
+ Variables: versionVars,
Messages: messages,
}
@@ -835,6 +848,7 @@ func (h *PromptsHandler) createSession(ctx *fasthttp.RequestCtx) {
ModelParams: req.ModelParams,
Provider: req.Provider,
Model: req.Model,
+ Variables: req.Variables,
Messages: messages,
}
@@ -890,6 +904,7 @@ func (h *PromptsHandler) updateSession(ctx *fasthttp.RequestCtx) {
session.ModelParams = req.ModelParams
session.Provider = req.Provider
session.Model = req.Model
+ session.Variables = req.Variables
// Update messages
var messages []tables.TablePromptSessionMessage
@@ -1066,12 +1081,22 @@ func (h *PromptsHandler) commitSession(ctx *fasthttp.RequestCtx) {
return
}
+ // Copy variable keys from session with empty values for the version
+ var versionVars tables.PromptVariables
+ if len(session.Variables) > 0 {
+ versionVars = make(tables.PromptVariables, len(session.Variables))
+ for key := range session.Variables {
+ versionVars[key] = ""
+ }
+ }
+
version := &tables.TablePromptVersion{
PromptID: session.PromptID,
CommitMessage: req.CommitMessage,
ModelParams: session.ModelParams,
Provider: session.Provider,
Model: session.Model,
+ Variables: versionVars,
Messages: messages,
}
diff --git a/ui/app/_fallbacks/enterprise/components/prompt-deployments/promptDeploymentView.tsx b/ui/app/_fallbacks/enterprise/components/prompt-deployments/promptDeploymentView.tsx
index 8f392276e8..628e44439a 100644
--- a/ui/app/_fallbacks/enterprise/components/prompt-deployments/promptDeploymentView.tsx
+++ b/ui/app/_fallbacks/enterprise/components/prompt-deployments/promptDeploymentView.tsx
@@ -3,10 +3,11 @@ import ContactUsView from "../views/contactUsView";
export default function PromptDeploymentView() {
return (
-
+
}
+ align="top"
+ className="justify-start gap-3 rounded-md border p-4"
+ icon={}
title="Unlock prompt deployments for better prompt versioning and A/B testing."
description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
readmeLink="https://docs.getbifrost.ai/enterprise/prompt-deployments"
diff --git a/ui/app/globals.css b/ui/app/globals.css
index 2a043151fb..96650d22a5 100644
--- a/ui/app/globals.css
+++ b/ui/app/globals.css
@@ -3,6 +3,7 @@
@source "../app/**/*.tsx";
@source "../node_modules/streamdown/dist/*.js";
+@source "../../../bifrost-enterprise/ui/**/*.tsx";
@custom-variant dark (&:is(.dark *));
diff --git a/ui/app/workspace/mcp-logs/views/bifrost.code-workspace b/ui/app/workspace/mcp-logs/views/bifrost.code-workspace
new file mode 100644
index 0000000000..8c5511061b
--- /dev/null
+++ b/ui/app/workspace/mcp-logs/views/bifrost.code-workspace
@@ -0,0 +1,11 @@
+{
+ "folders": [
+ {
+ "path": "../../../../.."
+ },
+ {
+ "path": "../../../../../../bifrost-enterprise"
+ }
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/ui/app/workspace/prompt-repo/deployments/page.tsx b/ui/app/workspace/prompt-repo/deployments/page.tsx
deleted file mode 100644
index 26adfda683..0000000000
--- a/ui/app/workspace/prompt-repo/deployments/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import PromptDeploymentView from "@enterprise/components/prompt-deployments/promptDeploymentView";
-
-export default function PromptDeploymentsPage() {
- return (
-
-
-
- );
-}
\ No newline at end of file
diff --git a/ui/app/workspace/prompt-repo/page.tsx b/ui/app/workspace/prompt-repo/page.tsx
new file mode 100644
index 0000000000..935bff8f59
--- /dev/null
+++ b/ui/app/workspace/prompt-repo/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { PromptProvider } from "@/components/prompts/context";
+import PromptsView from "@/components/prompts/promptsView";
+
+export default function PromptRepoPage() {
+ return (
+
+
+
+ );
+}
diff --git a/ui/app/workspace/prompt-repo/prompts/page.tsx b/ui/app/workspace/prompt-repo/prompts/page.tsx
index fa96bb86f7..b2d6f8a7db 100644
--- a/ui/app/workspace/prompt-repo/prompts/page.tsx
+++ b/ui/app/workspace/prompt-repo/prompts/page.tsx
@@ -1,10 +1,9 @@
-import { PromptProvider } from "@/components/prompts/context";
-import PromptsView from "@/components/prompts/promptsView";
+"use client";
+
+import { redirect, useSearchParams } from "next/navigation";
export default function PromptsPage() {
- return (
-
-
-
- );
-}
\ No newline at end of file
+ const searchParams = useSearchParams();
+ const queryString = searchParams.toString();
+ redirect(`/workspace/prompt-repo${queryString ? `?${queryString}` : ""}`);
+}
diff --git a/ui/app/workspace/routing-rules/components/celBuilder/celRuleBuilder.tsx b/ui/app/workspace/routing-rules/components/celBuilder/celRuleBuilder.tsx
index 4660699352..29f034d5d9 100644
--- a/ui/app/workspace/routing-rules/components/celBuilder/celRuleBuilder.tsx
+++ b/ui/app/workspace/routing-rules/components/celBuilder/celRuleBuilder.tsx
@@ -1,25 +1,16 @@
/**
- * CEL Rule Builder Component for Routing Rules
- * Visual query builder for creating CEL expressions
+ * CEL Rule Builder for Routing Rules
+ * Thin wrapper around the reusable CELRuleBuilder with routing-specific config
*/
-import { Button } from "@/components/ui/button";
-import { Label } from "@/components/ui/label";
-import { Textarea } from "@/components/ui/textarea";
+"use client";
+
+import { CELRuleBuilder as BaseCELRuleBuilder } from "@/components/ui/custom/celBuilder";
import { getRoutingFields } from "@/lib/config/celFieldsRouting";
import { celOperatorsRouting } from "@/lib/config/celOperatorsRouting";
-import { convertRuleGroupToCEL } from "@/lib/utils/celConverterRouting";
-import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
-import { Check, Copy, Loader2 } from "lucide-react";
-import { useEffect, useMemo, useRef, useState } from "react";
-import { Field, QueryBuilder, RuleGroupType } from "react-querybuilder";
-import "react-querybuilder/dist/query-builder.css";
-import { ActionButton } from "./actionButton";
-import { CombinatorSelector } from "./combinatorSelector";
-import { FieldSelector } from "./fieldSelector";
-import { OperatorSelector } from "./operatorSelector";
-import { QueryBuilderWrapper } from "./queryBuilderWrapper";
-import { ValueEditor } from "./valueEditor";
+import { convertRuleGroupToCEL, validateRegexPattern } from "@/lib/utils/celConverterRouting";
+import { useMemo } from "react";
+import { RuleGroupType } from "react-querybuilder";
interface CELRuleBuilderProps {
onChange?: (celExpression: string, query: RuleGroupType) => void;
@@ -30,11 +21,6 @@ interface CELRuleBuilderProps {
isLoading?: boolean;
}
-const defaultQuery: RuleGroupType = {
- combinator: "and",
- rules: [],
-};
-
export function CELRuleBuilder({
onChange,
initialQuery,
@@ -43,96 +29,18 @@ export function CELRuleBuilder({
isLoading = false,
allowCustomModels = false,
}: CELRuleBuilderProps) {
- const [query, setQuery] = useState(initialQuery || defaultQuery);
- const [celExpression, setCelExpression] = useState("");
- const { copy, copied } = useCopyToClipboard();
- const onChangeRef = useRef(onChange);
-
- // Keep ref updated so the query effect always invokes the latest callback
- useEffect(() => {
- onChangeRef.current = onChange;
- }, [onChange]);
-
- // Generate fields with dynamic providers and models
- const fields = useMemo(() => {
- const celFields = getRoutingFields(providers, models);
- return celFields.map((field) => ({
- ...field,
- value: field.name,
- })) as Field[];
- }, [providers, models]);
-
- useEffect(() => {
- const expression = convertRuleGroupToCEL(query);
- setCelExpression(expression);
- onChangeRef.current?.(expression, query);
- }, [query]);
-
- const handleCopy = () => copy(celExpression);
-
- // Show loading state
- if (isLoading) {
- return (
-