mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-11 08:38:24 +00:00
refactor(ui): clear hardcoded English from dashboard JSX (#50575)
* refactor(ui): clear hardcoded English from dashboard JSX
Move the 283 strings the planned no-literal-string guard flags so main
starts at zero:
- missed UI text (Switchyard router form, Create User custom ID field,
advanced settings placeholders) and our own fallbacks ("unknown",
seconds suffixes) onto t() with new en.json keys
- product names into src/i18n/untranslated/productNames.ts and code
samples, sample values and JSON placeholders into
src/i18n/untranslated/codeSamples.ts
- values that never display (filter ids, enum fallbacks, query keys,
CSS, ids) onto literal-union types or module constants
The rendered English is byte-identical.
* refactor(ui): put Bedrock policy chips and test panel error on t()
The six Bedrock policy chips and the compliance test panel's error word are our own display labels, not server values, so they go through t() instead of module constants. Rendered English is unchanged.
This commit is contained in:
parent
7617e66acf
commit
c22dc25316
117 changed files with 746 additions and 304 deletions
|
|
@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { AgentFormField, type AgentFormValues } from "./AgentFormKit";
|
||||
import { entraTenantFromIssuer, IDENTITY_UUID_PATTERN } from "./agent_identity";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const PROVIDER_OPTIONS: { value: string; labelKey: ParseKeys }[] = [
|
||||
{ value: "none", labelKey: "agents.identityFields.providers.none" },
|
||||
|
|
@ -206,7 +207,7 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
|
|||
ref={ref}
|
||||
value={typeof value === "string" ? value : ""}
|
||||
onChange={onChange}
|
||||
placeholder="Agent.Invoke"
|
||||
placeholder={codeSamples.agents.identityScope}
|
||||
/>
|
||||
)}
|
||||
</AgentFormField>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { PasswordInput } from "@/components/shared/PasswordInput";
|
|||
import i18n from "@/i18n/i18n";
|
||||
import { AgentFormField, AgentFormValues, labelWithHint } from "./AgentFormKit";
|
||||
import { KILL_SWITCH_AUTH_TYPES, KILL_SWITCH_METHODS, validateKillSwitchBody } from "./kill_switch_config";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const KeyValueFieldArray = ({
|
||||
name,
|
||||
|
|
@ -135,7 +136,7 @@ const KillSwitchAuthFields = () => {
|
|||
<TextField
|
||||
name="kill_switch.auth_header_name"
|
||||
label={t("agents.killSwitchFields.auth.headerName")}
|
||||
placeholder="X-API-Key"
|
||||
placeholder={codeSamples.agents.killSwitchAuthHeader}
|
||||
/>
|
||||
<TextField
|
||||
name="kill_switch.auth_api_key"
|
||||
|
|
@ -224,7 +225,7 @@ const KillSwitchFormFields = () => (
|
|||
{...control}
|
||||
ref={ref}
|
||||
rows={4}
|
||||
placeholder='{"reason": "manual kill switch"}'
|
||||
placeholder={codeSamples.agents.killSwitchPayloadJson}
|
||||
value={typeof value === "string" ? value : ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
|||
import CodeBlock from "@/components/CodeBlock";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import DocLink from "./DocLink";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
interface ApiRefProps {
|
||||
proxySettings: {
|
||||
|
|
@ -36,13 +37,13 @@ const APIReferenceView: React.FC<ApiRefProps> = ({ proxySettings }) => {
|
|||
<Tabs defaultValue="openai">
|
||||
<TabsList variant="line" className="border-b rounded-none w-full justify-start h-auto p-0">
|
||||
<TabsTrigger value="openai" className="rounded-none px-4 py-2 flex-none">
|
||||
OpenAI Python SDK
|
||||
{productNames.openAIPythonSdk}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="llamaindex" className="rounded-none px-4 py-2 flex-none">
|
||||
LlamaIndex
|
||||
{productNames.llamaIndex}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="langchain" className="rounded-none px-4 py-2 flex-none">
|
||||
Langchain Py
|
||||
{productNames.langchainPython}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="openai" keepMounted>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||
import { CheckCircle2, XCircle, ClipboardCopy } from "lucide-react";
|
||||
import { ResponseTimeIndicator } from "./response_time_indicator";
|
||||
|
||||
const UNHEALTHY_STATUS = "unhealthy";
|
||||
|
||||
// Helper function to deep-parse a JSON string if possible
|
||||
const deepParse = (input: any) => {
|
||||
let parsed = input;
|
||||
|
|
@ -179,7 +181,7 @@ const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => {
|
|||
className={`text-sm font-medium ${response?.status === "healthy" ? "text-success" : "text-destructive"}`}
|
||||
>
|
||||
{t("caching.health.cacheStatus")}
|
||||
{response?.status || "unhealthy"}
|
||||
{response?.status || UNHEALTHY_STATUS}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const DEFAULT_PROMPT = `Evaluate whether this guardrail's decision was correct.
|
||||
Analyze the user input, the guardrail action taken, and determine if it was appropriate.
|
||||
|
|
@ -130,7 +131,7 @@ export function EvaluationSettingsModal({
|
|||
<label htmlFor="evaluation-schema" className="mb-1.5 block text-sm font-medium text-foreground">
|
||||
{t("guardrailsMonitor.evaluationSettings.schema.label")}
|
||||
</label>
|
||||
<p className="mb-1 text-xs text-muted-foreground">response_format: json_schema</p>
|
||||
<p className="mb-1 text-xs text-muted-foreground">{codeSamples.guardrails.evaluationResponseFormat}</p>
|
||||
<Textarea
|
||||
id="evaluation-schema"
|
||||
value={schema}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { GuardrailModeRows } from "./GuardrailModeDisplay";
|
|||
import { GuardrailStreamScopeDetail } from "./StreamScopeFields";
|
||||
import ToolPermissionRulesEditor, { type ToolPermissionConfig } from "./tool_permission/ToolPermissionRulesEditor";
|
||||
|
||||
const DEFAULT_CHECK_ACTION = "block";
|
||||
|
||||
interface DecisionModelCheckView {
|
||||
name: string;
|
||||
action?: string;
|
||||
|
|
@ -35,7 +37,7 @@ const DecisionModelSection = ({
|
|||
<div key={check.name} className="flex items-center border-b border-border px-4 py-2 text-sm last:border-b-0">
|
||||
<span className="flex-1">{check.name.replace(/_/g, " ")}</span>
|
||||
<span className="w-24 text-right">
|
||||
<Badge variant="secondary">{check.action ?? "block"}</Badge>
|
||||
<Badge variant="secondary">{check.action ?? DEFAULT_CHECK_ACTION}</Badge>
|
||||
</span>
|
||||
<span className="w-24 text-right">{check.threshold ?? DEFAULT_DECISION_THRESHOLD}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import GuardrailTestResults from "./GuardrailTestResults";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface GuardrailTestPanelProps {
|
||||
guardrailNames: string[];
|
||||
|
|
@ -205,7 +206,7 @@ export function GuardrailTestPanel({
|
|||
setMetadataError(parseMetadata(e.target.value).error);
|
||||
}
|
||||
}}
|
||||
placeholder='{"forbidden_topics": ["tax", "finance"]}'
|
||||
placeholder={codeSamples.guardrails.testPanelParamsJson}
|
||||
rows={3}
|
||||
className="font-mono text-sm field-sizing-fixed"
|
||||
aria-invalid={metadataError ? true : undefined}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import { isValidUrl } from "@/lib/forms/urlValidation";
|
|||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const GUARDRAIL_MODES: { value: string; labelKey: ParseKeys }[] = [
|
||||
{ value: "pre_call", labelKey: "guardrails.teamTab.modes.preCall" },
|
||||
|
|
@ -1210,7 +1211,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
ref={ref}
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"forward_api_key": true, "headers": {"X-Custom": "value"}}'
|
||||
placeholder={codeSamples.guardrails.teamGuardrailParamsJson}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
|
|
@ -1225,7 +1226,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
ref={ref}
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"description": "Detects PII in requests"}'
|
||||
placeholder={codeSamples.guardrails.teamGuardrailInfoJson}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ const ALWAYS_ON_ITEMS: { labelKey: ParseKeys; value: boolean }[] = [
|
|||
];
|
||||
|
||||
const DEFAULT_MODES = ["pre_call", "during_call", "post_call", "logging_only"];
|
||||
const REALTIME_VIOLATION_ACTIONS = ["warn", "end_session"] as const;
|
||||
|
||||
const CALL_TYPE_ITEMS = [{ label: "/v1/realtime", value: "realtime" }];
|
||||
|
||||
|
|
@ -1171,7 +1172,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
|
|||
{t("guardrails.addForm.endpoint.realtime.onViolation.label")}
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{(["warn", "end_session"] as const).map((opt) => (
|
||||
{REALTIME_VIOLATION_ACTIONS.map((opt) => (
|
||||
<label key={opt} className="flex items-start gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import {
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { ACTION_ITEMS, SEVERITY_ITEMS, translateOptions } from "./action_options";
|
||||
|
||||
const DEFAULT_CATEGORY_FILE_TYPE = "yaml";
|
||||
|
||||
interface ContentCategory {
|
||||
name: string;
|
||||
display_name: string;
|
||||
|
|
@ -39,7 +41,7 @@ interface ContentCategoryConfigurationProps {
|
|||
selectedCategories: SelectedCategory[];
|
||||
onCategoryAdd: (category: SelectedCategory) => void;
|
||||
onCategoryRemove: (id: string) => void;
|
||||
onCategoryUpdate: (id: string, field: string, value: any) => void;
|
||||
onCategoryUpdate: (id: string, field: "action" | "severity_threshold", value: any) => void;
|
||||
accessToken?: string | null;
|
||||
pendingSelection?: string;
|
||||
onPendingSelectionChange?: (value: string) => void;
|
||||
|
|
@ -123,7 +125,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
}
|
||||
|
||||
setCategoryYaml((prev) => ({ ...prev, [categoryName]: content }));
|
||||
setCategoryFileTypes((prev) => ({ ...prev, [categoryName]: data.file_type || "yaml" }));
|
||||
setCategoryFileTypes((prev) => ({ ...prev, [categoryName]: data.file_type || DEFAULT_CATEGORY_FILE_TYPE }));
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch content for category ${categoryName}:`, error);
|
||||
} finally {
|
||||
|
|
@ -160,7 +162,10 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
setPreviewYaml(content);
|
||||
// Also cache it for later use
|
||||
setCategoryYaml((prev) => ({ ...prev, [selectedCategoryName]: content }));
|
||||
setCategoryFileTypes((prev) => ({ ...prev, [selectedCategoryName]: data.file_type || "yaml" }));
|
||||
setCategoryFileTypes((prev) => ({
|
||||
...prev,
|
||||
[selectedCategoryName]: data.file_type || DEFAULT_CATEGORY_FILE_TYPE,
|
||||
}));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed to fetch preview content for category ${selectedCategoryName}:`, error);
|
||||
|
|
@ -339,7 +344,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
<DataTable data={selectedCategories} columns={columns} getRowId={(row) => row.id} size="compact" />
|
||||
<div className="mt-4 space-y-2">
|
||||
{selectedCategories.map((category) => {
|
||||
const fileType = categoryFileTypes[category.category] || "yaml";
|
||||
const fileType = categoryFileTypes[category.category] || DEFAULT_CATEGORY_FILE_TYPE;
|
||||
const isExpanded = expandedYamlCategories.includes(category.category);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ interface BlockedWord {
|
|||
|
||||
interface KeywordTableProps {
|
||||
keywords: BlockedWord[];
|
||||
onActionChange: (id: string, field: string, value: any) => void;
|
||||
onActionChange: (id: string, field: "action", value: any) => void;
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import {
|
|||
TEMPLATE_ITEMS,
|
||||
type ModeOption,
|
||||
} from "./custom_code_catalog";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
// Data for editing an existing guardrail
|
||||
|
||||
|
|
@ -696,7 +697,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
|
|||
i18nKey="guardrails.customCode.test.fields.structuredMessages"
|
||||
components={{ field: <strong>structured_messages</strong> }}
|
||||
/>{" "}
|
||||
<span className="text-warning">(pre_call)</span>
|
||||
<span className="text-warning">{codeSamples.guardrails.customCodeHook}</span>
|
||||
</div>
|
||||
<div>
|
||||
<Trans
|
||||
|
|
@ -711,7 +712,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
|
|||
onChange={(e) => setTestInput(e.target.value)}
|
||||
rows={8}
|
||||
className="font-mono text-xs field-sizing-fixed"
|
||||
placeholder='{"texts": ["test message"], ...}'
|
||||
placeholder={codeSamples.guardrails.customCodeInputJson}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ const GuardrailCard: React.FC<{ card: GuardrailCardInfo; onClick: () => void }>
|
|||
<div className="mt-2.5 flex items-center gap-1 text-success">
|
||||
<CircleCheck className="size-3" />
|
||||
<span className="text-[11px] font-medium">
|
||||
F1: {card.eval.f1}% · {card.eval.testCases} {t("guardrails.gardenCard.testCases")}
|
||||
{t("guardrails.gardenCard.f1Label")}
|
||||
{card.eval.f1}% · {card.eval.testCases} {t("guardrails.gardenCard.testCases")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import AddGuardrailForm from "./add_guardrail_form";
|
|||
import { Logo } from "@/components/molecules/logo/Logo";
|
||||
import { GUARDRAIL_PRESETS } from "./guardrail_garden_configs";
|
||||
import { GuardrailCardInfo, gardenCardDescription, gardenCardName, gardenTagLabel } from "./guardrail_garden_data";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const CATEGORY_LABEL_KEYS: Record<GuardrailCardInfo["category"], { provider: ParseKeys; type: ParseKeys }> = {
|
||||
litellm: {
|
||||
|
|
@ -180,7 +181,10 @@ const GuardrailDetailView: React.FC<GuardrailDetailViewProps> = ({ card, onBack,
|
|||
{/* Guardrail ID */}
|
||||
<div className="mb-7">
|
||||
<div className="mb-1 text-xs text-muted-foreground">{t("guardrails.gardenDetail.guardrailIdLabel")}</div>
|
||||
<div className="break-all text-[13px] text-foreground">litellm/{card.id}</div>
|
||||
<div className="break-all text-[13px] text-foreground">
|
||||
{codeSamples.guardrails.gardenIdPrefix}
|
||||
{card.id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Type */}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||
import { Separator } from "@/components/ui/separator";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
export type ToolPermissionDecision = "allow" | "deny";
|
||||
export type ToolPermissionDefaultAction = "allow" | "deny";
|
||||
|
|
@ -154,13 +155,13 @@ const ToolPermissionRulesEditor: React.FC<ToolPermissionRulesEditorProps> = ({ v
|
|||
<div key={`${rule.id || index}-${patternIndex}`} className="flex items-start gap-2">
|
||||
<Input
|
||||
disabled={disabled}
|
||||
placeholder="messages[0].content"
|
||||
placeholder={codeSamples.guardrails.toolParamPath}
|
||||
value={path}
|
||||
onChange={(e) => updateAllowedParamPath(index, patternIndex, e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
disabled={disabled}
|
||||
placeholder="^email@.*$"
|
||||
placeholder={codeSamples.guardrails.toolParamPattern}
|
||||
value={pattern}
|
||||
onChange={(e) => updateAllowedParamPattern(index, patternIndex, e.target.value)}
|
||||
/>
|
||||
|
|
@ -262,7 +263,7 @@ const ToolPermissionRulesEditor: React.FC<ToolPermissionRulesEditorProps> = ({ v
|
|||
<p className="text-sm font-medium">{t("guardrails.toolPermission.rule.toolType")}</p>
|
||||
<Input
|
||||
disabled={disabled}
|
||||
placeholder="^function$"
|
||||
placeholder={codeSamples.guardrails.toolTypePattern}
|
||||
value={rule.tool_type ?? ""}
|
||||
onChange={(e) =>
|
||||
updateRule(index, {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export interface PaginatedModelInfoResponse {
|
|||
size: number;
|
||||
}
|
||||
|
||||
const modelKeys = createQueryKeys("models");
|
||||
export const modelKeys = createQueryKeys("models");
|
||||
const modelHubKeys = createQueryKeys("modelHub");
|
||||
const allProxyModelsKeys = createQueryKeys("allProxyModels");
|
||||
const selectedTeamModelsKeys = createQueryKeys("selectedTeamModels");
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { PasswordInput } from "@/components/shared/PasswordInput";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { requiredUnlessSiblingSet, tagsControl, textControl } from "./mcpFieldRules";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface IdJagFormFieldsProps {
|
||||
isEditing?: boolean;
|
||||
|
|
@ -174,7 +175,13 @@ const IdJagFormFields: React.FC<IdJagFormFieldsProps> = ({ isEditing = false })
|
|||
}
|
||||
name={["credentials", "client_assertion_signing_alg"]}
|
||||
>
|
||||
{(control) => <Input {...textControl(control)} placeholder="RS256" className={fieldClassName} />}
|
||||
{(control) => (
|
||||
<Input
|
||||
{...textControl(control)}
|
||||
placeholder={codeSamples.mcpServers.idJagSigningAlgorithm}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
)}
|
||||
</MountedFormField>
|
||||
<MountedFormField
|
||||
label={
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import type { generalSettingsItem } from "@/app/(dashboard)/router-settings/_com
|
|||
import { FIELD_GROUPS, MCP_REQUIRED_FIELD_DEFS, SETTINGS_KEY } from "./MCPStandardsSettings";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
const DEFAULT_TRANSPORT = "sse";
|
||||
|
||||
type MCPStatus = "active" | "pending_review" | "rejected";
|
||||
|
||||
const STATUS_CONFIG: Record<MCPStatus, { labelKey: ParseKeys; bg: string; text: string; dot: string }> = {
|
||||
|
|
@ -476,7 +478,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe
|
|||
<div className="flex items-center gap-3 mt-1.5 text-xs text-muted-foreground">
|
||||
<span>
|
||||
{t("mcpServers.submissionsTab.card.transport")}{" "}
|
||||
<span className="text-muted-foreground">{server.transport ?? "sse"}</span>
|
||||
<span className="text-muted-foreground">{server.transport ?? DEFAULT_TRANSPORT}</span>
|
||||
</span>
|
||||
<span>·</span>
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
tagsControl,
|
||||
textControl,
|
||||
} from "./mcpFieldRules";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface OAuthFlowStatus {
|
||||
startOAuthFlow: () => void;
|
||||
|
|
@ -373,7 +374,7 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
{(control) => (
|
||||
<Textarea
|
||||
{...textControl(control)}
|
||||
placeholder={'{\n "organization": "my-org",\n "team.id": "123"\n}'}
|
||||
placeholder={codeSamples.mcpServers.oauthExtraParamsJson}
|
||||
rows={4}
|
||||
className="font-mono text-sm rounded-lg border-border focus:border-info focus:ring-ring"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { requiredRule } from "@/components/common_components/formRules";
|
|||
import { PasswordInput } from "@/components/shared/PasswordInput";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { selectControl, selectTriggerControl, tagsControl, textControl } from "./mcpFieldRules";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface TokenExchangeFormFieldsProps {
|
||||
isEditing?: boolean;
|
||||
|
|
@ -193,7 +194,9 @@ const TokenExchangeFormFields: React.FC<TokenExchangeFormFieldsProps> = ({ isEdi
|
|||
<MultiSelect
|
||||
{...tagsControl(control, t)}
|
||||
placeholder={
|
||||
isEntraObo ? "api://<app-id>/.default" : t("mcpServers.tokenExchangeFormFields.scopes.placeholder")
|
||||
isEntraObo
|
||||
? codeSamples.mcpServers.entraOboScope
|
||||
: t("mcpServers.tokenExchangeFormFields.scopes.placeholder")
|
||||
}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input";
|
|||
|
||||
import { MountedFormField } from "@/components/common_components/MountedFormField";
|
||||
import { textControl } from "./mcpFieldRules";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const UpstreamTokenHeaderField: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -24,7 +25,7 @@ const UpstreamTokenHeaderField: React.FC = () => {
|
|||
{(control) => (
|
||||
<Input
|
||||
{...textControl(control)}
|
||||
placeholder="Authorization"
|
||||
placeholder={codeSamples.mcpServers.upstreamTokenHeader}
|
||||
className="rounded-lg border-border focus:border-info focus:ring-ring"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import {
|
|||
} from "lucide-react";
|
||||
import { getGatewayBaseUrl, getProxyBaseUrl } from "@/components/networking";
|
||||
import { copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface CodeBlockProps {
|
||||
code: string;
|
||||
|
|
@ -354,7 +356,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
|
|||
icon={<Code className="text-info" size={16} />}
|
||||
titleKey="mcpServers.connect.implementationExampleCard.title"
|
||||
description={t("mcpServers.connect.openaiTab.exampleDescription")}
|
||||
serverName="Zapier Gmail"
|
||||
serverName={codeSamples.mcpServers.exampleServerName}
|
||||
accessGroups={["dev-group"]}
|
||||
>
|
||||
<CodeBlock
|
||||
|
|
@ -427,7 +429,7 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
|
|||
icon={<Code className="text-purple-600 dark:text-purple-400" size={16} />}
|
||||
titleKey="mcpServers.connect.cursorTab.configurationCard.title"
|
||||
description={t("mcpServers.connect.cursorTab.configurationCard.description")}
|
||||
serverName="Zapier Gmail"
|
||||
serverName={codeSamples.mcpServers.exampleServerName}
|
||||
accessGroups={["dev-group"]}
|
||||
>
|
||||
<CodeBlock
|
||||
|
|
@ -524,25 +526,25 @@ const MCPConnect: React.FC<MCPConnectProps> = ({ currentServerAccessGroups = []
|
|||
<TabsTrigger value="openai" className="flex-none px-6 py-3">
|
||||
<span className="flex items-center gap-2 font-medium">
|
||||
<Code size={18} />
|
||||
OpenAI API
|
||||
{productNames.openAIApi}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="litellm" className="flex-none px-6 py-3">
|
||||
<span className="flex items-center gap-2 font-medium">
|
||||
<Zap size={18} />
|
||||
LiteLLM Proxy
|
||||
{productNames.liteLLMProxy}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="cursor" className="flex-none px-6 py-3">
|
||||
<span className="flex items-center gap-2 font-medium">
|
||||
<Terminal size={18} />
|
||||
Cursor
|
||||
{productNames.cursor}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="http" className="flex-none px-6 py-3">
|
||||
<span className="flex items-center gap-2 font-medium">
|
||||
<Globe size={18} />
|
||||
Streamable HTTP
|
||||
{productNames.streamableHttp}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { DiscoverableMCPServer, DiscoverMCPServersResponse } from "@/components/
|
|||
import { mcpLogoImg } from "./CreateMCPServer";
|
||||
import { resolveLogoSrc } from "@/lib/assetPaths";
|
||||
|
||||
const ALL_CATEGORIES = "All";
|
||||
|
||||
interface MCPDiscoveryProps {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
|
|
@ -49,7 +51,7 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedCategory, setSelectedCategory] = useState("All");
|
||||
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORIES);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && accessToken) {
|
||||
|
|
@ -72,13 +74,13 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
|
|||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
setSearchQuery("");
|
||||
setSelectedCategory("All");
|
||||
setSelectedCategory(ALL_CATEGORIES);
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
const filteredServers = useMemo(() => {
|
||||
let result = servers;
|
||||
if (selectedCategory !== "All") {
|
||||
if (selectedCategory !== ALL_CATEGORIES) {
|
||||
result = result.filter((s) => s.category === selectedCategory);
|
||||
}
|
||||
if (searchQuery.trim()) {
|
||||
|
|
@ -125,7 +127,7 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
|
|||
<div className="max-h-[70vh] overflow-y-auto">
|
||||
{/* Filter pills */}
|
||||
<div className="mb-3 flex flex-wrap gap-1.5">
|
||||
{["All", ...categories].map((cat) => {
|
||||
{[ALL_CATEGORIES, ...categories].map((cat) => {
|
||||
const isSelected = selectedCategory === cat;
|
||||
return (
|
||||
<Button
|
||||
|
|
@ -134,7 +136,7 @@ const MCPDiscovery: React.FC<MCPDiscoveryProps> = ({
|
|||
variant={isSelected ? "default" : "outline"}
|
||||
onClick={() => setSelectedCategory(cat)}
|
||||
>
|
||||
{cat === "All" ? t("mcpServers.discovery.allCategories") : cat}
|
||||
{cat === ALL_CATEGORIES ? t("mcpServers.discovery.allCategories") : cat}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ import {
|
|||
textControl,
|
||||
} from "./mcpFieldRules";
|
||||
import { getSecureItem, setSecureItem } from "@/utils/secureStorage";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface MCPServerEditProps {
|
||||
mcpServer: MCPServer;
|
||||
|
|
@ -1095,7 +1096,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
|
|||
{...textControl(control)}
|
||||
rows={6}
|
||||
className="rounded-lg border-border focus:border-info focus:ring-ring font-mono text-sm"
|
||||
placeholder={`{\n \"KEY\": \"value\"\n}`}
|
||||
placeholder={codeSamples.mcpServers.envJson}
|
||||
/>
|
||||
)}
|
||||
</MountedFormField>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ import { cn } from "@/lib/cva.config";
|
|||
import UserEnvVarsModal from "./UserEnvVarsModal";
|
||||
import { listMCPUserEnvVarStatus } from "@/components/networking";
|
||||
|
||||
const ALL_FILTER_VALUE = "all";
|
||||
|
||||
export type SortKey = "created_desc" | "updated_desc" | "name_asc" | "health";
|
||||
|
||||
const SORT_OPTIONS: { value: SortKey; labelKey: ParseKeys }[] = [
|
||||
|
|
@ -628,7 +630,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID, i
|
|||
<Select
|
||||
items={teamSelectItems}
|
||||
value={selectedTeam}
|
||||
onValueChange={(v: string | null) => handleTeamChange(v ?? "all")}
|
||||
onValueChange={(v: string | null) => handleTeamChange(v ?? ALL_FILTER_VALUE)}
|
||||
>
|
||||
<SelectTrigger className="w-55" aria-label={t("mcpServers.servers.filters.team.label")}>
|
||||
<SelectValue />
|
||||
|
|
@ -663,7 +665,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID, i
|
|||
<Select
|
||||
items={accessGroupSelectItems}
|
||||
value={selectedMcpAccessGroup}
|
||||
onValueChange={(v: string | null) => handleMcpAccessGroupChange(v ?? "all")}
|
||||
onValueChange={(v: string | null) => handleMcpAccessGroupChange(v ?? ALL_FILTER_VALUE)}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="w-55"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
|
|||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const memorySchema = z.object({
|
||||
key: z.string().min(1, { error: () => i18n.t("memory.editModal.errors.keyRequired") }),
|
||||
|
|
@ -131,7 +132,13 @@ export const MemoryEditModal: React.FC<MemoryEditModalProps> = ({ open, mode, in
|
|||
)}
|
||||
>
|
||||
{({ ref, ...field }) => (
|
||||
<Textarea {...field} ref={ref} rows={4} placeholder='{"tags": ["example"]}' className="font-mono" />
|
||||
<Textarea
|
||||
{...field}
|
||||
ref={ref}
|
||||
rows={4}
|
||||
placeholder={codeSamples.memory.metadataJson}
|
||||
className="font-mono"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</FieldGroup>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
|
||||
const SCALES = ["linear", "log"] as const;
|
||||
const GRANULARITIES = ["day", "week"] as const;
|
||||
const METRICS = ["requests", "spend", "tokens"] as const;
|
||||
const GRANULARITY_LABELS: Record<Granularity, ParseKeys> = {
|
||||
day: "modelInsights.modelInsightsView.granularity.day",
|
||||
week: "modelInsights.modelInsightsView.granularity.week",
|
||||
|
|
@ -171,7 +172,7 @@ export default function ModelInsightsView({ accessToken }: { accessToken: string
|
|||
<div className="flex items-center gap-3">
|
||||
<Tabs value={metric} onValueChange={(value) => setMetric(value as Metric)}>
|
||||
<TabsList>
|
||||
{(["requests", "spend", "tokens"] as const).map((value) => (
|
||||
{METRICS.map((value) => (
|
||||
<TabsTrigger key={value} value={value} className="capitalize">
|
||||
{t(METRIC_LABELS[value])}
|
||||
</TabsTrigger>
|
||||
|
|
|
|||
|
|
@ -127,7 +127,8 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
<Tooltip>
|
||||
<TooltipTrigger render={<span className="flex items-center text-info" />}>
|
||||
<Clock className="mr-1 size-3" />
|
||||
{(totalLatency / 1000).toFixed(2)}s
|
||||
{(totalLatency / 1000).toFixed(2)}
|
||||
{t("playground.chatUi.a2aMetrics.secondsSuffix")}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("playground.chatUi.a2aMetrics.totalLatency")}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { toast } from "@/lib/toast";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface SessionManagementProps {
|
||||
endpointType: string | null;
|
||||
|
|
@ -120,15 +121,7 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
|
|||
<div className="text-xs">
|
||||
<div className="mb-1">{t("playground.chatUi.sessionManagement.copyToContinue")}</div>
|
||||
<div className="bg-gray-800 text-gray-100 p-2 rounded-sm font-mono text-xs whitespace-pre-wrap">
|
||||
{`curl -X POST "your-proxy-url/v1/responses" \\
|
||||
-H "Authorization: Bearer your-api-key" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"model": "your-model",
|
||||
"input": [{"role": "user", "content": "your message", "type": "message"}],
|
||||
"previous_response_id": "${responsesSessionId}",
|
||||
"stream": true
|
||||
}'`}
|
||||
{codeSamples.playground.responsesSessionCurl(responsesSessionId)}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import SystemOneForm from "./SystemOneForm";
|
|||
import OpenAIDecisionsForm from "./OpenAIDecisionsForm";
|
||||
import { validateSystemOnePayload, validateOpenAIDecisionsPayload, errorCount } from "./lib/validatePayload";
|
||||
import { useDecisionModels, type ApiKeySource } from "./useDecisionModels";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
interface SystemOneUIProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -272,7 +273,7 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
|
|||
<div className="flex flex-wrap items-center justify-between gap-2 border-b px-3 py-2">
|
||||
<TabsList aria-label={t("playground.systemOne.systemOneUi.requestEditorView")}>
|
||||
<TabsTrigger value="form">{t("playground.systemOne.systemOneUi.formTab")}</TabsTrigger>
|
||||
<TabsTrigger value="curl">cURL</TabsTrigger>
|
||||
<TabsTrigger value="curl">{productNames.curl}</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex items-center gap-2">
|
||||
<ExamplesMenu onPick={handlePresetPick} />
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const readForm = (raw: string): FormPayload | undefined => {
|
|||
return formPayloadInInputOrder.safeParse(json.value).data;
|
||||
};
|
||||
|
||||
export const freeName = (taken: readonly string[], prefix: string): string =>
|
||||
export const freeName = (taken: readonly string[], prefix: "option" | "question"): string =>
|
||||
Array.from({ length: taken.length + 1 }, (_, index) => `${prefix}_${index + 1}`).find(
|
||||
(name) => !taken.includes(name),
|
||||
) ?? `${prefix}_${taken.length + 1}`;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import {
|
|||
import { COMPLIANCE_FRAMEWORK_LABEL_KEYS, getComplianceDatasetPrompts, getFrameworks } from "@/data/compliancePrompts";
|
||||
import type { CompliancePrompt } from "@/data/compliancePrompts";
|
||||
|
||||
const DEFAULT_VERSION_STATUS = "draft";
|
||||
|
||||
const TEST_SOURCE_QUICK = "quick_chat";
|
||||
const TEST_SOURCE_ALL = "__all__";
|
||||
|
||||
|
|
@ -961,7 +963,7 @@ const PipelineTestPanel: React.FC<PipelineTestPanelProps> = ({ pipeline, accessT
|
|||
padding: "0 4px",
|
||||
}}
|
||||
>
|
||||
x
|
||||
{t("policies.flowBuilder.testPanel.close")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1198,7 +1200,9 @@ const PipelineTestPanel: React.FC<PipelineTestPanelProps> = ({ pipeline, accessT
|
|||
}}
|
||||
>
|
||||
{complianceResults.map((entry, i) => {
|
||||
const actual = entry.result?.terminal_action ?? (entry.error ? "error" : "—");
|
||||
const actual =
|
||||
entry.result?.terminal_action ??
|
||||
(entry.error ? t("policies.flowBuilder.testPanel.compliance.errorResult") : "—");
|
||||
const matchStyle = entry.matched
|
||||
? { bg: "color-mix(in oklab, var(--color-success) 10%, transparent)", color: "var(--color-success)" }
|
||||
: {
|
||||
|
|
@ -1409,7 +1413,7 @@ const PolicyVersionsSidebar: React.FC<PolicyVersionsSidebarProps> = ({
|
|||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
{v.version_status ?? "draft"}
|
||||
{v.version_status ?? DEFAULT_VERSION_STATUS}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import { userDetailHref } from "@/utils/entityLinks";
|
|||
|
||||
import { extractModel, getProviderFromModelHub, ModelGroupInfo } from "./prompt_utils";
|
||||
|
||||
const DEFAULT_PROMPT_ENVIRONMENT = "development";
|
||||
|
||||
const ENVIRONMENT_TONE: Record<string, StatusTone> = {
|
||||
production: "error",
|
||||
staging: "warning",
|
||||
|
|
@ -99,7 +101,7 @@ function PromptRowActions({ prompt, isAdmin, onDeleteClick }: PromptRowActionsPr
|
|||
onDeleteClick?.(
|
||||
prompt.prompt_id,
|
||||
prompt.prompt_id || t("prompts.table.rowActions.unknownPrompt"),
|
||||
prompt.environment || "development",
|
||||
prompt.environment || DEFAULT_PROMPT_ENVIRONMENT,
|
||||
)
|
||||
}
|
||||
>
|
||||
|
|
@ -142,7 +144,7 @@ export const getPromptTableColumns = ({
|
|||
className="max-w-60"
|
||||
onClick={
|
||||
onPromptClick
|
||||
? () => onPromptClick(row.original.prompt_id, row.original.environment || "development")
|
||||
? () => onPromptClick(row.original.prompt_id, row.original.environment || DEFAULT_PROMPT_ENVIRONMENT)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
|
@ -184,7 +186,7 @@ export const getPromptTableColumns = ({
|
|||
size: 130,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const environment = row.original.environment || "development";
|
||||
const environment = row.original.environment || DEFAULT_PROMPT_ENVIRONMENT;
|
||||
return <StatusBadge tone={ENVIRONMENT_TONE[environment] ?? "neutral"} label={environment} />;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import PublishModal from "./PublishModal";
|
|||
import DotpromptViewTab from "./DotpromptViewTab";
|
||||
import VersionHistorySidePanel from "./VersionHistorySidePanel";
|
||||
|
||||
const versionedPromptId = (promptId: string, version: number) => `${promptId}.v${version}`;
|
||||
|
||||
const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess, accessToken, initialPromptData }) => {
|
||||
const { t } = useTranslation();
|
||||
const getInitialPrompt = (): PromptType => {
|
||||
|
|
@ -283,7 +285,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
const loadedPrompt = parseExistingPrompt(response);
|
||||
setPrompt({ ...loadedPrompt, environment: env });
|
||||
const versionNum = response.prompt_spec.version || 1;
|
||||
setActiveVersionId(`${response.prompt_spec.prompt_id}.v${versionNum}`);
|
||||
setActiveVersionId(versionedPromptId(response.prompt_spec.prompt_id, versionNum));
|
||||
}
|
||||
} catch {
|
||||
// No version in this environment yet — keep current content, just change env
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import type { ParseKeys } from "i18next";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
export interface RequestTagMapping {
|
||||
header: "baggage";
|
||||
|
|
@ -111,7 +112,7 @@ export function RequestTagMappingsEditor({ value, onChange, disabled = false }:
|
|||
{t("routerSettings.requestTagMappings.baggageKey")}
|
||||
<Input
|
||||
aria-label={t("routerSettings.requestTagMappings.baggageKeyAriaLabel", { number: index + 1 })}
|
||||
placeholder="service"
|
||||
placeholder={codeSamples.routerSettings.baggageKey}
|
||||
value={rule.key}
|
||||
onChange={(event) => update(index, { key: event.target.value })}
|
||||
/>
|
||||
|
|
@ -145,13 +146,16 @@ export function RequestTagMappingsEditor({ value, onChange, disabled = false }:
|
|||
{t("routerSettings.requestTagMappings.tagPrefix")}
|
||||
<Input
|
||||
aria-label={t("routerSettings.requestTagMappings.tagPrefixAriaLabel", { number: index + 1 })}
|
||||
placeholder="app:"
|
||||
placeholder={codeSamples.routerSettings.tagPrefix}
|
||||
value={rule.tag_prefix ?? ""}
|
||||
onChange={(event) => update(index, { tag_prefix: event.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{rule.key}=x → {rule.tag_prefix}x
|
||||
{rule.key}
|
||||
{codeSamples.routerSettings.anyValuePreviewSeparator}
|
||||
{rule.tag_prefix}
|
||||
{codeSamples.routerSettings.anyValuePreviewValue}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import { isValidSemanticVersion } from "@/components/claude_code_plugins/helpers
|
|||
import { isPendingSkill, type SkillHubSkill, type SkillMutationResponse } from "@/components/claude_code_plugins/types";
|
||||
import { buildSkillImport, buildSkillMetadata, skillFormValues } from "@/components/claude_code_plugins/skillForm";
|
||||
|
||||
const EDIT_SKILL_SOURCES = ["metadata", "upload"] as const;
|
||||
const NEW_SKILL_SOURCES = ["upload", "import"] as const;
|
||||
|
||||
interface AddPluginFormProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
|
|
@ -119,7 +122,7 @@ export default function AddPluginForm({
|
|||
<FormField control={form.control} name="mode" label={t("skills.addForm.labels.source")}>
|
||||
{({ value }) => (
|
||||
<RadioGroup value={value} onValueChange={changeMode} className="flex gap-5">
|
||||
{(skill ? (["metadata", "upload"] as const) : (["upload", "import"] as const)).map((option) => (
|
||||
{(skill ? EDIT_SKILL_SOURCES : NEW_SKILL_SOURCES).map((option) => (
|
||||
<label key={option} className="flex items-center gap-2 text-sm">
|
||||
<RadioGroupItem value={option} />
|
||||
{t(`skills.addForm.modes.${option}`)}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export function ProviderSpendBreakdown({ rows }: { rows: readonly ProviderSpendD
|
|||
<div
|
||||
key={row.provider}
|
||||
data-testid="provider-share-segment"
|
||||
title={`${row.provider || "unknown"} · ${shareOf(row.spend).toFixed(1)}%`}
|
||||
title={`${row.provider || t("usage.providerSpend.unknownProvider")} · ${shareOf(row.spend).toFixed(1)}%`}
|
||||
className="h-full first:rounded-l-full last:rounded-r-full"
|
||||
style={{ width: `${shareOf(row.spend)}%`, backgroundColor: colorOf(index) }}
|
||||
/>
|
||||
|
|
@ -119,7 +119,7 @@ export function ProviderSpendBreakdown({ rows }: { rows: readonly ProviderSpendD
|
|||
{visible.slice(0, 6).map((row, index) => (
|
||||
<span key={row.provider} className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<span className="size-2 rounded-[2px]" style={{ backgroundColor: colorOf(index) }} />
|
||||
<span className="text-foreground">{row.provider || "unknown"}</span>
|
||||
<span className="text-foreground">{row.provider || t("usage.providerSpend.unknownProvider")}</span>
|
||||
<span className="tabular-nums">{shareOf(row.spend).toFixed(1)}%</span>
|
||||
</span>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ import { useTagSummary } from "@/app/(dashboard)/hooks/tags/useTagSummary";
|
|||
import { Panel } from "./overview/Primitives";
|
||||
import UsageOverview from "./overview/UsageOverview";
|
||||
|
||||
const GATEWAY_ENDPOINT_COLORS = ["#2b3fd6", "#ef4444"];
|
||||
|
||||
interface UsagePageProps {
|
||||
teams: Team[];
|
||||
organizations: Organization[];
|
||||
|
|
@ -585,7 +587,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations, teamsLoadin
|
|||
t("usage.pageView.gatewayByEndpoint.successful"),
|
||||
t("usage.pageView.gatewayByEndpoint.failed"),
|
||||
]}
|
||||
colors={["#2b3fd6", "#ef4444"]}
|
||||
colors={GATEWAY_ENDPOINT_COLORS}
|
||||
xKey="route"
|
||||
format={(value) => value.toLocaleString()}
|
||||
className="h-64"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import {
|
|||
type DefaultUserSettingsSubmitValues,
|
||||
} from "./schema";
|
||||
|
||||
const DEFAULT_TEAM_ROLE = "user";
|
||||
|
||||
const NO_RESET = "never";
|
||||
|
||||
const BUDGET_DURATION_OPTIONS: readonly { value: string; labelKey: ParseKeys }[] = [
|
||||
|
|
@ -162,7 +164,11 @@ const TeamsField = ({ control }: { control: SettingsControl }) => {
|
|||
label={t("users.defaultSettings.teams.role")}
|
||||
>
|
||||
{({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => (
|
||||
<Select items={teamRoleItems} value={value} onValueChange={(selected) => onChange(selected ?? "user")}>
|
||||
<Select
|
||||
items={teamRoleItems}
|
||||
value={value}
|
||||
onValueChange={(selected) => onChange(selected ?? DEFAULT_TEAM_ROLE)}
|
||||
>
|
||||
<SelectTrigger
|
||||
id={id}
|
||||
className="w-full"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
DataTable,
|
||||
DataTableFilterDrawer,
|
||||
DataTableFilterField,
|
||||
type FilterDraft,
|
||||
DataTableToolbar,
|
||||
} from "@/components/shared/DataTable";
|
||||
import { SearchSelect } from "@/components/shared/SearchSelect";
|
||||
|
|
@ -183,7 +184,7 @@ export function UsersTable({
|
|||
title={t("users.table.filters.title")}
|
||||
description={t("users.table.filters.description")}
|
||||
>
|
||||
{({ get, set }) => (
|
||||
{({ get, set }: FilterDraft<"sso_user_id" | "team" | "user_id" | "user_role">) => (
|
||||
<>
|
||||
<DataTableFilterField label={t("users.table.filters.userId")}>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
|
|||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import { SearchSelect } from "@/components/shared/SearchSelect";
|
||||
import { credentialOptions } from "@/components/shared/credentialOptions";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const EMBEDDING_MODEL_RENAME_PROVIDERS = new Set(["milvus", "valkey", "mongodb", "opensearch"]);
|
||||
|
||||
|
|
@ -521,7 +522,7 @@ const VectorStoreForm: React.FC<VectorStoreFormProps> = ({
|
|||
rows={4}
|
||||
value={metadataJson}
|
||||
onChange={(event) => setMetadataJson(event.target.value)}
|
||||
placeholder='{"key": "value"}'
|
||||
placeholder={codeSamples.vectorStores.metadataJson}
|
||||
/>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
|
|||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import { SearchSelect } from "@/components/shared/SearchSelect";
|
||||
import { credentialOptions } from "@/components/shared/credentialOptions";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const DEFAULT_VECTOR_STORE_PROVIDER = "bedrock";
|
||||
|
||||
interface VectorStoreInfoViewProps {
|
||||
vectorStoreId: string;
|
||||
|
|
@ -353,7 +356,7 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
rows={4}
|
||||
value={metadataString}
|
||||
onChange={(event) => setMetadataString(event.target.value)}
|
||||
placeholder='{"key": "value"}'
|
||||
placeholder={codeSamples.vectorStores.metadataJson}
|
||||
/>
|
||||
</div>
|
||||
</FieldGroup>
|
||||
|
|
@ -394,7 +397,7 @@ const VectorStoreInfoView: React.FC<VectorStoreInfoViewProps> = ({
|
|||
<p className="font-medium">{t("vectorStores.info.details.provider")}</p>
|
||||
<div className="flex items-center space-x-2 mt-1">
|
||||
{(() => {
|
||||
const provider = vectorStoreDetails.custom_llm_provider || "bedrock";
|
||||
const provider = vectorStoreDetails.custom_llm_provider || DEFAULT_VECTOR_STORE_PROVIDER;
|
||||
const { displayName, logo } = getVectorStoreProviderLogoAndName(provider);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
DataTable,
|
||||
DataTableFilterDrawer,
|
||||
DataTableFilterField,
|
||||
type FilterDraft,
|
||||
DataTableToolbar,
|
||||
} from "@/components/shared/DataTable";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -560,7 +561,7 @@ const WorkflowRuns: React.FC<WorkflowRunsProps> = ({ accessToken }) => {
|
|||
title={t("workflows.filters.title")}
|
||||
description={t("workflows.filters.description")}
|
||||
>
|
||||
{({ get, set }) => (
|
||||
{({ get, set }: FilterDraft<"status" | "workflow_type">) => (
|
||||
<>
|
||||
<DataTableFilterField label={t("workflows.filters.status")}>
|
||||
<Select
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { Trans, useTranslation } from "react-i18next";
|
|||
import { z } from "zod";
|
||||
import { useWorker } from "@/hooks/useWorker";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
const loginSchema = z.object({
|
||||
username: z.string().min(1, { error: () => i18n.t("login.schema.usernameRequired") }),
|
||||
|
|
@ -211,7 +212,7 @@ function LoginPageContent() {
|
|||
<CardContent>
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-semibold text-foreground">🚅 LiteLLM</h2>
|
||||
<h2 className="text-3xl font-semibold text-foreground">{productNames.liteLLMWithLogo}</h2>
|
||||
</div>
|
||||
|
||||
<Alert variant="warning">
|
||||
|
|
@ -238,7 +239,7 @@ function LoginPageContent() {
|
|||
<TooltipProvider>
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl font-semibold text-foreground">🚅 LiteLLM</h2>
|
||||
<h2 className="text-3xl font-semibold text-foreground">{productNames.liteLLMWithLogo}</h2>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Suspense, useEffect, useMemo } from "react";
|
|||
import { useSearchParams } from "next/navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getSecureItem, setSecureItem } from "@/utils/secureStorage";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
// Written to sessionStorage so the admin hook (useMcpOAuthFlow), the user hook
|
||||
// (useUserMcpOAuthFlow), and the tools re-auth hook (useToolsOAuthFlow) can each
|
||||
|
|
@ -81,7 +82,7 @@ const McpOAuthCallbackContent = () => {
|
|||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-muted p-6">
|
||||
<div className="max-w-lg w-full rounded-lg bg-card shadow-md p-8 text-center space-y-4">
|
||||
<h1 className="text-xl font-semibold text-foreground">LiteLLM MCP OAuth</h1>
|
||||
<h1 className="text-xl font-semibold text-foreground">{productNames.liteLLMMcpOAuth}</h1>
|
||||
<p className="text-sm text-foreground">{t("mcpOAuthCallback.authorizationComplete")}</p>
|
||||
<p className="text-xs text-muted-foreground">{t("mcpOAuthCallback.manualCloseHint")}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
|||
import { cn } from "@/lib/cva.config";
|
||||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
type OnboardingFormBodyProps = {
|
||||
variant: "signup" | "reset_password";
|
||||
|
|
@ -41,7 +42,7 @@ export function OnboardingFormBody({ variant, userEmail, isPending, claimError,
|
|||
<div className="mx-auto w-full max-w-md mt-10">
|
||||
<Card>
|
||||
<CardContent>
|
||||
<h5 className="text-center mb-5 text-base font-semibold text-foreground">🚅 LiteLLM</h5>
|
||||
<h5 className="text-center mb-5 text-base font-semibold text-foreground">{productNames.liteLLMWithLogo}</h5>
|
||||
<h3 className="text-2xl font-semibold text-foreground">{actionLabel}</h3>
|
||||
<p className="text-sm text-foreground">
|
||||
{isResetPassword ? t("onboarding.formBody.resetDescription") : t("onboarding.formBody.signUpDescription")}
|
||||
|
|
|
|||
|
|
@ -833,8 +833,6 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
|
|||
<div className="flex flex-wrap gap-2">
|
||||
{(() => {
|
||||
const capabilities = getModelCapabilities(selectedModel);
|
||||
const colors = ["green", "blue", "purple", "orange", "red", "yellow"];
|
||||
|
||||
if (capabilities.length === 0) {
|
||||
return (
|
||||
<p className="text-muted-foreground">{t("aiHub.modelHubTable.modelDialog.noCapabilities")}</p>
|
||||
|
|
|
|||
|
|
@ -274,7 +274,12 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
|
||||
const userIdField = (
|
||||
<>
|
||||
<FormField control={form.control} name="supply_user_id" label="Supply custom User ID" orientation="horizontal">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="supply_user_id"
|
||||
label={t("users.createUser.fields.supplyUserId")}
|
||||
orientation="horizontal"
|
||||
>
|
||||
{({ id, value, onChange, onBlur }) => (
|
||||
<Checkbox
|
||||
id={id}
|
||||
|
|
@ -291,8 +296,8 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
|
|||
<FormField
|
||||
control={form.control}
|
||||
name="user_id"
|
||||
label="User ID"
|
||||
description="A unique ID for this user. Leave the checkbox unchecked to generate one automatically."
|
||||
label={t("users.createUser.fields.userId")}
|
||||
description={t("users.createUser.fields.userIdDescription")}
|
||||
>
|
||||
{({ ref, value, ...control }) => <Input {...control} ref={ref} value={value ?? ""} />}
|
||||
</FormField>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import {
|
|||
isPositiveWholePtuCount,
|
||||
ptuWindowIsOrdered,
|
||||
} from "../utils/ptuValidation";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface PtuEditField {
|
||||
name: string;
|
||||
|
|
@ -811,7 +812,7 @@ const ModelInfoEditForm: React.FC<ModelInfoEditFormProps> = ({
|
|||
<Textarea
|
||||
{...control}
|
||||
rows={4}
|
||||
placeholder={'{"gpt-4": 100, "claude-v1": 200}'}
|
||||
placeholder={codeSamples.models.perModelLimitsJson}
|
||||
defaultValue={JSON.stringify(modelData.model_info, null, 2)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -841,7 +842,7 @@ const ModelInfoEditForm: React.FC<ModelInfoEditFormProps> = ({
|
|||
aria-label={t("modelInfoEditForm.fields.litellmParams.label")}
|
||||
value={(value as string) ?? ""}
|
||||
rows={4}
|
||||
placeholder={'{\n "drop_params": true\n}'}
|
||||
placeholder={codeSamples.models.litellmParamsJson}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"
|
|||
import { uiHref } from "@/utils/uiHref";
|
||||
import { cn } from "@/lib/cva.config";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
const GATEWAY = "ai-gateway";
|
||||
const CHAT = "chat";
|
||||
|
|
@ -123,7 +124,7 @@ export default function ViewSwitcher({ className, contentClassName }: ViewSwitch
|
|||
label: (
|
||||
<div className="flex w-full items-center justify-between gap-6 py-0.5">
|
||||
<span className="flex flex-col">
|
||||
<span className="font-medium">Moyai</span>{" "}
|
||||
<span className="font-medium">{productNames.moyai}</span>{" "}
|
||||
<span className="text-xs leading-snug text-muted-foreground">
|
||||
{t("shell.viewSwitcher.moyaiDescription")}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||
import CyberArkEmptyPlaceholder from "./CyberArkEmptyPlaceholder";
|
||||
import EditCyberArkModal from "./EditCyberArkModal";
|
||||
import { FIELD_LABELS, SENSITIVE_FIELDS } from "./constants";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
function detectAuthMethod(values: Record<string, unknown>, t: TFunction): string {
|
||||
if (values.cyberark_api_key) return t("adminPanel.cyberArk.authMethods.apiKey");
|
||||
|
|
@ -145,7 +146,7 @@ export default function CyberArk() {
|
|||
<KeyRound className="size-6 text-muted-foreground" />
|
||||
<div>
|
||||
<CardTitle>
|
||||
<h3>CyberArk Conjur</h3>
|
||||
<h3>{productNames.cyberArkConjur}</h3>
|
||||
</CardTitle>
|
||||
<CardDescription>{t("adminPanel.cyberArk.description")}</CardDescription>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||
import EditHashicorpVaultModal from "./EditHashicorpVaultModal";
|
||||
import HashicorpVaultEmptyPlaceholder from "./HashicorpVaultEmptyPlaceholder";
|
||||
import { FIELD_LABELS, SENSITIVE_FIELDS } from "./constants";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
function detectAuthMethod(values: Record<string, unknown>, t: TFunction): string {
|
||||
if (values.approle_role_id || values.approle_secret_id) return "AppRole";
|
||||
|
|
@ -142,7 +143,7 @@ export default function HashicorpVault() {
|
|||
<KeyRound className="size-6 text-muted-foreground" />
|
||||
<div>
|
||||
<CardTitle>
|
||||
<h3>Hashicorp Vault</h3>
|
||||
<h3>{productNames.hashicorpVault}</h3>
|
||||
</CardTitle>
|
||||
<CardDescription>{t("adminPanel.hashicorpVault.description")}</CardDescription>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import {
|
|||
storedValuesToForm,
|
||||
ToolSearchFormValues,
|
||||
} from "./toolSearchForm";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface MCPToolSearchSettingsProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -232,7 +233,7 @@ export default function MCPToolSearchSettings({ accessToken }: MCPToolSearchSett
|
|||
id={id}
|
||||
ref={ref}
|
||||
value={value}
|
||||
placeholder={"my_server-get_rates\nmy_server-list_accounts"}
|
||||
placeholder={codeSamples.mcpServers.toolSearchToolNames}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
onBlur={onBlur}
|
||||
disabled={isUpdating}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
|||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import { pluginSchema, type PluginFormValues } from "./schema";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
const INLINE_CODE_CLASS = "rounded-sm bg-muted px-1 py-0.5 font-mono text-xs";
|
||||
|
||||
|
|
@ -209,7 +210,7 @@ export default function PluginSettings() {
|
|||
name="display_name"
|
||||
label={t("adminPanel.pluginSettings.modal.fields.displayName.label")}
|
||||
>
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} placeholder="Agent Control Plane" />}
|
||||
{({ ref, ...field }) => <Input {...field} ref={ref} placeholder={productNames.agentControlPlane} />}
|
||||
</FormField>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { Button } from "@/components/ui/button";
|
|||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
|
||||
const pageVisibilityCheckboxId = (page: string) => `page-visibility-${page}`;
|
||||
|
||||
interface PageVisibilitySettingsProps {
|
||||
enabledPagesInternalUsers: string[] | null | undefined;
|
||||
enabledPagesPropertyDescription?: string;
|
||||
|
|
@ -89,7 +91,7 @@ export default function PageVisibilitySettings({
|
|||
</legend>
|
||||
<div className="ml-4 space-y-2">
|
||||
{pages.map((page) => {
|
||||
const checkboxId = `page-visibility-${page.page}`;
|
||||
const checkboxId = pageVisibilityCheckboxId(page.page);
|
||||
return (
|
||||
<label key={page.page} htmlFor={checkboxId} className="flex cursor-pointer items-start gap-2">
|
||||
<Checkbox
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||
import { Label } from "@/components/ui/label";
|
||||
import PageVisibilitySettings from "./PageVisibilitySettings";
|
||||
import BackgroundChatSettings from "./BackgroundChatSettings";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
interface SettingRowProps {
|
||||
ariaLabel: string;
|
||||
|
|
@ -453,7 +454,7 @@ export default function UISettings() {
|
|||
<Separator />
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-foreground">Moyai</p>
|
||||
<p className="text-sm font-medium text-foreground">{productNames.moyai}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{moyaiUrl
|
||||
? t("adminPanel.uiSettings.moyai.connectedTo", { url: moyaiUrl })
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { ChevronsUpDown, Crown, IdCard, KeyRound, LogOut, Mail, ShieldCheck } fr
|
|||
import { useRouter } from "next/navigation";
|
||||
import React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
const RELEASE_NOTES_URL = "https://docs.litellm.ai/release_notes";
|
||||
|
||||
|
|
@ -193,7 +194,7 @@ const SidebarAccountMenu: React.FC<SidebarAccountMenuProps> = ({ onLogout, colla
|
|||
data-testid="sidebar-account-menu-panel"
|
||||
>
|
||||
<div className="flex items-center gap-2 border-b border-border px-3 py-3">
|
||||
<span className="text-[15px] font-bold tracking-tight text-foreground">LiteLLM</span>
|
||||
<span className="text-[15px] font-bold tracking-tight text-foreground">{productNames.liteLLM}</span>
|
||||
{!disableBouncingIcon && (
|
||||
<span
|
||||
className="animate-bounce text-lg leading-none"
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ import { teamCreateCall } from "./networking";
|
|||
import { normalizeTeamModelSelection } from "./team/teamModelAccess";
|
||||
import { ModelSelect } from "./ModelSelect/ModelSelect";
|
||||
import { StorePromptsToggle } from "./common_components/StorePromptsToggle";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const SUPPRESSED_BY_DESCRIPTION = "";
|
||||
|
||||
|
|
@ -1010,7 +1011,7 @@ const Teams: React.FC<TeamProps> = ({ accessToken, userID, userRole, isViewOnly
|
|||
ref={ref}
|
||||
value={value ?? ""}
|
||||
rows={4}
|
||||
placeholder='{"namespace": "admin", "mount": "secret", "path_prefix": "litellm"}'
|
||||
placeholder={codeSamples.teams.vaultConfigJson}
|
||||
disabled={!premiumUser}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
DataTable,
|
||||
DataTableFilterDrawer,
|
||||
DataTableFilterField,
|
||||
type FilterDraft,
|
||||
DataTableToolbar,
|
||||
} from "@/components/shared/DataTable";
|
||||
import { SearchSelect } from "@/components/shared/SearchSelect";
|
||||
|
|
@ -206,7 +207,7 @@ export function TeamsTable({ userRole, userID, onSelectTeam, onEditTeam, onDelet
|
|||
title={t("teams.table.filters.title")}
|
||||
description={t("teams.table.filters.description")}
|
||||
>
|
||||
{({ get, set }) => (
|
||||
{({ get, set }: FilterDraft<"org_id" | "team_id" | "alias">) => (
|
||||
<>
|
||||
<DataTableFilterField label={t("teams.table.filters.organization")}>
|
||||
<SearchSelect
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { useTranslation } from "react-i18next";
|
|||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const oppositeTheme = (isDark: boolean) => (isDark ? "light" : "dark");
|
||||
|
||||
const ThemeToggle: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { setTheme, resolvedTheme } = useTheme();
|
||||
|
|
@ -20,7 +22,7 @@ const ThemeToggle: React.FC = () => {
|
|||
aria-label={label}
|
||||
title={label}
|
||||
className="text-muted-foreground"
|
||||
onClick={() => setTheme(isDark ? "light" : "dark")}
|
||||
onClick={() => setTheme(oppositeTheme(isDark))}
|
||||
>
|
||||
{isDark ? <Moon /> : <Sun />}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useZodForm } from "@/lib/forms/useZodForm";
|
|||
import i18n from "@/i18n/i18n";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { getSSOSettings, updateSSOSettings } from "./networking";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface UIAccessControlFormProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -211,7 +212,9 @@ const UIAccessControlForm: React.FC<UIAccessControlFormProps> = ({ accessToken,
|
|||
t("adminPanel.uiAccessControl.fields.jwtFieldHint"),
|
||||
)}
|
||||
>
|
||||
{({ ref, value, ...field }) => <Input {...field} ref={ref} value={value ?? ""} placeholder="groups" />}
|
||||
{({ ref, value, ...field }) => (
|
||||
<Input {...field} ref={ref} value={value ?? ""} placeholder={codeSamples.users.ssoGroupsClaim} />
|
||||
)}
|
||||
</FormField>
|
||||
</FieldGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ interface KeySummaryStripProps {
|
|||
const KeySummaryStrip: React.FC<KeySummaryStripProps> = ({ summary, loading, hidePromptCachingMetrics }) => {
|
||||
const { t } = useTranslation();
|
||||
const daily = useMemo(() => summarySeries(summary), [summary]);
|
||||
const trend = (dataKey: string) =>
|
||||
const trend = (dataKey: "spend" | "requests" | "tokens") =>
|
||||
loading || daily.length < 2 ? undefined : (
|
||||
<Sparkline data={daily} dataKey={dataKey} color={BRAND} className="h-12" />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
DataTable,
|
||||
DataTableFilterDrawer,
|
||||
DataTableFilterField,
|
||||
type FilterDraft,
|
||||
DataTableToolbar,
|
||||
usePersistedColumnVisibility,
|
||||
useUrlTableState,
|
||||
|
|
@ -298,7 +299,7 @@ export function VirtualKeysTable({ headerActions }: VirtualKeysTableProps) {
|
|||
title={t("apiKeys.virtualKeysTable.filters.title")}
|
||||
description={t("apiKeys.virtualKeysTable.filters.description")}
|
||||
>
|
||||
{({ get, set }) => (
|
||||
{({ get, set }: FilterDraft<"key_hash" | "org_id" | "status" | "team_id" | "user_id">) => (
|
||||
<>
|
||||
<DataTableFilterField label={t("apiKeys.virtualKeysTable.filters.team")}>
|
||||
<SearchSelect
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import type { ModelTopKeysResponse } from "./UsagePage/dailyActivityApi";
|
|||
import { DailyData, KeyMetricWithMetadata, ModelActivityData, TopModelData } from "./UsagePage/types";
|
||||
import { averageResponseTimeMs, formatResponseTime, valueFormatter } from "./UsagePage/utils/value_formatters";
|
||||
|
||||
const UNKNOWN_MODEL_NAME = "Unknown Model";
|
||||
|
||||
interface ActivityMetricsProps {
|
||||
modelMetrics: Record<string, ModelActivityData>;
|
||||
summaryMetrics?: ModelActivityData;
|
||||
|
|
@ -680,7 +682,7 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({
|
|||
}
|
||||
>
|
||||
<ModelSection
|
||||
modelName={modelName || "Unknown Model"}
|
||||
modelName={modelName || UNKNOWN_MODEL_NAME}
|
||||
metrics={metrics}
|
||||
hidePromptCachingMetrics={hidePromptCachingMetrics}
|
||||
fetchTopApiKeys={fetchTopApiKeys}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
isAllowanceExhausted,
|
||||
AUTO_ROUTER_CONTACT_URL,
|
||||
} from "./AutoRouterAvailability";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
export function ClassifierOption({
|
||||
value,
|
||||
|
|
@ -229,7 +230,13 @@ const AutoRouterClassifierTabs: React.FC<AutoRouterClassifierTabsProps> = ({
|
|||
description: t("addModel.autoRouterClassifierTabs.families.decisionModelDescription"),
|
||||
},
|
||||
...(switchyard
|
||||
? [{ value: "switchyard", label: "Switchyard", description: "Route by the conversation’s tool history." }]
|
||||
? [
|
||||
{
|
||||
value: "switchyard",
|
||||
label: productNames.switchyard,
|
||||
description: t("addModel.autoRouterClassifierTabs.families.switchyardDescription"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((option) => (
|
||||
<Label
|
||||
|
|
@ -262,10 +269,10 @@ const AutoRouterClassifierTabs: React.FC<AutoRouterClassifierTabsProps> = ({
|
|||
<Label htmlFor={`${id}-decision-model`}>{t("addModel.autoRouterClassifierTabs.decisionModelLabel")}</Label>
|
||||
<Select
|
||||
items={[
|
||||
{ value: "jev", label: "Jev" },
|
||||
{ value: "laya", label: "Laya" },
|
||||
{ value: "bespoke", label: "Bespoke Nimble" },
|
||||
{ value: "databricks", label: "Databricks" },
|
||||
{ value: "jev", label: productNames.jev },
|
||||
{ value: "laya", label: productNames.laya },
|
||||
{ value: "bespoke", label: productNames.bespokeNimble },
|
||||
{ value: "databricks", label: productNames.databricks },
|
||||
]}
|
||||
value={normalizeJevClassifierConfig(value.jev_classifier_config).provider}
|
||||
onValueChange={changeProvider}
|
||||
|
|
@ -274,10 +281,10 @@ const AutoRouterClassifierTabs: React.FC<AutoRouterClassifierTabsProps> = ({
|
|||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="jev">Jev</SelectItem>
|
||||
<SelectItem value="laya">Laya</SelectItem>
|
||||
<SelectItem value="bespoke">Bespoke Nimble</SelectItem>
|
||||
<SelectItem value="databricks">Databricks</SelectItem>
|
||||
<SelectItem value="jev">{productNames.jev}</SelectItem>
|
||||
<SelectItem value="laya">{productNames.laya}</SelectItem>
|
||||
<SelectItem value="bespoke">{productNames.bespokeNimble}</SelectItem>
|
||||
<SelectItem value="databricks">{productNames.databricks}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import { ROUTER_FAMILIES, ROUTER_GOALS, type FamilyPreference, type RouterGoal, type SolverModel } from "./auto_setup";
|
||||
|
||||
const TIER_ROLES = ["everyday", "hard"] as const;
|
||||
|
||||
type Props = {
|
||||
customized: boolean;
|
||||
hasSuggestions: boolean;
|
||||
|
|
@ -144,7 +146,7 @@ export default function AutoRouterSimpleSetup({
|
|||
)}
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{(["everyday", "hard"] as const).map((role) => {
|
||||
{TIER_ROLES.map((role) => {
|
||||
const chosen = role === "everyday" ? everyday : hard;
|
||||
const other = role === "everyday" ? hard : everyday;
|
||||
const choices = models.filter((model) => model.name !== other?.name);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||
import { Slider } from "@/components/ui/slider";
|
||||
import type { CustomDimensionRow } from "./custom_dimensions";
|
||||
|
||||
const MATCH_FIELDS = ["keywords", "patterns"] as const;
|
||||
|
||||
const SCORING_MODES = [
|
||||
{ value: "binary", labelKey: "addModel.customDimensionRows.scoringModes.binary" },
|
||||
{ value: "match_count", labelKey: "addModel.customDimensionRows.scoringModes.matchCount" },
|
||||
|
|
@ -87,7 +89,7 @@ export default function CustomDimensionRows({ rows, disabled, onChange, onWeight
|
|||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
{(["keywords", "patterns"] as const).map((field) => (
|
||||
{MATCH_FIELDS.map((field) => (
|
||||
<div key={field} className="min-w-0 space-y-1">
|
||||
<Label htmlFor={`${row.id}-${field}`}>
|
||||
{field === "keywords"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import { activeTierRows } from "./tier_rows";
|
|||
import { setTierModels } from "./tier_set_actions";
|
||||
import { tierRowLabel, setTierModelParam, setTierModelReasoningEffort } from "./complexity_router_tiers";
|
||||
|
||||
const CALIBRATION_ROLES = ["efficient", "capable"] as const;
|
||||
|
||||
interface Props {
|
||||
value: ComplexityRouterConfigValue;
|
||||
onChange: (value: ComplexityRouterConfigValue) => void;
|
||||
|
|
@ -401,7 +403,7 @@ const ForecastClassifierConfig = ({ value, onChange, modelOptions, effortOptions
|
|||
)}
|
||||
{!isCapability &&
|
||||
fuse.calibration &&
|
||||
(["efficient", "capable"] as const).map((role) => (
|
||||
CALIBRATION_ROLES.map((role) => (
|
||||
<CalibrationFields
|
||||
key={role}
|
||||
label={
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
ClassificationRubric,
|
||||
ComplexityTierLabels,
|
||||
} from "./ComplexityRouterConfig";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
export type OpeningPromptTierSource =
|
||||
| { kind: "custom"; tierRows: readonly TierRow[] }
|
||||
|
|
@ -252,7 +253,7 @@ const OpeningPromptEditor: React.FC<OpeningPromptEditorProps> = ({
|
|||
value={exampleDraft}
|
||||
onChange={(e) => setExampleDraft(e.target.value)}
|
||||
rows={6}
|
||||
placeholder={'- "what is the capital of France?" -> SIMPLE'}
|
||||
placeholder={codeSamples.models.openingPromptExample}
|
||||
aria-label={t("addModel.openingPromptEditor.examples.label")}
|
||||
className="mt-2 font-mono text-xs"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig";
|
||||
import { getReminderMarkersError, type ReminderMarkerPair } from "./build_complexity_router_config";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const ReminderMarkers: React.FC<{
|
||||
value: ComplexityRouterConfigValue;
|
||||
|
|
@ -42,7 +43,7 @@ const ReminderMarkers: React.FC<{
|
|||
</label>
|
||||
<Input
|
||||
id={`reminder-marker-${index}-open`}
|
||||
placeholder="<note>"
|
||||
placeholder={codeSamples.models.reminderMarkerOpen}
|
||||
value={marker.open}
|
||||
onChange={(event) => update(index, { open: event.target.value })}
|
||||
/>
|
||||
|
|
@ -53,7 +54,7 @@ const ReminderMarkers: React.FC<{
|
|||
</label>
|
||||
<Input
|
||||
id={`reminder-marker-${index}-close`}
|
||||
placeholder="</note>"
|
||||
placeholder={codeSamples.models.reminderMarkerClose}
|
||||
value={marker.close}
|
||||
onChange={(event) => update(index, { close: event.target.value })}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {
|
|||
parseToolSemantics,
|
||||
type SwitchyardConfig,
|
||||
} from "./switchyard_config";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface Props {
|
||||
value: SwitchyardConfig;
|
||||
|
|
@ -23,6 +25,15 @@ interface Props {
|
|||
onToolSemanticsError: (error: string | null) => void;
|
||||
}
|
||||
|
||||
const TIERS = ["capable", "efficient"] as const;
|
||||
|
||||
const LLM_NUMBER_FIELDS = [
|
||||
["base_threshold", "addModel.switchyardConfigFields.llmFields.capabilityThreshold"],
|
||||
["threshold_step", "addModel.switchyardConfigFields.llmFields.thresholdStep"],
|
||||
["max_output_tokens", "addModel.switchyardConfigFields.llmFields.maxOutputTokens"],
|
||||
["recent_turn_window", "addModel.switchyardConfigFields.llmFields.recentTurnWindow"],
|
||||
] as const;
|
||||
|
||||
const isStructured = (instructions: unknown): instructions is Record<string, unknown> =>
|
||||
typeof instructions === "object" && instructions !== null;
|
||||
const instructionsText = (instructions: string | Record<string, unknown> | null | undefined): string =>
|
||||
|
|
@ -35,6 +46,7 @@ export default function SwitchyardConfigFields({
|
|||
decisionModels,
|
||||
onToolSemanticsError,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const id = useId();
|
||||
const [toolSemantics, setToolSemantics] = useState(
|
||||
value.options.tool_semantics ? JSON.stringify(value.options.tool_semantics, null, 2) : "",
|
||||
|
|
@ -65,24 +77,24 @@ export default function SwitchyardConfigFields({
|
|||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<div className="min-w-0 space-y-2">
|
||||
<span id={`${id}-heuristic`} className="text-sm font-medium">
|
||||
Heuristic
|
||||
{t("addModel.switchyardConfigFields.heuristic")}
|
||||
</span>
|
||||
<p
|
||||
aria-labelledby={`${id}-heuristic`}
|
||||
className="flex min-h-10 items-center rounded-md border bg-muted/30 px-3 py-2 text-sm"
|
||||
>
|
||||
Stage Router
|
||||
{t("addModel.switchyardConfigFields.stageRouter")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-bias`} className="text-sm font-medium">
|
||||
Bias
|
||||
{t("addModel.switchyardConfigFields.bias")}
|
||||
</label>
|
||||
<Select
|
||||
value={value.options.picker}
|
||||
items={[
|
||||
{ value: "efficient_first", label: "Cost-first" },
|
||||
{ value: "capable_first", label: "Quality-first (experimental)" },
|
||||
{ value: "efficient_first", label: t("addModel.switchyardConfigFields.biasOptions.costFirst") },
|
||||
{ value: "capable_first", label: t("addModel.switchyardConfigFields.biasOptions.qualityFirst") },
|
||||
]}
|
||||
onValueChange={(picker) =>
|
||||
onChange({
|
||||
|
|
@ -98,29 +110,33 @@ export default function SwitchyardConfigFields({
|
|||
<SelectValue className="whitespace-normal break-words" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="efficient_first">Cost-first</SelectItem>
|
||||
<SelectItem value="efficient_first">
|
||||
{t("addModel.switchyardConfigFields.biasOptions.costFirst")}
|
||||
</SelectItem>
|
||||
<SelectItem value="capable_first">
|
||||
<span className="whitespace-normal">Quality-first (experimental)</span>
|
||||
<span className="whitespace-normal">
|
||||
{t("addModel.switchyardConfigFields.biasOptions.qualityFirst")}
|
||||
</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{value.options.picker === "capable_first" && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Switchyard has not benchmarked this bias, so it has no calibrated confidence thresholds.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{t("addModel.switchyardConfigFields.qualityFirstWarning")}</p>
|
||||
)}
|
||||
</div>
|
||||
{(["capable", "efficient"] as const).map((tier) => (
|
||||
{TIERS.map((tier) => (
|
||||
<div key={tier} className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-${tier}`} className="text-sm font-medium">
|
||||
{tier === "capable" ? "Capable model" : "Efficient model"}
|
||||
{tier === "capable"
|
||||
? t("addModel.switchyardConfigFields.capableModel")
|
||||
: t("addModel.switchyardConfigFields.efficientModel")}
|
||||
</label>
|
||||
<ModelChoiceCombobox
|
||||
id={`${id}-${tier}`}
|
||||
value={value.models[tier]}
|
||||
onChange={(model) => onChange({ ...value, models: { ...value.models, [tier]: model ?? "" } })}
|
||||
choices={models}
|
||||
placeholder="Select a model"
|
||||
placeholder={t("addModel.switchyardConfigFields.selectModel")}
|
||||
ariaInvalid={undefined}
|
||||
ariaDescribedBy={undefined}
|
||||
/>
|
||||
|
|
@ -131,10 +147,10 @@ export default function SwitchyardConfigFields({
|
|||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<label htmlFor={`${id}-classifier`} className="text-sm font-medium">
|
||||
Classifier
|
||||
{t("addModel.switchyardConfigFields.classifier")}
|
||||
</label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ask a judge model when the tool history does not give a confident routing decision.
|
||||
{t("addModel.switchyardConfigFields.classifierDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
|
|
@ -147,13 +163,13 @@ export default function SwitchyardConfigFields({
|
|||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-classifier-kind`} className="text-sm font-medium">
|
||||
Classifier type
|
||||
{t("addModel.switchyardConfigFields.classifierType")}
|
||||
</label>
|
||||
<Select
|
||||
value={classifier.kind}
|
||||
items={[
|
||||
{ value: "llm", label: "LLM judge" },
|
||||
{ value: "decision", label: "Decision model" },
|
||||
{ value: "llm", label: t("addModel.switchyardConfigFields.classifierKinds.llm") },
|
||||
{ value: "decision", label: t("addModel.switchyardConfigFields.classifierKinds.decision") },
|
||||
]}
|
||||
onValueChange={(kind) => onChange(setClassifierKind(value, kind === "decision" ? "decision" : "llm"))}
|
||||
>
|
||||
|
|
@ -161,21 +177,29 @@ export default function SwitchyardConfigFields({
|
|||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="llm">LLM judge</SelectItem>
|
||||
<SelectItem value="decision">Decision model</SelectItem>
|
||||
<SelectItem value="llm">{t("addModel.switchyardConfigFields.classifierKinds.llm")}</SelectItem>
|
||||
<SelectItem value="decision">
|
||||
{t("addModel.switchyardConfigFields.classifierKinds.decision")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-judge`} className="text-sm font-medium">
|
||||
{decision ? "Decision model" : "Judge model"}
|
||||
{decision
|
||||
? t("addModel.switchyardConfigFields.decisionModel")
|
||||
: t("addModel.switchyardConfigFields.judgeModel")}
|
||||
</label>
|
||||
<ModelChoiceCombobox
|
||||
id={`${id}-judge`}
|
||||
value={value.models.judge ?? ""}
|
||||
onChange={(judge) => onChange({ ...value, models: { ...value.models, judge: judge ?? "" } })}
|
||||
choices={decision ? decisionModels : models}
|
||||
placeholder={decision ? "Select a decision model" : "Select a judge model"}
|
||||
placeholder={
|
||||
decision
|
||||
? t("addModel.switchyardConfigFields.selectDecisionModel")
|
||||
: t("addModel.switchyardConfigFields.selectJudgeModel")
|
||||
}
|
||||
ariaInvalid={undefined}
|
||||
ariaDescribedBy={undefined}
|
||||
/>
|
||||
|
|
@ -186,7 +210,7 @@ export default function SwitchyardConfigFields({
|
|||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span id={`${id}-confidence`} className="text-sm font-medium">
|
||||
Confidence threshold
|
||||
{t("addModel.switchyardConfigFields.confidenceThreshold")}
|
||||
</span>
|
||||
<span className="text-sm tabular-nums">{stageConfidence(value).toFixed(2)}</span>
|
||||
</div>
|
||||
|
|
@ -204,39 +228,42 @@ export default function SwitchyardConfigFields({
|
|||
}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Below this confidence, use {classifier ? "the judge model" : "the bias’s default model"}. Critical errors can
|
||||
still select the capable model.
|
||||
{classifier
|
||||
? t("addModel.switchyardConfigFields.fallbackToJudge")
|
||||
: t("addModel.switchyardConfigFields.fallbackToDefault")}
|
||||
</p>
|
||||
</div>
|
||||
<details className="rounded-lg border p-4">
|
||||
<summary className="cursor-pointer text-sm font-medium">Advanced settings</summary>
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
{t("addModel.switchyardConfigFields.advancedSettings")}
|
||||
</summary>
|
||||
<div className="mt-5 space-y-5">
|
||||
<h3 className="text-sm font-semibold">Stage</h3>
|
||||
<h3 className="text-sm font-semibold">{t("addModel.switchyardConfigFields.sections.stage")}</h3>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-recent`}>Recent window</label>
|
||||
<label htmlFor={`${id}-recent`}>{t("addModel.switchyardConfigFields.recentWindow")}</label>
|
||||
<Input
|
||||
id={`${id}-recent`}
|
||||
type="number"
|
||||
min={1}
|
||||
value={value.options.recent_window ?? ""}
|
||||
placeholder="Library default"
|
||||
placeholder={t("addModel.switchyardConfigFields.libraryDefault")}
|
||||
onChange={(event) => setOption("recent_window", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-tools`}>Tool semantics</label>
|
||||
<label htmlFor={`${id}-tools`}>{t("addModel.switchyardConfigFields.toolSemantics")}</label>
|
||||
<Textarea
|
||||
id={`${id}-tools`}
|
||||
value={toolSemantics}
|
||||
placeholder={'{"observe": ["read_file"], "mutate": ["edit_file"]}'}
|
||||
placeholder={codeSamples.models.toolSemanticsJson}
|
||||
onChange={(event) => changeToolSemantics(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{decision && (
|
||||
<>
|
||||
<h3 className="text-sm font-semibold">Decision model</h3>
|
||||
<h3 className="text-sm font-semibold">{t("addModel.switchyardConfigFields.sections.decisionModel")}</h3>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-cutoff`}>Advantage cutoff</label>
|
||||
<label htmlFor={`${id}-cutoff`}>{t("addModel.switchyardConfigFields.advantageCutoff")}</label>
|
||||
<Input
|
||||
id={`${id}-cutoff`}
|
||||
type="number"
|
||||
|
|
@ -249,16 +276,18 @@ export default function SwitchyardConfigFields({
|
|||
}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Route to the capable model when the decision model’s advantage probability is above this value.
|
||||
{t("addModel.switchyardConfigFields.advantageCutoffHelp")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-instructions`}>Decision instructions</label>
|
||||
<label htmlFor={`${id}-instructions`}>
|
||||
{t("addModel.switchyardConfigFields.decisionInstructions")}
|
||||
</label>
|
||||
<Textarea
|
||||
id={`${id}-instructions`}
|
||||
readOnly={isStructured(decision.instructions)}
|
||||
value={instructionsText(decision.instructions)}
|
||||
placeholder="Use Switchyard’s built-in instructions"
|
||||
placeholder={t("addModel.switchyardConfigFields.decisionInstructionsPlaceholder")}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
...value,
|
||||
|
|
@ -271,18 +300,11 @@ export default function SwitchyardConfigFields({
|
|||
)}
|
||||
{llm && (
|
||||
<>
|
||||
<h3 className="text-sm font-semibold">Classifier</h3>
|
||||
<h3 className="text-sm font-semibold">{t("addModel.switchyardConfigFields.sections.classifier")}</h3>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{(
|
||||
[
|
||||
["base_threshold", "Capability threshold"],
|
||||
["threshold_step", "Threshold step"],
|
||||
["max_output_tokens", "Maximum output tokens"],
|
||||
["recent_turn_window", "Recent turn window"],
|
||||
] as const
|
||||
).map(([key, label]) => (
|
||||
{LLM_NUMBER_FIELDS.map(([key, labelKey]) => (
|
||||
<div key={key} className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-${key}`}>{label}</label>
|
||||
<label htmlFor={`${id}-${key}`}>{t(labelKey)}</label>
|
||||
<Input
|
||||
id={`${id}-${key}`}
|
||||
type="number"
|
||||
|
|
@ -296,12 +318,12 @@ export default function SwitchyardConfigFields({
|
|||
))}
|
||||
</div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-format`}>Response format</label>
|
||||
<label htmlFor={`${id}-format`}>{t("addModel.switchyardConfigFields.responseFormat")}</label>
|
||||
<Select
|
||||
value={llm.response_format_type}
|
||||
items={[
|
||||
{ value: "json_schema", label: "JSON schema" },
|
||||
{ value: "json_object", label: "JSON object" },
|
||||
{ value: "json_schema", label: t("addModel.switchyardConfigFields.responseFormats.jsonSchema") },
|
||||
{ value: "json_object", label: t("addModel.switchyardConfigFields.responseFormats.jsonObject") },
|
||||
]}
|
||||
onValueChange={(format) =>
|
||||
onChange({
|
||||
|
|
@ -317,17 +339,21 @@ export default function SwitchyardConfigFields({
|
|||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="json_schema">JSON schema</SelectItem>
|
||||
<SelectItem value="json_object">JSON object</SelectItem>
|
||||
<SelectItem value="json_schema">
|
||||
{t("addModel.switchyardConfigFields.responseFormats.jsonSchema")}
|
||||
</SelectItem>
|
||||
<SelectItem value="json_object">
|
||||
{t("addModel.switchyardConfigFields.responseFormats.jsonObject")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-2">
|
||||
<label htmlFor={`${id}-prompt`}>Classifier prompt</label>
|
||||
<label htmlFor={`${id}-prompt`}>{t("addModel.switchyardConfigFields.classifierPrompt")}</label>
|
||||
<Textarea
|
||||
id={`${id}-prompt`}
|
||||
value={llm.prompt ?? ""}
|
||||
placeholder="Use Switchyard’s built-in prompt"
|
||||
placeholder={t("addModel.switchyardConfigFields.classifierPromptPlaceholder")}
|
||||
onChange={(event) =>
|
||||
onChange({ ...value, classifier: { ...llm, prompt: event.target.value || undefined } })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from "./switchyard_config";
|
||||
import SwitchyardConfigFields from "./SwitchyardConfigFields";
|
||||
import { handleAddSwitchyardSubmit, type SwitchyardModelInfo } from "./handle_add_auto_router_submit";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface FieldsProps {
|
||||
accessToken: string;
|
||||
|
|
@ -26,6 +27,7 @@ interface FieldsProps {
|
|||
}
|
||||
|
||||
export function SwitchyardModelFields({ accessToken, routerName, value, onChange, onToolSemanticsError }: FieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data: models = [], isError } = useQuery({
|
||||
queryKey: ["availableModels", "switchyard", accessToken],
|
||||
queryFn: () => fetchAvailableModels(accessToken),
|
||||
|
|
@ -37,12 +39,10 @@ export function SwitchyardModelFields({ accessToken, routerName, value, onChange
|
|||
const toChoice = (model: { model_group: string }) => ({ value: model.model_group, label: model.model_group });
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Route between a capable and an efficient model using the conversation’s tool history.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{t("addModel.switchyardRouterForm.modelFields.description")}</p>
|
||||
{isError && (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
Unable to load available models.
|
||||
{t("addModel.switchyardRouterForm.modelFields.modelsLoadFailed")}
|
||||
</p>
|
||||
)}
|
||||
<SwitchyardConfigFields
|
||||
|
|
@ -110,6 +110,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export default function SwitchyardRouterForm({ accessToken, onSuccess, initial }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const id = useId();
|
||||
const parsed = switchyardConfigSchema.safeParse(initial.config);
|
||||
const [config, setConfig] = useState<SwitchyardConfig>(parsed.success ? parsed.data : emptySwitchyardConfig());
|
||||
|
|
@ -150,7 +151,7 @@ export default function SwitchyardRouterForm({ accessToken, onSuccess, initial }
|
|||
>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor={`${id}-name`} className="text-sm font-medium">
|
||||
Router name
|
||||
{t("addModel.switchyardRouterForm.routerName")}
|
||||
</label>
|
||||
<Input id={`${id}-name`} value={name} onChange={(event) => setName(event.target.value)} />
|
||||
</div>
|
||||
|
|
@ -168,7 +169,7 @@ export default function SwitchyardRouterForm({ accessToken, onSuccess, initial }
|
|||
)}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={saving || Boolean(toolError)}>
|
||||
{saving ? "Saving…" : "Save changes"}
|
||||
{saving ? t("addModel.switchyardRouterForm.saving") : t("addModel.switchyardRouterForm.saveChanges")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
PTU_END_FIELD,
|
||||
} from "../../utils/ptuValidation";
|
||||
import { usePtuCostAttributionEnabled } from "@/app/(dashboard)/hooks/uiSettings/usePtuCostAttributionEnabled";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface AdvancedSettingsProps {
|
||||
showAdvancedSettings: boolean;
|
||||
|
|
@ -230,7 +231,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
|||
value={(control.value as string | undefined) ?? ""}
|
||||
onChange={control.onChange}
|
||||
onBlur={control.onBlur}
|
||||
placeholder="e.g. 15"
|
||||
placeholder={t("addModel.advancedSettings.ptuCountPlaceholder")}
|
||||
/>
|
||||
)}
|
||||
</MountedFormField>
|
||||
|
|
@ -257,7 +258,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
|||
value={(control.value as string | undefined) ?? ""}
|
||||
onChange={control.onChange}
|
||||
onBlur={control.onBlur}
|
||||
placeholder="e.g. 2.00"
|
||||
placeholder={t("addModel.advancedSettings.ptuRatePlaceholder")}
|
||||
/>
|
||||
)}
|
||||
</MountedFormField>
|
||||
|
|
@ -531,11 +532,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
|||
onChange={control.onChange}
|
||||
onBlur={control.onBlur}
|
||||
rows={4}
|
||||
placeholder='{
|
||||
"rpm": 100,
|
||||
"timeout": 0,
|
||||
"stream_timeout": 0
|
||||
}'
|
||||
placeholder={codeSamples.models.advancedLitellmParamsJson}
|
||||
/>
|
||||
)}
|
||||
</MountedFormField>
|
||||
|
|
@ -572,9 +569,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
|||
onChange={control.onChange}
|
||||
onBlur={control.onBlur}
|
||||
rows={4}
|
||||
placeholder='{
|
||||
"mode": "chat"
|
||||
}'
|
||||
placeholder={codeSamples.models.advancedModelInfoJson}
|
||||
/>
|
||||
)}
|
||||
</MountedFormField>
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ const AutoRouterConnectionTest: React.FC<AutoRouterConnectionTestProps> = ({
|
|||
<span className="font-medium">{target.labels.join(", ")}</span>{" "}
|
||||
<span className="text-muted-foreground">
|
||||
{"->"} {target.modelGroup}
|
||||
{target.mode === "embedding" ? " (embedding)" : ""}
|
||||
{target.mode === "embedding" ? t("addModel.autoRouterConnectionTest.embeddingSuffix") : ""}
|
||||
</span>
|
||||
{result.status === "error" && (
|
||||
<p className="mt-1 text-xs text-destructive" data-testid="test-error-message">
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { Switch } from "@/components/ui/switch";
|
|||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"] as const;
|
||||
const HTTP_METHOD_OPTIONS = HTTP_METHODS.map((method) => ({ label: method, value: method }));
|
||||
|
|
@ -183,7 +184,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
|
|||
{({ value, onChange, ...field }) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="bria"
|
||||
placeholder={codeSamples.passThrough.targetPrefix}
|
||||
value={value ?? ""}
|
||||
onChange={(event) => {
|
||||
const raw = event.target.value;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { CopyToClipboard } from "react-copy-to-clipboard";
|
|||
import { toast } from "@/lib/toast";
|
||||
import { downloadBlob } from "@/components/EntityUsageExport/utils";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface BulkCreateUsersProps {
|
||||
accessToken: string;
|
||||
|
|
@ -590,7 +591,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
|
|||
<div className="flex items-start">
|
||||
<div className="w-3 h-3 rounded-full bg-border mt-1.5 mr-2 shrink-0"></div>
|
||||
<div>
|
||||
<p className="font-medium">teams</p>
|
||||
<p className="font-medium">{codeSamples.users.bulkTemplateTeamsColumn}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("users.bulkCreate.templateColumns.teams")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -615,7 +616,7 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
|
|||
<div className="flex items-start">
|
||||
<div className="w-3 h-3 rounded-full bg-border mt-1.5 mr-2 shrink-0"></div>
|
||||
<div>
|
||||
<p className="font-medium">models</p>
|
||||
<p className="font-medium">{codeSamples.users.bulkTemplateModelsColumn}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("users.bulkCreate.templateColumns.models")}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,32 @@ import { ChatAttachment, ChatImageAttachment, ChatMessage } from "./types";
|
|||
import ChatFileChip from "./ChatFileChip";
|
||||
import { isChatImageUrl } from "./imageHelpers";
|
||||
|
||||
const THINKING_PULSE_CSS = `
|
||||
@keyframes thinking-pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
.chat-thinking-text {
|
||||
animation: thinking-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
`;
|
||||
|
||||
const TYPING_DOTS_CSS = `
|
||||
@keyframes chat-typing-bounce {
|
||||
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
||||
30% { transform: translateY(-4px); opacity: 1; }
|
||||
}
|
||||
.chat-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-muted-foreground);
|
||||
animation: chat-typing-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
.chat-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.chat-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
`;
|
||||
|
||||
const REDACTED_KEY_PATTERNS = /token|key|secret|password|auth/i;
|
||||
|
||||
function redactSensitiveValues(obj: Record<string, unknown>): Record<string, unknown> {
|
||||
|
|
@ -442,15 +468,7 @@ function ThinkingPlaceholder() {
|
|||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@keyframes thinking-pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
.chat-thinking-text {
|
||||
animation: thinking-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
`}</style>
|
||||
<style>{THINKING_PULSE_CSS}</style>
|
||||
<div className="inline-flex items-center gap-1.5 px-2.5 mb-2 bg-muted/50 border rounded-lg text-xs text-muted-foreground">
|
||||
<span className="chat-thinking-text py-1">{t("chat.messages.thinking")}</span>
|
||||
</div>
|
||||
|
|
@ -461,21 +479,7 @@ function ThinkingPlaceholder() {
|
|||
function TypingDots() {
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@keyframes chat-typing-bounce {
|
||||
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
||||
30% { transform: translateY(-4px); opacity: 1; }
|
||||
}
|
||||
.chat-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-muted-foreground);
|
||||
animation: chat-typing-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
.chat-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.chat-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
`}</style>
|
||||
<style>{TYPING_DOTS_CSS}</style>
|
||||
<div className="chat-dot" />
|
||||
<div className="chat-dot" />
|
||||
<div className="chat-dot" />
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default function ChatModelPicker({
|
|||
["all", t("chat.modelPicker.filters.all")],
|
||||
["chat", t("chat.modelPicker.filters.chat")],
|
||||
["image", t("chat.modelPicker.filters.images")],
|
||||
] as const
|
||||
] satisfies ReadonlyArray<readonly ["all" | ChatMode, string]>
|
||||
).map(([value, label]) => (
|
||||
<Button
|
||||
key={value}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ import { useChatShell } from "@/contexts/ChatShellContext";
|
|||
import ViewSwitcher from "@/components/Navbar/ViewSwitcher";
|
||||
import ConversationList from "./ConversationList";
|
||||
|
||||
const conversationUrl = (chatsRoute: string, id: string) => `${chatsRoute}?id=${encodeURIComponent(id)}`;
|
||||
|
||||
export function getChatRoutes() {
|
||||
const base = uiHref("chat");
|
||||
return {
|
||||
|
|
@ -193,7 +195,7 @@ const ChatShell: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||
<ConversationList
|
||||
conversations={conversations}
|
||||
activeConversationId={activeConversationId}
|
||||
onSelect={(id) => navigate(`${routes.chats}?id=${encodeURIComponent(id)}`)}
|
||||
onSelect={(id) => navigate(conversationUrl(routes.chats, id))}
|
||||
onDelete={(id) => {
|
||||
deleteConversation(id);
|
||||
if (id === activeConversationId) navigate(routes.chats);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import type { MCPEvent } from "@/components/mcp_tools/types";
|
|||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { cn } from "@/lib/cva.config";
|
||||
|
||||
const mcpCallPanelKey = (index: number) => `mcp-call-${index}`;
|
||||
|
||||
interface MCPEventsDisplayProps {
|
||||
events: MCPEvent[];
|
||||
className?: string;
|
||||
|
|
@ -92,7 +94,7 @@ function MCPEventsPanels({ toolsEvent, mcpCallEvents }: MCPEventsPanelsProps) {
|
|||
)}
|
||||
|
||||
{mcpCallEvents.map((callEvent, index) => {
|
||||
const key = `mcp-call-${index}`;
|
||||
const key = mcpCallPanelKey(index);
|
||||
return (
|
||||
<MCPEventPanel
|
||||
key={key}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
|||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
const cloudZeroSettingsSchema = z.object({
|
||||
api_key: z.string().min(1, { error: () => i18n.t("usage.cloudzero.validation.apiKeyRequired") }),
|
||||
|
|
@ -219,7 +220,7 @@ const CloudZeroExportModal: React.FC<CloudZeroExportModalProps> = ({ isOpen, onC
|
|||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/cloudzero.png"
|
||||
alt="CloudZero"
|
||||
alt={productNames.cloudZero}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => {
|
||||
// Fallback to text if image fails to load
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { cx } from "@/lib/cva.config";
|
||||
import { UiLoadingSpinner } from "../ui/ui-loading-spinner";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
export default function LoadingScreen() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={cx("h-screen", "flex items-center justify-center gap-4")}>
|
||||
<div className="text-lg font-medium py-2 pr-4 border-r border-r-gray-200">🚅 LiteLLM</div>
|
||||
<div className="text-lg font-medium py-2 pr-4 border-r border-r-gray-200">{productNames.liteLLMWithLogo}</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<UiLoadingSpinner className="size-4" />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
DataTable,
|
||||
DataTableFilterDrawer,
|
||||
DataTableFilterField,
|
||||
type FilterDraft,
|
||||
DataTableSortHeader,
|
||||
DataTableToolbar,
|
||||
DEFAULT_PAGE_SIZE_OPTIONS,
|
||||
|
|
@ -317,7 +318,7 @@ export default function MemberTable({
|
|||
title={t("memberTable.filters.title")}
|
||||
description={t("memberTable.filters.description")}
|
||||
>
|
||||
{({ get, set }) => (
|
||||
{({ get, set }: FilterDraft<"role">) => (
|
||||
<DataTableFilterField label={roleTitle}>
|
||||
<Select
|
||||
items={roleFilterItems}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Table, TableHeader, TableHead, TableBody, TableRow, TableCell } from "@/components/ui/table";
|
||||
import ModelSelector from "./ModelSelector";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface ModelAliasManagerProps {
|
||||
accessToken: string;
|
||||
|
|
@ -302,7 +303,7 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
{Object.keys(aliasObject).length === 0 ? (
|
||||
<span className="text-muted-foreground">
|
||||
<br />
|
||||
# No aliases configured yet
|
||||
{codeSamples.models.noAliasesYamlComment}
|
||||
</span>
|
||||
) : (
|
||||
Object.entries(aliasObject).map(([key, value]) => (
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Field, FieldLabel } from "@/components/ui/field";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
type GuardrailFields = { request_fields?: string[]; response_fields?: string[] };
|
||||
type GuardrailSettings = Record<string, GuardrailFields | null>;
|
||||
|
|
@ -56,7 +57,11 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
|
|||
emit({ ...value, [guardrailName]: isEmpty ? null : updated });
|
||||
};
|
||||
|
||||
const appendField = (guardrailName: string, fieldType: keyof GuardrailFields, field: string) => {
|
||||
const appendField = (
|
||||
guardrailName: string,
|
||||
fieldType: keyof GuardrailFields,
|
||||
field: "query" | "documents[*]" | "results[*]",
|
||||
) => {
|
||||
handleFieldChange(guardrailName, fieldType, [...(value[guardrailName]?.[fieldType] ?? []), field]);
|
||||
};
|
||||
|
||||
|
|
@ -139,9 +144,9 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
|
|||
</div>
|
||||
<div className="space-y-1 text-xs">
|
||||
<div>{t("passThroughGuardrailsSection.examples")}</div>
|
||||
<div>• query</div>
|
||||
<div>• documents[*].text</div>
|
||||
<div>• messages[*].content</div>
|
||||
<div>{codeSamples.passThrough.guardrailRequestQuery}</div>
|
||||
<div>{codeSamples.passThrough.guardrailRequestDocuments}</div>
|
||||
<div>{codeSamples.passThrough.guardrailRequestMessages}</div>
|
||||
</div>
|
||||
</div>,
|
||||
)}
|
||||
|
|
@ -154,7 +159,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
|
|||
disabled={disabled}
|
||||
onClick={() => appendField(guardrailName, "request_fields", "query")}
|
||||
>
|
||||
+ query
|
||||
{codeSamples.passThrough.guardrailAddQuery}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -163,7 +168,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
|
|||
disabled={disabled}
|
||||
onClick={() => appendField(guardrailName, "request_fields", "documents[*]")}
|
||||
>
|
||||
+ documents[*]
|
||||
{codeSamples.passThrough.guardrailAddDocuments}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -190,8 +195,8 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
|
|||
</div>
|
||||
<div className="space-y-1 text-xs">
|
||||
<div>{t("passThroughGuardrailsSection.examples")}</div>
|
||||
<div>• results[*].text</div>
|
||||
<div>• choices[*].message.content</div>
|
||||
<div>{codeSamples.passThrough.guardrailResponseResults}</div>
|
||||
<div>{codeSamples.passThrough.guardrailResponseChoices}</div>
|
||||
</div>
|
||||
</div>,
|
||||
)}
|
||||
|
|
@ -204,7 +209,7 @@ const PassThroughGuardrailsSection: React.FC<PassThroughGuardrailsSectionProps>
|
|||
disabled={disabled}
|
||||
onClick={() => appendField(guardrailName, "response_fields", "results[*]")}
|
||||
>
|
||||
+ results[*]
|
||||
{codeSamples.passThrough.guardrailAddResults}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { Trans } from "react-i18next";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import LoggingSettings from "../team/LoggingSettings";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
interface PremiumLoggingSettingsProps {
|
||||
value: any[];
|
||||
|
|
@ -23,10 +24,10 @@ export function PremiumLoggingSettings({
|
|||
<div>
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
<Badge variant="secondary" className="opacity-50">
|
||||
✨ langfuse-logging
|
||||
{codeSamples.logging.premiumLangfuse}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="opacity-50">
|
||||
✨ datadog-logging
|
||||
{codeSamples.logging.premiumDatadog}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="p-3 bg-muted border border-border rounded-lg">
|
||||
|
|
|
|||
|
|
@ -826,6 +826,7 @@ const ExistingEditAutoRouterModal: React.FC<EditAutoRouterModalProps> = ({
|
|||
};
|
||||
|
||||
const EditAutoRouterModal: React.FC<EditAutoRouterModalProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
if (props.modelData?.litellm_params?.model?.startsWith("auto_router/switchyard")) {
|
||||
return (
|
||||
<Dialog
|
||||
|
|
@ -836,7 +837,7 @@ const EditAutoRouterModal: React.FC<EditAutoRouterModalProps> = (props) => {
|
|||
>
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Switchyard router</DialogTitle>
|
||||
<DialogTitle>{t("editAutoRouter.editAutoRouterModal.switchyardTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<SwitchyardRouterForm
|
||||
key={props.modelData.model_info.id}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { toast } from "@/lib/toast";
|
|||
import { serviceHealthCheck, setCallbacksCall } from "./networking";
|
||||
import { EmailEventSettings } from "./email_events";
|
||||
|
||||
const EMAIL_SERVICE = "email";
|
||||
|
||||
interface EmailSettingsProps {
|
||||
accessToken: string | null;
|
||||
premiumUser: boolean;
|
||||
|
|
@ -197,7 +199,7 @@ const EmailSettings: React.FC<EmailSettingsProps> = ({ accessToken, premiumUser,
|
|||
onClick={async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
await serviceHealthCheck(accessToken, "email");
|
||||
await serviceHealthCheck(accessToken, EMAIL_SERVICE);
|
||||
toast.success(t("loggingAndAlerts.emailSettings.toasts.testTriggered"));
|
||||
} catch (error) {
|
||||
toast.fromError(error);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
DataTable,
|
||||
DataTableFilterDrawer,
|
||||
DataTableFilterField,
|
||||
type FilterDraft,
|
||||
DataTableToolbar,
|
||||
} from "@/components/shared/DataTable";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -167,7 +168,10 @@ export function AuditLogsTable({
|
|||
title={t("logs.audit.table.filtersTitle")}
|
||||
description={t("logs.audit.table.filtersDescription")}
|
||||
>
|
||||
{({ get, set }) => (
|
||||
{({
|
||||
get,
|
||||
set,
|
||||
}: FilterDraft<"action" | "changed_by" | "key_hash" | "object_id" | "table_name" | "team_id">) => (
|
||||
<>
|
||||
<DataTableFilterField label={t("logs.audit.table.objectId")}>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -551,11 +551,13 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
|
|||
${formatNumberWithCommas(logEntry.spend || 0, 8)}
|
||||
</DescriptionItem>
|
||||
<DescriptionItem label={t("logs.detail.metrics.duration")}>
|
||||
{logEntry.request_duration_ms != null ? (logEntry.request_duration_ms / 1000).toFixed(3) : "-"} s
|
||||
{logEntry.request_duration_ms != null ? (logEntry.request_duration_ms / 1000).toFixed(3) : "-"}
|
||||
{t("logs.detail.metrics.secondsSuffix")}
|
||||
</DescriptionItem>
|
||||
{ttftMs != null && ttftMs > 0 && (
|
||||
<DescriptionItem label={t("logs.detail.metrics.timeToFirstToken")}>
|
||||
{(ttftMs / 1000).toFixed(3)} s
|
||||
{(ttftMs / 1000).toFixed(3)}
|
||||
{t("logs.detail.metrics.secondsSuffix")}
|
||||
</DescriptionItem>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,10 @@ function TraceEventRow({ row, isSelected, onClick }: TraceEventRowProps) {
|
|||
<ClassifyTag origin={row.metadata?.internal_call_origin} className="ml-auto" />
|
||||
</div>
|
||||
<div className="text-[10px] text-muted-foreground mt-0 flex items-center gap-1.5 font-mono">
|
||||
<span>{durationValue}s</span>
|
||||
<span>
|
||||
{durationValue}
|
||||
{t("logs.detail.drawer.secondsSuffix")}
|
||||
</span>
|
||||
{row.spend ? (
|
||||
<>
|
||||
<span>·</span>
|
||||
|
|
@ -363,7 +366,7 @@ export function LogDetailsDrawer({
|
|||
? mcpCount
|
||||
: logsForList.filter((row) => MCP_CALL_TYPES.includes(row.call_type)).length,
|
||||
].map((count, i) => {
|
||||
const label = [" LLM", " Agent", " MCP"][i];
|
||||
const label = [" LLM", t("logs.detail.drawer.agentCountSuffix"), " MCP"][i];
|
||||
return count > 0 ? (
|
||||
<span key={label}>
|
||||
<span className="mx-1.5">·</span>
|
||||
|
|
@ -377,7 +380,8 @@ export function LogDetailsDrawer({
|
|||
{isSessionMode && (
|
||||
<>
|
||||
<span className="mx-1.5">·</span>
|
||||
{sessionDurationSeconds}s
|
||||
{sessionDurationSeconds}
|
||||
{t("logs.detail.drawer.secondsSuffix")}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ function ResponseTurn({ response, index }: { response: RealtimeResponse; index:
|
|||
}}
|
||||
>
|
||||
<Badge variant={response.status === "completed" ? "secondary" : "outline"} style={{ margin: 0 }}>
|
||||
{response.status || "unknown"}
|
||||
{response.status || t("logs.detail.realtime.unknownStatus")}
|
||||
</Badge>
|
||||
{usage && (
|
||||
<span className="text-muted-foreground" style={{ fontSize: 11 }}>
|
||||
|
|
|
|||
|
|
@ -258,12 +258,15 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo
|
|||
{response.assessments.map((assess, idx) => {
|
||||
const policyBadges = (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{assess.wordPolicy && chip("word", "slate")}
|
||||
{assess.contentPolicy && chip("content", "slate")}
|
||||
{assess.topicPolicy && chip("topic", "slate")}
|
||||
{assess.sensitiveInformationPolicy && chip("sensitive-info", "slate")}
|
||||
{assess.contextualGroundingPolicy && chip("contextual-grounding", "slate")}
|
||||
{assess.automatedReasoningPolicy && chip("automated-reasoning", "slate")}
|
||||
{assess.wordPolicy && chip(t("logs.detail.bedrock.policyChips.word"), "slate")}
|
||||
{assess.contentPolicy && chip(t("logs.detail.bedrock.policyChips.content"), "slate")}
|
||||
{assess.topicPolicy && chip(t("logs.detail.bedrock.policyChips.topic"), "slate")}
|
||||
{assess.sensitiveInformationPolicy &&
|
||||
chip(t("logs.detail.bedrock.policyChips.sensitiveInformation"), "slate")}
|
||||
{assess.contextualGroundingPolicy &&
|
||||
chip(t("logs.detail.bedrock.policyChips.contextualGrounding"), "slate")}
|
||||
{assess.automatedReasoningPolicy &&
|
||||
chip(t("logs.detail.bedrock.policyChips.automatedReasoning"), "slate")}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -415,7 +418,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo
|
|||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{chip(r.action ?? t("logs.detail.bedrock.notAvailable"), r.detected ? "red" : "slate")}
|
||||
<span className="font-medium">{r.name ?? "regex"}</span>
|
||||
<span className="font-medium">{r.name ?? t("logs.detail.bedrock.unnamedRegex")}</span>
|
||||
<span className="font-mono text-xs break-all">{r.regex}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -439,7 +442,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo
|
|||
<div key={i} className="px-3 py-1.5 bg-muted rounded-md text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
{chip(t.action ?? i18n.t("logs.detail.bedrock.notAvailable"), t.detected ? "red" : "slate")}
|
||||
<span className="font-medium">{t.name ?? "topic"}</span>
|
||||
<span className="font-medium">{t.name ?? i18n.t("logs.detail.bedrock.unnamedTopic")}</span>
|
||||
{t.type && chip(t.type, "slate")}
|
||||
{boolPill(t.detected)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,9 @@ const ContentFilterDetails: React.FC<ContentFilterDetailsProps> = ({ response })
|
|||
<div key={idx} className="p-3 bg-muted rounded-md">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<KV label={t("logs.detail.contentFilter.pattern")}>{detection.pattern_name || "unknown"}</KV>
|
||||
<KV label={t("logs.detail.contentFilter.pattern")}>
|
||||
{detection.pattern_name || t("logs.detail.contentFilter.unknown")}
|
||||
</KV>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<KV label={t("logs.detail.contentFilter.action")}>
|
||||
|
|
@ -177,7 +179,7 @@ const ContentFilterDetails: React.FC<ContentFilterDetailsProps> = ({ response })
|
|||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<KV label={t("logs.detail.contentFilter.keyword")} mono>
|
||||
{detection.keyword || "unknown"}
|
||||
{detection.keyword || t("logs.detail.contentFilter.unknown")}
|
||||
</KV>
|
||||
{detection.description && (
|
||||
<KV label={t("logs.detail.contentFilter.description")}>{detection.description}</KV>
|
||||
|
|
@ -207,9 +209,11 @@ const ContentFilterDetails: React.FC<ContentFilterDetailsProps> = ({ response })
|
|||
<div key={idx} className="p-3 bg-muted rounded-md">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<KV label={t("logs.detail.contentFilter.category")}>{detection.category || "unknown"}</KV>
|
||||
<KV label={t("logs.detail.contentFilter.category")}>
|
||||
{detection.category || t("logs.detail.contentFilter.unknown")}
|
||||
</KV>
|
||||
<KV label={t("logs.detail.contentFilter.keyword")} mono>
|
||||
{detection.keyword || "unknown"}
|
||||
{detection.keyword || t("logs.detail.contentFilter.unknown")}
|
||||
</KV>
|
||||
{detection.severity && (
|
||||
<KV label={t("logs.detail.contentFilter.severity")}>
|
||||
|
|
|
|||
|
|
@ -66,9 +66,10 @@ const checkTimeLabel = (value: string, t: TFunction): string => {
|
|||
};
|
||||
|
||||
function HealthStatusBadge({ status }: { status: string }) {
|
||||
const { t } = useTranslation();
|
||||
const tone = HEALTH_STATUS_TONES[status];
|
||||
if (!tone) {
|
||||
return <StatusBadge tone="neutral" label="unknown" />;
|
||||
return <StatusBadge tone="neutral" label={t("modelDashboard.healthChecksTableColumns.unknownStatus")} />;
|
||||
}
|
||||
return <StatusBadge tone={tone} label={status} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
movePinnedAlias,
|
||||
orderAliases,
|
||||
} from "./model_group_alias_ordering";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
type ModelGroupAliasValue = string | { model: string; hidden?: boolean };
|
||||
|
||||
|
|
@ -542,7 +543,7 @@ const ModelGroupAliasSettings: React.FC<ModelGroupAliasSettingsProps> = ({
|
|||
{Object.keys(aliasObject).length === 0 ? (
|
||||
<span className="text-muted-foreground">
|
||||
<br />
|
||||
# No aliases configured yet
|
||||
{codeSamples.models.noModelGroupAliasesYamlComment}
|
||||
</span>
|
||||
) : (
|
||||
Object.entries(aliasObject).map(([key, value]) => (
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import { Tag } from "./tag_management/types";
|
|||
import { getDisplayModelName } from "./view_model/model_name_display";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import KubernetesPodsCard from "./model_info/KubernetesPodsCard";
|
||||
import { modelKeys } from "@/app/(dashboard)/hooks/models/useModels";
|
||||
|
||||
interface ModelInfoViewProps {
|
||||
modelId: string;
|
||||
|
|
@ -830,7 +831,7 @@ export default function ModelInfoView({
|
|||
accessToken={accessToken}
|
||||
modelId={modelId}
|
||||
onUpdated={() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
|
||||
queryClient.invalidateQueries({ queryKey: modelKeys.lists() });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { normalizeMoyaiUrl } from "./moyaiConnect";
|
|||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import styles from "./MoyaiLanding.module.css";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
export const MOYAI_GITHUB_URL = "https://github.com/BerriAI/moyai";
|
||||
export const MOYAI_WALKTHROUGH_URL = `${MOYAI_GITHUB_URL}#see-it-in-action`;
|
||||
|
|
@ -214,7 +215,7 @@ function DemoPreview() {
|
|||
<span className="size-2.5 rounded-full bg-[#ff5f57]" />
|
||||
<span className="size-2.5 rounded-full bg-[#febc2e]" />
|
||||
<span className="size-2.5 rounded-full bg-[#28c840]" />
|
||||
<span className="ml-3 truncate text-xs text-[#a6a4c6]">github.com/BerriAI/moyai</span>
|
||||
<span className="ml-3 truncate text-xs text-[#a6a4c6]">{productNames.moyaiRepository}</span>
|
||||
</div>
|
||||
{failed ? (
|
||||
<div className="flex aspect-[1196/720] flex-col items-center justify-center gap-3 text-[#c2c6d0]">
|
||||
|
|
@ -350,7 +351,7 @@ export default function MoyaiLanding({
|
|||
style={{ animationDelay: "0.08s" }}
|
||||
>
|
||||
<img src={moyaiHead.src} alt="" className={`${styles.mark} mb-[0.08em] h-[0.78em] w-auto`} />
|
||||
<span>Moyai</span>
|
||||
<span>{productNames.moyai}</span>
|
||||
</h1>
|
||||
<p
|
||||
className={`${styles.rise} mb-0 mt-3 text-[clamp(18px,1.8vw,26px)] tracking-[-0.01em] text-[rgba(226,234,255,0.86)]`}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,23 @@ import { buildKeyCreatePayload, type KeyCreateInput } from "./createKeyPayload";
|
|||
import { simplifyKeyGenerateError } from "./utils";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
|
||||
const GENERATE_KEY_EXCLUDED_FIELDS = [
|
||||
"key_alias",
|
||||
"team_id",
|
||||
"organization_id",
|
||||
"models",
|
||||
"duration",
|
||||
"metadata",
|
||||
"tags",
|
||||
"guardrails",
|
||||
"max_budget",
|
||||
"budget_duration",
|
||||
"tpm_limit",
|
||||
"rpm_limit",
|
||||
"tpd_limit",
|
||||
];
|
||||
const GENERATE_KEY_EXCLUDED_FIELDS_WITH_KEY = [...GENERATE_KEY_EXCLUDED_FIELDS, "key"];
|
||||
|
||||
const KEY_TYPE_OPTIONS: { value: string; label: ParseKeys; hint: ParseKeys }[] = [
|
||||
{ value: "llm_api", label: "createKey.keyTypes.llmApi.label", hint: "createKey.keyTypes.llmApi.hint" },
|
||||
{ value: "management", label: "createKey.keyTypes.management.label", hint: "createKey.keyTypes.management.hint" },
|
||||
|
|
@ -1877,22 +1894,11 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
|
|||
<SchemaFormFields
|
||||
schemaComponent="GenerateKeyRequest"
|
||||
setValue={form.setValue}
|
||||
excludedFields={[
|
||||
"key_alias",
|
||||
"team_id",
|
||||
"organization_id",
|
||||
"models",
|
||||
"duration",
|
||||
"metadata",
|
||||
"tags",
|
||||
"guardrails",
|
||||
"max_budget",
|
||||
"budget_duration",
|
||||
"tpm_limit",
|
||||
"rpm_limit",
|
||||
"tpd_limit",
|
||||
...(disableCustomApiKeys ? ["key"] : []),
|
||||
]}
|
||||
excludedFields={
|
||||
disableCustomApiKeys
|
||||
? GENERATE_KEY_EXCLUDED_FIELDS_WITH_KEY
|
||||
: GENERATE_KEY_EXCLUDED_FIELDS
|
||||
}
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useZodForm } from "@/lib/forms/useZodForm";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { codeSamples } from "@/i18n/untranslated/codeSamples";
|
||||
|
||||
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"] as const;
|
||||
const HTTP_METHOD_OPTIONS = HTTP_METHODS.map((method) => ({ label: method, value: method }));
|
||||
|
|
@ -427,7 +428,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
|
|||
{...field}
|
||||
rows={5}
|
||||
value={value ?? ""}
|
||||
placeholder='{"Authorization": "Bearer your-token", "Content-Type": "application/json"}'
|
||||
placeholder={codeSamples.passThrough.headersJson}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue