mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for prompts batch 2
This commit is contained in:
parent
524569b9ae
commit
129811bce8
11 changed files with 285 additions and 81 deletions
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { ArrowUpOutlined } from "@ant-design/icons";
|
||||
import { Button as TremorButton } from "@tremor/react";
|
||||
import { Input } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||
onKeyDown,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center flex-1 bg-white border border-gray-300 rounded-xl px-3 py-1 min-h-[44px]">
|
||||
|
|
@ -31,7 +33,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||
value={inputMessage}
|
||||
onChange={(e) => onInputChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder="Type your message... (Shift+Enter for new line)"
|
||||
placeholder={t("promptsPage.messageInput.placeholder")}
|
||||
disabled={isLoading}
|
||||
className="flex-1"
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
|
|
@ -57,7 +59,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||
|
||||
{isLoading && (
|
||||
<TremorButton onClick={onCancel} className="bg-red-50 hover:bg-red-100 text-red-600 border-red-200">
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</TremorButton>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import { Input } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface VariableInputProps {
|
||||
extractedVariables: string[];
|
||||
|
|
@ -8,13 +9,14 @@ interface VariableInputProps {
|
|||
}
|
||||
|
||||
const VariableInput: React.FC<VariableInputProps> = ({ extractedVariables, variables, onVariableChange }) => {
|
||||
const { t } = useTranslation();
|
||||
if (extractedVariables.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 border-b border-gray-200 bg-blue-50">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-3">Fill in template variables to start testing</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-3">{t("promptsPage.variableInput.title")}</h3>
|
||||
<div className="space-y-2">
|
||||
{extractedVariables.map((varName) => (
|
||||
<div key={varName}>
|
||||
|
|
@ -26,7 +28,7 @@ const VariableInput: React.FC<VariableInputProps> = ({ extractedVariables, varia
|
|||
<Input
|
||||
value={variables[varName] || ""}
|
||||
onChange={(e) => onVariableChange(varName, e.target.value)}
|
||||
placeholder={`Enter value for ${varName}`}
|
||||
placeholder={t("promptsPage.variableInput.valuePlaceholder", { varName })}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface VariableWarningProps {
|
||||
extractedVariables: string[];
|
||||
|
|
@ -6,6 +7,7 @@ interface VariableWarningProps {
|
|||
}
|
||||
|
||||
const VariableWarning: React.FC<VariableWarningProps> = ({ extractedVariables, variables }) => {
|
||||
const { t } = useTranslation();
|
||||
const missingVariables = extractedVariables.filter(
|
||||
(varName) => !variables[varName] || variables[varName].trim() === "",
|
||||
);
|
||||
|
|
@ -19,9 +21,11 @@ const VariableWarning: React.FC<VariableWarningProps> = ({ extractedVariables, v
|
|||
<div className="flex items-start gap-2">
|
||||
<span className="text-yellow-600 text-sm">⚠️</span>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-yellow-800 font-medium mb-1">Please fill in all template variables above</p>
|
||||
<p className="text-sm text-yellow-800 font-medium mb-1">{t("promptsPage.variableWarning.title")}</p>
|
||||
<p className="text-xs text-yellow-700">
|
||||
Missing: {missingVariables.map((varName) => `{{${varName}}}`).join(", ")}
|
||||
{t("promptsPage.variableWarning.missing", {
|
||||
list: missingVariables.map((varName) => `{{${varName}}}`).join(", "),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { ClearOutlined } from "@ant-design/icons";
|
||||
import { Button as TremorButton } from "@tremor/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ConversationPanelProps } from "./types";
|
||||
import { useConversation } from "./useConversation";
|
||||
import VariableInput from "./VariableInput";
|
||||
|
|
@ -9,6 +10,7 @@ import VariableWarning from "./VariableWarning";
|
|||
import MessageInput from "./MessageInput";
|
||||
|
||||
const ConversationPanel: React.FC<ConversationPanelProps> = ({ prompt, accessToken }) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isLoading,
|
||||
messages,
|
||||
|
|
@ -43,7 +45,7 @@ const ConversationPanel: React.FC<ConversationPanelProps> = ({ prompt, accessTok
|
|||
className="bg-gray-100 hover:bg-gray-200 text-gray-700 border-gray-300"
|
||||
icon={ClearOutlined}
|
||||
>
|
||||
Clear Chat
|
||||
{t("promptsPage.conversationPanel.clearChat")}
|
||||
</TremorButton>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NotificationsManager from "../../../molecules/notifications_manager";
|
||||
import { TokenUsage } from "../../../playground/chat_ui/ResponseMetrics";
|
||||
import { Message } from "./types";
|
||||
|
|
@ -6,6 +7,7 @@ import { convertToDotPrompt, extractVariables } from "../utils";
|
|||
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "../../../networking";
|
||||
|
||||
export const useConversation = (prompt: any, accessToken: string | null) => {
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [inputMessage, setInputMessage] = useState("");
|
||||
|
|
@ -37,12 +39,12 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
|
|||
|
||||
const handleSendMessage = async () => {
|
||||
if (!accessToken) {
|
||||
NotificationsManager.fromBackend("Access token is required");
|
||||
NotificationsManager.fromBackend(t("promptsPage.conversationPanel.accessTokenRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (extractedVariables.length > 0 && !allVariablesFilled) {
|
||||
NotificationsManager.fromBackend("Please fill in all template variables");
|
||||
NotificationsManager.fromBackend(t("promptsPage.conversationPanel.fillAllVariables"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -183,9 +185,18 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
|
|||
setMessages((prev) => {
|
||||
const lastMsg = prev[prev.length - 1];
|
||||
if (lastMsg && lastMsg.role === "assistant" && lastMsg.content === "") {
|
||||
return [...prev.slice(0, -1), { role: "assistant", content: `Error: ${error.message}` }];
|
||||
return [
|
||||
...prev.slice(0, -1),
|
||||
{
|
||||
role: "assistant",
|
||||
content: t("promptsPage.conversationPanel.errorMessage", { message: error.message }),
|
||||
},
|
||||
];
|
||||
}
|
||||
return [...prev, { role: "assistant", content: `Error: ${error.message}` }];
|
||||
return [
|
||||
...prev,
|
||||
{ role: "assistant", content: t("promptsPage.conversationPanel.errorMessage", { message: error.message }) },
|
||||
];
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -199,14 +210,14 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
|
|||
abortController.abort();
|
||||
setAbortController(null);
|
||||
setIsLoading(false);
|
||||
NotificationsManager.info("Request cancelled");
|
||||
NotificationsManager.info(t("promptsPage.conversationPanel.requestCancelled"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearConversation = () => {
|
||||
setMessages([]);
|
||||
setVariablesFilled(false);
|
||||
NotificationsManager.success("Chat history cleared.");
|
||||
NotificationsManager.success(t("promptsPage.conversationPanel.chatCleared"));
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ToolModal from "../tool_modal";
|
||||
import NotificationsManager from "../../molecules/notifications_manager";
|
||||
import { createPromptCall, updatePromptCall, getPromptInfo } from "../../networking";
|
||||
|
|
@ -15,13 +16,14 @@ import DotpromptViewTab from "./DotpromptViewTab";
|
|||
import VersionHistorySidePanel from "./VersionHistorySidePanel";
|
||||
|
||||
const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess, accessToken, initialPromptData }) => {
|
||||
const { t } = useTranslation();
|
||||
const getInitialPrompt = (): PromptType => {
|
||||
if (initialPromptData) {
|
||||
try {
|
||||
return parseExistingPrompt(initialPromptData);
|
||||
} catch (error) {
|
||||
console.error("Error parsing existing prompt:", error);
|
||||
NotificationsManager.fromBackend("Failed to parse prompt data");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptEditorView.failedToParsePrompt"));
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
|
@ -142,7 +144,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
setShowToolModal(false);
|
||||
setEditingToolIndex(null);
|
||||
} catch (error) {
|
||||
NotificationsManager.fromBackend("Invalid JSON format");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptEditorView.invalidJsonFormat"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -172,7 +174,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
// NotificationsManager.success(`Loaded version v${versionNum}`);
|
||||
} catch (error) {
|
||||
console.error("Error loading version:", error);
|
||||
NotificationsManager.fromBackend("Failed to load prompt version");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptEditorView.failedToLoadVersion"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -186,12 +188,12 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
|
||||
const handleSave = async () => {
|
||||
if (!accessToken) {
|
||||
NotificationsManager.fromBackend("Access token is required");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptEditorView.accessTokenRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prompt.name || prompt.name.trim() === "") {
|
||||
NotificationsManager.fromBackend("Please enter a valid prompt name");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptEditorView.invalidPromptName"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -215,16 +217,18 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
|
||||
if (editMode && initialPromptData?.prompt_spec?.prompt_id) {
|
||||
await updatePromptCall(accessToken, initialPromptData.prompt_spec.prompt_id, promptData);
|
||||
NotificationsManager.success("Prompt updated successfully!");
|
||||
NotificationsManager.success(t("promptsPage.promptEditorView.updateSuccess"));
|
||||
} else {
|
||||
await createPromptCall(accessToken, promptData);
|
||||
NotificationsManager.success("Prompt created successfully!");
|
||||
NotificationsManager.success(t("promptsPage.promptEditorView.createSuccess"));
|
||||
}
|
||||
onSuccess();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error saving prompt:", error);
|
||||
NotificationsManager.fromBackend(editMode ? "Failed to update prompt" : "Failed to save prompt");
|
||||
NotificationsManager.fromBackend(
|
||||
editMode ? t("promptsPage.promptEditorView.updateFailed") : t("promptsPage.promptEditorView.saveFailed"),
|
||||
);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
setShowNameModal(false);
|
||||
|
|
@ -321,7 +325,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
}`}
|
||||
onClick={() => setViewMode("pretty")}
|
||||
>
|
||||
PRETTY
|
||||
{t("promptsPage.promptEditorView.viewModePretty")}
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-1 text-xs font-medium rounded-full transition-colors ${
|
||||
|
|
@ -329,7 +333,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
|
|||
}`}
|
||||
onClick={() => setViewMode("dotprompt")}
|
||||
>
|
||||
DOTPROMPT
|
||||
{t("promptsPage.promptEditorView.viewModeDotprompt")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import {
|
||||
Card,
|
||||
Title,
|
||||
|
|
@ -43,6 +44,7 @@ export interface PromptInfoProps {
|
|||
}
|
||||
|
||||
const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessToken, isAdmin, onDelete, onEdit }) => {
|
||||
const { t } = useTranslation();
|
||||
const [promptData, setPromptData] = useState<PromptSpec | null>(null);
|
||||
const [promptTemplate, setPromptTemplate] = useState<PromptTemplateBase | null>(null);
|
||||
const [rawApiResponse, setRawApiResponse] = useState<any>(null);
|
||||
|
|
@ -77,7 +79,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
}
|
||||
setSelectedVersion(response.prompt_spec.version || null);
|
||||
} catch (error) {
|
||||
NotificationsManager.fromBackend("Failed to load prompt information");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptInfo.failedToLoadInfo"));
|
||||
console.error("Error fetching prompt info:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
@ -124,11 +126,11 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
}, [selectedEnv]);
|
||||
|
||||
if (loading && !promptData) {
|
||||
return <div className="p-4">Loading...</div>;
|
||||
return <div className="p-4">{t("common.loading")}</div>;
|
||||
}
|
||||
|
||||
if (!promptData) {
|
||||
return <div className="p-4">Prompt not found</div>;
|
||||
return <div className="p-4">{t("promptsPage.promptInfo.promptNotFound")}</div>;
|
||||
}
|
||||
|
||||
const formatDate = (dateString?: string) => {
|
||||
|
|
@ -156,12 +158,12 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
setIsDeleting(true);
|
||||
try {
|
||||
await deletePromptCall(accessToken, basePromptId);
|
||||
NotificationsManager.success(`Prompt "${basePromptId}" deleted successfully`);
|
||||
NotificationsManager.success(t("promptsPage.promptInfo.deleteSuccess", { id: basePromptId }));
|
||||
onDelete?.();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error deleting prompt:", error);
|
||||
NotificationsManager.fromBackend("Failed to delete prompt");
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptInfo.deleteFailed"));
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setShowDeleteConfirm(false);
|
||||
|
|
@ -184,7 +186,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
setPromptTemplate(response.raw_prompt_template);
|
||||
setRawApiResponse(response);
|
||||
} catch {
|
||||
NotificationsManager.fromBackend(`Failed to load version v${versionNum}`);
|
||||
NotificationsManager.fromBackend(t("promptsPage.promptInfo.failedToLoadVersion", { version: versionNum }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -198,11 +200,11 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
<div className="p-4">
|
||||
<div>
|
||||
<TremorButton icon={ArrowLeftIcon} variant="light" onClick={onClose} className="mb-4">
|
||||
Back to Prompts
|
||||
{t("promptsPage.promptInfo.backToPrompts")}
|
||||
</TremorButton>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<Title>Prompt Details</Title>
|
||||
<Title>{t("promptsPage.promptInfo.title")}</Title>
|
||||
<div className="flex items-center cursor-pointer">
|
||||
<Text className="text-gray-500 font-mono">{basePromptId}</Text>
|
||||
<Button
|
||||
|
|
@ -232,7 +234,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
onClick={() => onEdit?.(rawApiResponse)}
|
||||
className="flex items-center"
|
||||
>
|
||||
Prompt Studio
|
||||
{t("promptsPage.promptInfo.promptStudio")}
|
||||
</TremorButton>
|
||||
{isAdmin && (
|
||||
<TremorButton
|
||||
|
|
@ -241,7 +243,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
onClick={handleDeleteClick}
|
||||
className="flex items-center"
|
||||
>
|
||||
Delete Prompt
|
||||
{t("promptsPage.promptInfo.deletePrompt")}
|
||||
</TremorButton>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -286,7 +288,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
{isViewingOldVersion && (
|
||||
<div className="mb-4 p-3 bg-amber-50 border border-amber-200 rounded-lg flex items-center justify-between">
|
||||
<Text className="text-amber-800">
|
||||
Viewing v{selectedVersion} — not the latest version (v{latestVersion})
|
||||
{t("promptsPage.promptInfo.viewingOldVersion", { version: selectedVersion, latest: latestVersion })}
|
||||
</Text>
|
||||
<TremorButton
|
||||
variant="light"
|
||||
|
|
@ -296,16 +298,16 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
if (latest) handleVersionClick(latest);
|
||||
}}
|
||||
>
|
||||
Go to latest
|
||||
{t("promptsPage.promptInfo.goToLatest")}
|
||||
</TremorButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TabGroup>
|
||||
<TabList className="mb-4">
|
||||
<Tab key="overview">Overview</Tab>
|
||||
{promptTemplate ? <Tab key="prompt-template">Prompt Template</Tab> : <></>}
|
||||
<Tab key="raw-json">Raw JSON</Tab>
|
||||
<Tab key="overview">{t("promptsPage.promptInfo.tabOverview")}</Tab>
|
||||
{promptTemplate ? <Tab key="prompt-template">{t("promptsPage.promptInfo.tabPromptTemplate")}</Tab> : <></>}
|
||||
<Tab key="raw-json">{t("promptsPage.promptInfo.tabRawJson")}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
|
|
@ -313,7 +315,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
<TabPanel>
|
||||
<Grid numItems={1} numItemsSm={2} numItemsLg={4} className="gap-4">
|
||||
<Card>
|
||||
<Text>Version</Text>
|
||||
<Text>{t("promptsPage.promptInfo.cardVersion")}</Text>
|
||||
<div className="mt-2">
|
||||
<Title>{currentVersion}</Title>
|
||||
<Badge color="blue" className="mt-1">
|
||||
|
|
@ -323,41 +325,43 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Prompt Type</Text>
|
||||
<Text>{t("promptsPage.promptInfo.cardPromptType")}</Text>
|
||||
<div className="mt-2">
|
||||
<Title>{promptData.prompt_info?.prompt_type || "-"}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Created By</Text>
|
||||
<Text>{t("promptsPage.promptInfo.cardCreatedBy")}</Text>
|
||||
<div className="mt-2">
|
||||
<Title className="text-sm">{promptData.created_by || "-"}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Created At</Text>
|
||||
<Text>{t("common.createdAt")}</Text>
|
||||
<div className="mt-2">
|
||||
<Title className="text-sm">{formatDate(promptData.created_at)}</Title>
|
||||
<Text className="text-xs">Updated: {formatDate(promptData.updated_at)}</Text>
|
||||
<Text className="text-xs">
|
||||
{t("promptsPage.promptInfo.updatedAt", { date: formatDate(promptData.updated_at) })}
|
||||
</Text>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Version History Table */}
|
||||
<Card className="mt-6">
|
||||
<Title className="mb-3">Version History — {selectedEnv}</Title>
|
||||
<Title className="mb-3">{t("promptsPage.promptInfo.versionHistoryTitle", { env: selectedEnv })}</Title>
|
||||
{loadingVersions ? (
|
||||
<Text>Loading versions...</Text>
|
||||
<Text>{t("promptsPage.promptInfo.loadingVersions")}</Text>
|
||||
) : versionHistory.length > 0 ? (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Version</TableHeaderCell>
|
||||
<TableHeaderCell>Created By</TableHeaderCell>
|
||||
<TableHeaderCell>Date</TableHeaderCell>
|
||||
<TableHeaderCell>Actions</TableHeaderCell>
|
||||
<TableHeaderCell>{t("promptsPage.promptInfo.colVersion")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("promptsPage.promptInfo.cardCreatedBy")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("promptsPage.promptInfo.colDate")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("common.actions")}</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
@ -377,7 +381,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
<span className={isSelected ? "font-bold" : ""}>v{vNum}</span>
|
||||
{isLatest && (
|
||||
<Badge color="blue" className="ml-2" size="xs">
|
||||
latest
|
||||
{t("promptsPage.promptInfo.latestBadge")}
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
|
|
@ -394,7 +398,6 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
size="xs"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Build a response-like object for the editor
|
||||
const editData = {
|
||||
prompt_spec: {
|
||||
...v,
|
||||
|
|
@ -406,7 +409,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
onEdit?.(editData);
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
{t("common.edit")}
|
||||
</TremorButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -415,7 +418,9 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<Text className="text-gray-400">No versions found in {selectedEnv}</Text>
|
||||
<Text className="text-gray-400">
|
||||
{t("promptsPage.promptInfo.noVersionsFound", { env: selectedEnv })}
|
||||
</Text>
|
||||
)}
|
||||
</Card>
|
||||
</TabPanel>
|
||||
|
|
@ -425,7 +430,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
<TabPanel>
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Prompt Template</Title>
|
||||
<Title>{t("promptsPage.promptInfo.tabPromptTemplate")}</Title>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
|
|
@ -437,18 +442,18 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
: "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
{copiedStates["prompt-content"] ? "Copied!" : "Copy Content"}
|
||||
{copiedStates["prompt-content"] ? t("common.copied") : t("promptsPage.promptInfo.copyContent")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Text className="font-medium">Template ID</Text>
|
||||
<Text className="font-medium">{t("promptsPage.promptInfo.templateId")}</Text>
|
||||
<div className="font-mono text-sm bg-gray-50 p-2 rounded">{promptTemplate.litellm_prompt_id}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Content</Text>
|
||||
<Text className="font-medium">{t("promptsPage.promptInfo.templateContent")}</Text>
|
||||
<div className="mt-2 p-4 bg-gray-50 rounded-md border overflow-auto max-h-96">
|
||||
<pre className="text-sm text-gray-800 whitespace-pre-wrap">{promptTemplate.content}</pre>
|
||||
</div>
|
||||
|
|
@ -456,7 +461,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
|
||||
{promptTemplate.metadata && Object.keys(promptTemplate.metadata).length > 0 && (
|
||||
<div>
|
||||
<Text className="font-medium">Template Metadata</Text>
|
||||
<Text className="font-medium">{t("promptsPage.promptInfo.templateMetadata")}</Text>
|
||||
<div className="mt-2 p-3 bg-gray-50 rounded-md border">
|
||||
<pre className="text-xs text-gray-800 whitespace-pre-wrap overflow-auto max-h-64">
|
||||
{JSON.stringify(promptTemplate.metadata, null, 2)}
|
||||
|
|
@ -473,7 +478,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
<TabPanel>
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Raw API Response</Title>
|
||||
<Title>{t("promptsPage.promptInfo.rawApiResponse")}</Title>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
|
|
@ -485,7 +490,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
: "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
|
||||
}`}
|
||||
>
|
||||
{copiedStates["raw-json"] ? "Copied!" : "Copy JSON"}
|
||||
{copiedStates["raw-json"] ? t("common.copied") : t("promptsPage.promptInfo.copyJson")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -501,18 +506,22 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
|
|||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Modal
|
||||
title="Delete Prompt"
|
||||
title={t("promptsPage.promptInfo.deleteModalTitle")}
|
||||
open={showDeleteConfirm}
|
||||
onOk={handleDeleteConfirm}
|
||||
onCancel={handleDeleteCancel}
|
||||
confirmLoading={isDeleting}
|
||||
okText="Delete"
|
||||
okText={t("common.delete")}
|
||||
okButtonProps={{ danger: true }}
|
||||
>
|
||||
<p>
|
||||
Are you sure you want to delete prompt: <strong>{basePromptId}</strong>?
|
||||
<Trans
|
||||
i18nKey="promptsPage.promptInfo.deleteConfirmText"
|
||||
values={{ id: basePromptId }}
|
||||
components={{ strong: <strong /> }}
|
||||
/>
|
||||
</p>
|
||||
<p>This action cannot be undone.</p>
|
||||
<p>{t("promptsPage.promptInfo.deleteCannotUndo")}</p>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Button } from "@tremor/react";
|
||||
import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { Tooltip } from "antd";
|
||||
|
|
@ -38,6 +39,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
accessToken,
|
||||
isAdmin,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [sorting, setSorting] = useState<SortingState>([{ id: "created_at", desc: true }]);
|
||||
const [modelHubData, setModelHubData] = useState<Map<string, ModelGroupInfo>>(new Map());
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
|
||||
const columns: ColumnDef<PromptSpec>[] = [
|
||||
{
|
||||
header: "Prompt ID",
|
||||
header: t("promptsPage.promptTable.colPromptId"),
|
||||
accessorKey: "prompt_id",
|
||||
cell: (info: any) => {
|
||||
const fullId = String(info.getValue() || "");
|
||||
|
|
@ -92,7 +94,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
{displayId}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy prompt ID">
|
||||
<Tooltip title={t("promptsPage.promptTable.copyPromptId")}>
|
||||
<CopyOutlined
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -106,7 +108,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Model",
|
||||
header: t("promptsPage.promptTable.colModel"),
|
||||
accessorKey: "model",
|
||||
cell: ({ row }) => {
|
||||
const prompt = row.original;
|
||||
|
|
@ -160,7 +162,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Created At",
|
||||
header: t("common.createdAt"),
|
||||
accessorKey: "created_at",
|
||||
cell: ({ row }) => {
|
||||
const prompt = row.original;
|
||||
|
|
@ -172,7 +174,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Updated At",
|
||||
header: t("common.updatedAt"),
|
||||
accessorKey: "updated_at",
|
||||
cell: ({ row }) => {
|
||||
const prompt = row.original;
|
||||
|
|
@ -184,7 +186,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Environment",
|
||||
header: t("promptsPage.promptTable.colEnvironment"),
|
||||
accessorKey: "environment",
|
||||
cell: ({ row }) => {
|
||||
const prompt = row.original;
|
||||
|
|
@ -200,7 +202,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Created By",
|
||||
header: t("promptsPage.promptTable.colCreatedBy"),
|
||||
accessorKey: "created_by",
|
||||
cell: ({ row }) => {
|
||||
const prompt = row.original;
|
||||
|
|
@ -208,7 +210,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Type",
|
||||
header: t("promptsPage.promptTable.colType"),
|
||||
accessorKey: "prompt_info.prompt_type",
|
||||
cell: ({ row }) => {
|
||||
const prompt = row.original;
|
||||
|
|
@ -222,7 +224,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
header: "Actions",
|
||||
header: t("common.actions"),
|
||||
id: "actions",
|
||||
enableSorting: false,
|
||||
cell: ({ row }: any) => {
|
||||
|
|
@ -231,7 +233,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip title="Delete prompt">
|
||||
<Tooltip title={t("promptsPage.promptTable.deletePrompt")}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
|
|
@ -302,7 +304,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>Loading...</p>
|
||||
<p>{t("common.loading")}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -320,7 +322,7 @@ const PromptTable: React.FC<PromptTableProps> = ({
|
|||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>No prompts found</p>
|
||||
<p>{t("promptsPage.promptTable.noPromptsFound")}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { Modal, Button } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ToolModalProps {
|
||||
visible: boolean;
|
||||
|
|
@ -31,6 +32,7 @@ const defaultToolJson = `{
|
|||
}`;
|
||||
|
||||
const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
const [json, setJson] = useState(initialJson || defaultToolJson);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -40,7 +42,7 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
|
|||
setError(null);
|
||||
onSave(json);
|
||||
} catch (e) {
|
||||
setError("Invalid JSON format. Please check your syntax.");
|
||||
setError(t("promptsPage.toolModal.invalidJsonError"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -53,7 +55,7 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
|
|||
<Modal
|
||||
title={
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-lg font-medium">Add Tool</span>
|
||||
<span className="text-lg font-medium">{t("promptsPage.toolModal.title")}</span>
|
||||
</div>
|
||||
}
|
||||
open={visible}
|
||||
|
|
@ -61,10 +63,10 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
|
|||
width={800}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleClose}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>,
|
||||
<Button key="save" type="primary" onClick={handleSave}>
|
||||
Add
|
||||
{t("promptsPage.toolModal.addButton")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
|
|
@ -74,7 +76,7 @@ const ToolModal: React.FC<ToolModalProps> = ({ visible, initialJson, onSave, onC
|
|||
value={json}
|
||||
onChange={(e) => setJson(e.target.value)}
|
||||
className="w-full min-h-[400px] px-4 py-3 border border-gray-300 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
placeholder="Paste your tool JSON here..."
|
||||
placeholder={t("promptsPage.toolModal.placeholder")}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -4565,6 +4565,89 @@
|
|||
"active": "Active",
|
||||
"savedToDatabase": "Saved to Database",
|
||||
"configPrompt": "Config Prompt"
|
||||
},
|
||||
"messageInput": {
|
||||
"placeholder": "Type your message... (Shift+Enter for new line)"
|
||||
},
|
||||
"variableInput": {
|
||||
"title": "Fill in template variables to start testing",
|
||||
"valuePlaceholder": "Enter value for {{varName}}"
|
||||
},
|
||||
"variableWarning": {
|
||||
"title": "Please fill in all template variables above",
|
||||
"missing": "Missing: {{list}}"
|
||||
},
|
||||
"conversationPanel": {
|
||||
"clearChat": "Clear Chat",
|
||||
"accessTokenRequired": "Access token is required",
|
||||
"fillAllVariables": "Please fill in all template variables",
|
||||
"requestCancelled": "Request cancelled",
|
||||
"chatCleared": "Chat history cleared.",
|
||||
"errorMessage": "Error: {{message}}"
|
||||
},
|
||||
"promptEditorView": {
|
||||
"failedToParsePrompt": "Failed to parse prompt data",
|
||||
"invalidJsonFormat": "Invalid JSON format",
|
||||
"failedToLoadVersion": "Failed to load prompt version",
|
||||
"accessTokenRequired": "Access token is required",
|
||||
"invalidPromptName": "Please enter a valid prompt name",
|
||||
"updateSuccess": "Prompt updated successfully!",
|
||||
"createSuccess": "Prompt created successfully!",
|
||||
"updateFailed": "Failed to update prompt",
|
||||
"saveFailed": "Failed to save prompt",
|
||||
"viewModePretty": "PRETTY",
|
||||
"viewModeDotprompt": "DOTPROMPT"
|
||||
},
|
||||
"promptInfo": {
|
||||
"failedToLoadInfo": "Failed to load prompt information",
|
||||
"promptNotFound": "Prompt not found",
|
||||
"backToPrompts": "Back to Prompts",
|
||||
"title": "Prompt Details",
|
||||
"promptStudio": "Prompt Studio",
|
||||
"deletePrompt": "Delete Prompt",
|
||||
"viewingOldVersion": "Viewing v{{version}} — not the latest version (v{{latest}})",
|
||||
"goToLatest": "Go to latest",
|
||||
"tabOverview": "Overview",
|
||||
"tabPromptTemplate": "Prompt Template",
|
||||
"tabRawJson": "Raw JSON",
|
||||
"cardVersion": "Version",
|
||||
"cardPromptType": "Prompt Type",
|
||||
"cardCreatedBy": "Created By",
|
||||
"updatedAt": "Updated: {{date}}",
|
||||
"versionHistoryTitle": "Version History — {{env}}",
|
||||
"loadingVersions": "Loading versions...",
|
||||
"colVersion": "Version",
|
||||
"colDate": "Date",
|
||||
"latestBadge": "latest",
|
||||
"noVersionsFound": "No versions found in {{env}}",
|
||||
"templateId": "Template ID",
|
||||
"templateContent": "Content",
|
||||
"templateMetadata": "Template Metadata",
|
||||
"copyContent": "Copy Content",
|
||||
"rawApiResponse": "Raw API Response",
|
||||
"copyJson": "Copy JSON",
|
||||
"deleteModalTitle": "Delete Prompt",
|
||||
"deleteConfirmText": "Are you sure you want to delete prompt: <strong>{{id}}</strong>?",
|
||||
"deleteCannotUndo": "This action cannot be undone.",
|
||||
"deleteSuccess": "Prompt \"{{id}}\" deleted successfully",
|
||||
"deleteFailed": "Failed to delete prompt",
|
||||
"failedToLoadVersion": "Failed to load version v{{version}}"
|
||||
},
|
||||
"promptTable": {
|
||||
"colPromptId": "Prompt ID",
|
||||
"colModel": "Model",
|
||||
"colEnvironment": "Environment",
|
||||
"colCreatedBy": "Created By",
|
||||
"colType": "Type",
|
||||
"copyPromptId": "Copy prompt ID",
|
||||
"deletePrompt": "Delete prompt",
|
||||
"noPromptsFound": "No prompts found"
|
||||
},
|
||||
"toolModal": {
|
||||
"title": "Add Tool",
|
||||
"invalidJsonError": "Invalid JSON format. Please check your syntax.",
|
||||
"placeholder": "Paste your tool JSON here...",
|
||||
"addButton": "Add"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4565,6 +4565,89 @@
|
|||
"active": "当前",
|
||||
"savedToDatabase": "已保存到数据库",
|
||||
"configPrompt": "配置提示词"
|
||||
},
|
||||
"messageInput": {
|
||||
"placeholder": "输入消息...(Shift+Enter 换行)"
|
||||
},
|
||||
"variableInput": {
|
||||
"title": "填写模板变量以开始测试",
|
||||
"valuePlaceholder": "输入 {{varName}} 的值"
|
||||
},
|
||||
"variableWarning": {
|
||||
"title": "请填写上方所有模板变量",
|
||||
"missing": "缺少:{{list}}"
|
||||
},
|
||||
"conversationPanel": {
|
||||
"clearChat": "清空对话",
|
||||
"accessTokenRequired": "访问令牌不能为空",
|
||||
"fillAllVariables": "请填写所有模板变量",
|
||||
"requestCancelled": "请求已取消",
|
||||
"chatCleared": "对话历史已清空。",
|
||||
"errorMessage": "错误:{{message}}"
|
||||
},
|
||||
"promptEditorView": {
|
||||
"failedToParsePrompt": "解析提示词数据失败",
|
||||
"invalidJsonFormat": "JSON 格式无效",
|
||||
"failedToLoadVersion": "加载提示词版本失败",
|
||||
"accessTokenRequired": "访问令牌不能为空",
|
||||
"invalidPromptName": "请输入有效的提示词名称",
|
||||
"updateSuccess": "提示词更新成功!",
|
||||
"createSuccess": "提示词创建成功!",
|
||||
"updateFailed": "更新提示词失败",
|
||||
"saveFailed": "保存提示词失败",
|
||||
"viewModePretty": "可视化",
|
||||
"viewModeDotprompt": "DOTPROMPT"
|
||||
},
|
||||
"promptInfo": {
|
||||
"failedToLoadInfo": "加载提示词信息失败",
|
||||
"promptNotFound": "提示词不存在",
|
||||
"backToPrompts": "返回提示词列表",
|
||||
"title": "提示词详情",
|
||||
"promptStudio": "提示词工作室",
|
||||
"deletePrompt": "删除提示词",
|
||||
"viewingOldVersion": "正在查看 v{{version}} — 非最新版本(v{{latest}})",
|
||||
"goToLatest": "跳转到最新版本",
|
||||
"tabOverview": "概览",
|
||||
"tabPromptTemplate": "提示词模板",
|
||||
"tabRawJson": "原始 JSON",
|
||||
"cardVersion": "版本",
|
||||
"cardPromptType": "提示词类型",
|
||||
"cardCreatedBy": "创建者",
|
||||
"updatedAt": "更新于:{{date}}",
|
||||
"versionHistoryTitle": "版本历史 — {{env}}",
|
||||
"loadingVersions": "加载版本中...",
|
||||
"colVersion": "版本",
|
||||
"colDate": "日期",
|
||||
"latestBadge": "最新",
|
||||
"noVersionsFound": "在 {{env}} 中未找到版本",
|
||||
"templateId": "模板 ID",
|
||||
"templateContent": "内容",
|
||||
"templateMetadata": "模板元数据",
|
||||
"copyContent": "复制内容",
|
||||
"rawApiResponse": "原始 API 响应",
|
||||
"copyJson": "复制 JSON",
|
||||
"deleteModalTitle": "删除提示词",
|
||||
"deleteConfirmText": "确定要删除提示词:<strong>{{id}}</strong>?",
|
||||
"deleteCannotUndo": "此操作无法撤销。",
|
||||
"deleteSuccess": "提示词「{{id}}」已成功删除",
|
||||
"deleteFailed": "删除提示词失败",
|
||||
"failedToLoadVersion": "加载版本 v{{version}} 失败"
|
||||
},
|
||||
"promptTable": {
|
||||
"colPromptId": "提示词 ID",
|
||||
"colModel": "模型",
|
||||
"colEnvironment": "环境",
|
||||
"colCreatedBy": "创建者",
|
||||
"colType": "类型",
|
||||
"copyPromptId": "复制提示词 ID",
|
||||
"deletePrompt": "删除提示词",
|
||||
"noPromptsFound": "未找到提示词"
|
||||
},
|
||||
"toolModal": {
|
||||
"title": "添加工具",
|
||||
"invalidJsonError": "JSON 格式无效,请检查语法。",
|
||||
"placeholder": "在此粘贴工具 JSON...",
|
||||
"addButton": "添加"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue