This commit is contained in:
hailin 2025-04-19 20:37:09 +08:00
parent 475b67ee02
commit 05a8685504
14 changed files with 174 additions and 57 deletions

View File

@ -13,11 +13,15 @@ import { SidebarItem } from "../all/sidebar-display-item"
import { AssistantRetrievalSelect } from "./assistant-retrieval-select"
import { AssistantToolSelect } from "./assistant-tool-select"
import { useTranslation } from 'react-i18next'
interface AssistantItemProps {
assistant: Tables<"assistants">
}
export const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {
const { t } = useTranslation()
const { selectedWorkspace, assistantImages } = useContext(ChatbotUIContext)
const [name, setName] = useState(assistant.name)
@ -167,10 +171,10 @@ export const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {
}) => (
<>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input
placeholder="Assistant name..."
placeholder={t("side.assistantNamePlaceholder")}
value={name}
onChange={e => setName(e.target.value)}
maxLength={ASSISTANT_NAME_MAX}
@ -178,10 +182,10 @@ export const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {
</div>
<div className="space-y-1 pt-2">
<Label>Description</Label>
<Label>{t("side.description")}</Label>
<Input
placeholder="Assistant description..."
placeholder={t("side.assistantDescriptionPlaceholder")}
value={description}
onChange={e => setDescription(e.target.value)}
maxLength={ASSISTANT_DESCRIPTION_MAX}
@ -189,7 +193,7 @@ export const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {
</div>
<div className="space-y-1">
<Label>Image</Label>
<Label>{t("side.image")}</Label>
<ImagePicker
src={imageLink}
@ -208,7 +212,7 @@ export const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {
/>
<div className="space-y-1 pt-2">
<Label>Files & Collections</Label>
<Label>{t("side.filesAndCollections")}</Label>
<AssistantRetrievalSelect
selectedAssistantRetrievalItems={
@ -270,7 +274,7 @@ export const AssistantItem: FC<AssistantItemProps> = ({ assistant }) => {
</div>
<div className="space-y-1">
<Label>Tools</Label>
<Label>{t("side.tools")}</Label>
<AssistantToolSelect
selectedAssistantTools={

View File

@ -15,6 +15,8 @@ import {
import { FileIcon } from "lucide-react"
import { FC, useContext, useEffect, useRef, useState } from "react"
import { useTranslation } from 'react-i18next'
interface AssistantRetrievalSelectProps {
selectedAssistantRetrievalItems: Tables<"files">[] | Tables<"collections">[]
onAssistantRetrievalItemsSelect: (
@ -26,6 +28,8 @@ export const AssistantRetrievalSelect: FC<AssistantRetrievalSelectProps> = ({
selectedAssistantRetrievalItems,
onAssistantRetrievalItemsSelect
}) => {
const { t } = useTranslation()
const { files, collections } = useContext(ChatbotUIContext)
const inputRef = useRef<HTMLInputElement>(null)
@ -67,7 +71,7 @@ export const AssistantRetrievalSelect: FC<AssistantRetrievalSelectProps> = ({
>
<div className="flex items-center">
<div className="ml-2 flex items-center">
{selectedAssistantRetrievalItems.length} files selected
{selectedAssistantRetrievalItems.length} {t("side.filesSelected")}
</div>
</div>
@ -82,7 +86,7 @@ export const AssistantRetrievalSelect: FC<AssistantRetrievalSelectProps> = ({
>
<Input
ref={inputRef}
placeholder="Search files..."
placeholder={t("side.searchFilesPlaceholder")}
value={search}
onChange={e => setSearch(e.target.value)}
onKeyDown={e => e.stopPropagation()}

View File

@ -14,6 +14,8 @@ import {
} from "@tabler/icons-react"
import { FC, useContext, useEffect, useRef, useState } from "react"
import { useTranslation } from 'react-i18next'
interface AssistantToolSelectProps {
selectedAssistantTools: Tables<"tools">[]
onAssistantToolsSelect: (tool: Tables<"tools">) => void
@ -23,6 +25,8 @@ export const AssistantToolSelect: FC<AssistantToolSelectProps> = ({
selectedAssistantTools,
onAssistantToolsSelect
}) => {
const { t } = useTranslation()
const { tools } = useContext(ChatbotUIContext)
const inputRef = useRef<HTMLInputElement>(null)
@ -64,7 +68,7 @@ export const AssistantToolSelect: FC<AssistantToolSelectProps> = ({
>
<div className="flex items-center">
<div className="ml-2 flex items-center">
{selectedAssistantTools.length} tools selected
{selectedAssistantTools.length} {t("side.toolsSelected")}
</div>
</div>
@ -79,7 +83,7 @@ export const AssistantToolSelect: FC<AssistantToolSelectProps> = ({
>
<Input
ref={inputRef}
placeholder="Search tools..."
placeholder={t("side.searchToolsPlaceholder")}
value={search}
onChange={e => setSearch(e.target.value)}
onKeyDown={e => e.stopPropagation()}

View File

@ -11,6 +11,8 @@ import { CollectionFile } from "@/types"
import { IconChevronDown, IconCircleCheckFilled } from "@tabler/icons-react"
import { FC, useContext, useEffect, useRef, useState } from "react"
import { useTranslation } from 'react-i18next'
interface CollectionFileSelectProps {
selectedCollectionFiles: CollectionFile[]
onCollectionFileSelect: (file: CollectionFile) => void
@ -20,6 +22,8 @@ export const CollectionFileSelect: FC<CollectionFileSelectProps> = ({
selectedCollectionFiles,
onCollectionFileSelect
}) => {
const { t } = useTranslation()
const { files } = useContext(ChatbotUIContext)
const inputRef = useRef<HTMLInputElement>(null)
@ -61,7 +65,7 @@ export const CollectionFileSelect: FC<CollectionFileSelectProps> = ({
>
<div className="flex items-center">
<div className="ml-2 flex items-center">
{selectedCollectionFiles.length} files selected
{selectedCollectionFiles.length} {t("side.filesSelected")}
</div>
</div>
@ -76,7 +80,7 @@ export const CollectionFileSelect: FC<CollectionFileSelectProps> = ({
>
<Input
ref={inputRef}
placeholder="Search files..."
placeholder={t("side.searchFilesPlaceholder")}
value={search}
onChange={e => setSearch(e.target.value)}
onKeyDown={e => e.stopPropagation()}

View File

@ -8,11 +8,16 @@ import { FC, useState } from "react"
import { SidebarItem } from "../all/sidebar-display-item"
import { CollectionFileSelect } from "./collection-file-select"
import { useTranslation } from 'react-i18next'
interface CollectionItemProps {
collection: Tables<"collections">
}
export const CollectionItem: FC<CollectionItemProps> = ({ collection }) => {
const { t } = useTranslation()
const [name, setName] = useState(collection.name)
const [isTyping, setIsTyping] = useState(false)
const [description, setDescription] = useState(collection.description)
@ -59,7 +64,7 @@ export const CollectionItem: FC<CollectionItemProps> = ({ collection }) => {
return (
<>
<div className="space-y-1">
<Label>Files</Label>
<Label>{t("side.files")}</Label>
<CollectionFileSelect
selectedCollectionFiles={
@ -89,10 +94,10 @@ export const CollectionItem: FC<CollectionItemProps> = ({ collection }) => {
</div>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input
placeholder="Collection name..."
placeholder={t("side.collectionNamePlaceholder")}
value={name}
onChange={e => setName(e.target.value)}
maxLength={COLLECTION_NAME_MAX}
@ -100,10 +105,10 @@ export const CollectionItem: FC<CollectionItemProps> = ({ collection }) => {
</div>
<div className="space-y-1">
<Label>Description</Label>
<Label>{t("side.description")}</Label>
<Input
placeholder="Collection description..."
placeholder={t("side.collectionDescriptionPlaceholder")}
value={description}
onChange={e => setDescription(e.target.value)}
maxLength={COLLECTION_DESCRIPTION_MAX}

View File

@ -8,6 +8,8 @@ import { CollectionFile } from "@/types"
import { FC, useContext, useState } from "react"
import { CollectionFileSelect } from "./collection-file-select"
import { useTranslation } from 'react-i18next'
interface CreateCollectionProps {
isOpen: boolean
onOpenChange: (isOpen: boolean) => void
@ -17,6 +19,8 @@ export const CreateCollection: FC<CreateCollectionProps> = ({
isOpen,
onOpenChange
}) => {
const { t } = useTranslation()
const { profile, selectedWorkspace } = useContext(ChatbotUIContext)
const [name, setName] = useState("")
@ -64,7 +68,7 @@ export const CreateCollection: FC<CreateCollectionProps> = ({
renderInputs={() => (
<>
<div className="space-y-1">
<Label>Files</Label>
<Label>{t("side.files")}</Label>
<CollectionFileSelect
selectedCollectionFiles={selectedCollectionFiles}
@ -73,7 +77,7 @@ export const CreateCollection: FC<CreateCollectionProps> = ({
</div>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input
placeholder="Collection name..."
@ -84,10 +88,10 @@ export const CreateCollection: FC<CreateCollectionProps> = ({
</div>
<div className="space-y-1">
<Label>Description</Label>
<Label>{t("side.description")}</Label>
<Input
placeholder="Collection description..."
placeholder={t("side.collectionDescriptionPlaceholder")}
value={description}
onChange={e => setDescription(e.target.value)}
maxLength={COLLECTION_DESCRIPTION_MAX}

View File

@ -7,12 +7,16 @@ import { FILE_DESCRIPTION_MAX, FILE_NAME_MAX } from "@/db/limits"
import { TablesInsert } from "@/supabase/types"
import { FC, useContext, useState } from "react"
import { useTranslation } from 'react-i18next'
interface CreateFileProps {
isOpen: boolean
onOpenChange: (isOpen: boolean) => void
}
export const CreateFile: FC<CreateFileProps> = ({ isOpen, onOpenChange }) => {
const { t } = useTranslation()
const { profile, selectedWorkspace } = useContext(ChatbotUIContext)
const [name, setName] = useState("")
@ -56,7 +60,7 @@ export const CreateFile: FC<CreateFileProps> = ({ isOpen, onOpenChange }) => {
renderInputs={() => (
<>
<div className="space-y-1">
<Label>File</Label>
<Label>{t("side.file")}</Label>
<Input
type="file"
@ -66,10 +70,10 @@ export const CreateFile: FC<CreateFileProps> = ({ isOpen, onOpenChange }) => {
</div>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input
placeholder="File name..."
placeholder={t("side.fileNamePlaceholder")}
value={name}
onChange={e => setName(e.target.value)}
maxLength={FILE_NAME_MAX}
@ -77,10 +81,10 @@ export const CreateFile: FC<CreateFileProps> = ({ isOpen, onOpenChange }) => {
</div>
<div className="space-y-1">
<Label>Description</Label>
<Label>{t("side.description")}</Label>
<Input
placeholder="File description..."
placeholder={t("side.fileDescriptionPlaceholder")}
value={name}
onChange={e => setDescription(e.target.value)}
maxLength={FILE_DESCRIPTION_MAX}

View File

@ -6,12 +6,14 @@ import { getFileFromStorage } from "@/db/storage/files"
import { Tables } from "@/supabase/types"
import { FC, useState } from "react"
import { SidebarItem } from "../all/sidebar-display-item"
import { useTranslation } from 'react-i18next'
interface FileItemProps {
file: Tables<"files">
}
export const FileItem: FC<FileItemProps> = ({ file }) => {
const { t } = useTranslation()
const [name, setName] = useState(file.name)
const [isTyping, setIsTyping] = useState(false)
const [description, setDescription] = useState(file.description)
@ -34,7 +36,7 @@ export const FileItem: FC<FileItemProps> = ({ file }) => {
className="cursor-pointer underline hover:opacity-50"
onClick={getLinkAndView}
>
View {file.name}
{t("side.view")} {file.name}
</div>
<div className="flex flex-col justify-between">
@ -46,10 +48,10 @@ export const FileItem: FC<FileItemProps> = ({ file }) => {
</div>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input
placeholder="File name..."
placeholder={t("side.fileNamePlaceholder")}
value={name}
onChange={e => setName(e.target.value)}
maxLength={FILE_NAME_MAX}
@ -57,10 +59,10 @@ export const FileItem: FC<FileItemProps> = ({ file }) => {
</div>
<div className="space-y-1">
<Label>Description</Label>
<Label>{t("side.description")}</Label>
<Input
placeholder="File description..."
placeholder={t("side.fileDescriptionPlaceholder")}
value={description}
onChange={e => setDescription(e.target.value)}
maxLength={FILE_DESCRIPTION_MAX}

View File

@ -16,6 +16,7 @@ import { ContentType } from "@/types"
import { IconTrash } from "@tabler/icons-react"
import { FC, useContext, useRef, useState } from "react"
import { toast } from "sonner"
import { useTranslation } from 'react-i18next'
interface DeleteFolderProps {
folder: Tables<"folders">
@ -26,6 +27,7 @@ export const DeleteFolder: FC<DeleteFolderProps> = ({
folder,
contentType
}) => {
const { t } = useTranslation()
const {
setChats,
setFolders,
@ -107,16 +109,16 @@ export const DeleteFolder: FC<DeleteFolderProps> = ({
<DialogContent className="min-w-[550px]">
<DialogHeader>
<DialogTitle>Delete {folder.name}</DialogTitle>
<DialogTitle>{t("side.delete")} {folder.name}</DialogTitle>
<DialogDescription>
Are you sure you want to delete this folder?
{t("side.confirmDeleteFolder")}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost" onClick={() => setShowFolderDialog(false)}>
Cancel
{t("side.cancel")}
</Button>
<Button
@ -124,7 +126,7 @@ export const DeleteFolder: FC<DeleteFolderProps> = ({
variant="destructive"
onClick={handleDeleteFolderAndItems}
>
Delete Folder & Included Items
{t("side.deleteFolderWithContents")}
</Button>
<Button
@ -132,7 +134,7 @@ export const DeleteFolder: FC<DeleteFolderProps> = ({
variant="destructive"
onClick={handleDeleteFolderOnly}
>
Delete Folder Only
{t("side.deleteFolderOnly")}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -14,12 +14,14 @@ import { updateFolder } from "@/db/folders"
import { Tables } from "@/supabase/types"
import { IconEdit } from "@tabler/icons-react"
import { FC, useContext, useRef, useState } from "react"
import { useTranslation } from 'react-i18next'
interface UpdateFolderProps {
folder: Tables<"folders">
}
export const UpdateFolder: FC<UpdateFolderProps> = ({ folder }) => {
const { t } = useTranslation()
const { setFolders } = useContext(ChatbotUIContext)
const buttonRef = useRef<HTMLButtonElement>(null)
@ -52,22 +54,22 @@ export const UpdateFolder: FC<UpdateFolderProps> = ({ folder }) => {
<DialogContent onKeyDown={handleKeyDown}>
<DialogHeader>
<DialogTitle>Edit Folder</DialogTitle>
<DialogTitle>{t("side.editFolder")}</DialogTitle>
</DialogHeader>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input value={name} onChange={e => setName(e.target.value)} />
</div>
<DialogFooter>
<Button variant="ghost" onClick={() => setShowFolderDialog(false)}>
Cancel
{t("side.cancel")}
</Button>
<Button ref={buttonRef} onClick={handleUpdateFolder}>
Save
{t("side.save")}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -5,6 +5,7 @@ import { ChatbotUIContext } from "@/context/context"
import { MODEL_NAME_MAX } from "@/db/limits"
import { TablesInsert } from "@/supabase/types"
import { FC, useContext, useState } from "react"
import { useTranslation, Trans } from 'react-i18next'
interface CreateModelProps {
isOpen: boolean
@ -12,6 +13,7 @@ interface CreateModelProps {
}
export const CreateModel: FC<CreateModelProps> = ({ isOpen, onOpenChange }) => {
const { t } = useTranslation()
const { profile, selectedWorkspace } = useContext(ChatbotUIContext)
const [isTyping, setIsTyping] = useState(false)
@ -45,19 +47,20 @@ export const CreateModel: FC<CreateModelProps> = ({ isOpen, onOpenChange }) => {
renderInputs={() => (
<>
<div className="space-y-1.5 text-sm">
<div>Create a custom model.</div>
<div>{t("side.createCustomModel")}</div>
<div>
Your API <span className="font-bold">*must*</span> be compatible
with the OpenAI SDK.
{/* Your API <span className="font-bold">*must*</span> be compatible
with the OpenAI SDK. */}
<Trans i18nKey="side.apiCompatibilityWarning" components={{ strong: <span className="font-bold" /> }} />
</div>
</div>
<div className="space-y-1">
<Label>Name</Label>
<Label>{t("side.name")}</Label>
<Input
placeholder="Model name..."
placeholder={t("side.modelNamePlaceholder")}
value={name}
onChange={e => setName(e.target.value)}
maxLength={MODEL_NAME_MAX}
@ -65,42 +68,43 @@ export const CreateModel: FC<CreateModelProps> = ({ isOpen, onOpenChange }) => {
</div>
<div className="space-y-1">
<Label>Model ID</Label>
<Label>{t("side.modelId")}</Label>
<Input
placeholder="Model ID..."
placeholder={t("side.modelIdPlaceholder")}
value={modelId}
onChange={e => setModelId(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label>Base URL</Label>
<Label>{t("side.baseUrl")}</Label>
<Input
placeholder="Base URL..."
placeholder={t("side.baseUrlPlaceholder")}
value={baseUrl}
onChange={e => setBaseUrl(e.target.value)}
/>
<div className="pt-1 text-xs italic">
Your API must be compatible with the OpenAI SDK.
{t("side.apiCompatibilityNotice")}
</div>
</div>
<div className="space-y-1">
<Label>API Key</Label>
<Label>{t("side.apiKey")}</Label>
<Input
type="password"
placeholder="API Key..."
placeholder={t("side.apiKeyPlaceholder")}
value={apiKey}
onChange={e => setApiKey(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label>Max Context Length</Label>
<Label>{t("side.maxContextLength")}</Label>
<Input
type="number"

View File

@ -166,7 +166,33 @@
"editChat": "Edit Chat",
"confirmDelete": "Are you sure you want to delete?",
"edit": "Edit",
"assignedWorkspaces": "Assigned Workspaces"
"assignedWorkspaces": "Assigned Workspaces",
"searchFilesPlaceholder": "Search files...",
"filesSelected": "files selected",
"toolsSelected": "tools selected",
"searchToolsPlaceholder": "Search tools...",
"files": "Files",
"file": "File",
"collectionDescriptionPlaceholder": "Collection description...",
"collectionNamePlaceholder": "Collection name...",
"fileNamePlaceholder": "File name...",
"fileDescriptionPlaceholder": "File description...",
"view": "View",
"confirmDeleteFolder": "Are you sure you want to delete this folder?",
"deleteFolderWithContents": "Delete Folder & Included Items",
"deleteFolderOnly": "Delete Folder Only",
"editFolder": "Edit Folder",
"createCustomModel": "Create a custom model.",
"apiCompatibilityWarning": "Your API <strong>*must*</strong> be compatible with the OpenAI SDK.",
"apiCompatibilityNotice": "Your API must be compatible with the OpenAI SDK.",
"modelNamePlaceholder": "Model name...",
"modelId": "Model ID",
"modelIdPlaceholder": "Model ID...",
"baseUrl": "Base URL",
"baseUrlPlaceholder": "Base URL...",
"apiKey": "API Key",
"apiKeyPlaceholder": "API Key...",
"maxContextLength": "Max Context Length"
},
"contentType": {

View File

@ -165,7 +165,33 @@
"editChat": "チャットを編集",
"confirmDelete": "本当に削除しますか?",
"edit": "編集",
"assignedWorkspaces": "割り当てられたワークスペース"
"assignedWorkspaces": "割り当てられたワークスペース",
"searchFilesPlaceholder": "ファイルを検索...",
"filesSelected": "件のファイルが選択されました",
"toolsSelected": "件のツールが選択されました",
"searchToolsPlaceholder": "ツールを検索...",
"files": "ファイル",
"file": "ファイル",
"collectionDescriptionPlaceholder": "コレクションの説明...",
"collectionNamePlaceholder": "コレクション名...",
"fileNamePlaceholder": "ファイル名...",
"fileDescriptionPlaceholder": "ファイルの説明...",
"view": "表示",
"confirmDeleteFolder": "このフォルダーを削除してもよろしいですか?",
"deleteFolderWithContents": "フォルダーと含まれる項目を削除",
"deleteFolderOnly": "フォルダーのみ削除",
"editFolder": "フォルダーを編集",
"createCustomModel": "カスタムモデルを作成します。",
"apiCompatibilityWarning": "あなたの API は <strong>*完全に*</strong> OpenAI SDK と互換である必要があります。",
"apiCompatibilityNotice": "あなたの API は OpenAI SDK と互換である必要があります。",
"modelNamePlaceholder": "モデル名...",
"modelId": "モデル ID",
"modelIdPlaceholder": "モデル ID...",
"baseUrl": "ベース URL",
"baseUrlPlaceholder": "ベース URL...",
"apiKey": "API キー",
"apiKeyPlaceholder": "API キー...",
"maxContextLength": "最大コンテキスト長"
},
"contentType": {

View File

@ -165,7 +165,33 @@
"editChat": "编辑对话",
"confirmDelete": "你确定要删除吗?",
"edit": "编辑",
"assignedWorkspaces": "已分配的工作区"
"assignedWorkspaces": "已分配的工作区",
"searchFilesPlaceholder": "搜索文件...",
"filesSelected": "个文件已选择",
"toolsSelected": "个工具已选择",
"searchToolsPlaceholder": "搜索工具...",
"files": "文件",
"file": "文件",
"collectionDescriptionPlaceholder": "集合描述...",
"collectionNamePlaceholder": "集合名称...",
"fileNamePlaceholder": "文件名称...",
"fileDescriptionPlaceholder": "文件描述...",
"view": "查看",
"confirmDeleteFolder": "你确定要删除这个文件夹吗?",
"deleteFolderWithContents": "删除文件夹及其内容",
"deleteFolderOnly": "仅删除文件夹",
"editFolder": "编辑文件夹",
"createCustomModel": "创建自定义模型。",
"apiCompatibilityWarning": "您的 API <strong>*必须*</strong> 与 OpenAI SDK 兼容。",
"apiCompatibilityNotice": "您的 API 必须与 OpenAI SDK 兼容。",
"modelNamePlaceholder": "模型名称...",
"modelId": "模型 ID",
"modelIdPlaceholder": "模型 ID...",
"baseUrl": "基础地址",
"baseUrlPlaceholder": "基础地址...",
"apiKey": "API 密钥",
"apiKeyPlaceholder": "API 密钥...",
"maxContextLength": "最大上下文长度"
},
"contentType": {