feat(ui): i18n for prompts batch 2

This commit is contained in:
吹雪 2026-06-10 23:10:59 +09:00
parent 524569b9ae
commit 129811bce8
11 changed files with 285 additions and 81 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>
)}

View file

@ -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>) => {

View file

@ -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>

View file

@ -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>
);

View file

@ -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>

View file

@ -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>

View file

@ -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"
}
}
}

View file

@ -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": "添加"
}
}
}