mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-22 03:01:07 +00:00
Edit space names from selector (#949)
This commit is contained in:
parent
efd25bbd27
commit
2fe01a5290
5 changed files with 412 additions and 114 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useMemo, useEffect } from "react"
|
||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react"
|
||||
import Image from "next/image"
|
||||
import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts"
|
||||
import { Dialog, DialogContent } from "@repo/ui/components/dialog"
|
||||
|
|
@ -17,6 +17,8 @@ import {
|
|||
ArrowRight,
|
||||
BookOpen,
|
||||
Loader,
|
||||
Pencil,
|
||||
Check,
|
||||
} from "lucide-react"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -40,6 +42,7 @@ import {
|
|||
type PluginInfo,
|
||||
} from "@/lib/plugin-catalog"
|
||||
import { InstallSteps, PillButton } from "./integrations/install-steps"
|
||||
import { useProjectMutations } from "@/hooks/use-project-mutations"
|
||||
|
||||
interface SelectSpacesModalProps {
|
||||
isOpen: boolean
|
||||
|
|
@ -85,6 +88,14 @@ export function SelectSpacesModal({
|
|||
onDeleteRequest,
|
||||
}: SelectSpacesModalProps) {
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [editingProject, setEditingProject] = useState<{
|
||||
id: string
|
||||
containerTag: string
|
||||
originalName: string
|
||||
name: string
|
||||
} | null>(null)
|
||||
const editInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const editingContainerTag = editingProject?.containerTag
|
||||
const currentSelection = selectedProjects[0] ?? ""
|
||||
|
||||
const pluginTags = useMemo(
|
||||
|
|
@ -181,6 +192,9 @@ export function SelectSpacesModal({
|
|||
|
||||
const [activeCategory, setActiveCategory] =
|
||||
useState<CategoryId>(defaultCategory)
|
||||
const activeDiscoverId = activeCategory.startsWith("discover:")
|
||||
? activeCategory.slice("discover:".length)
|
||||
: null
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) setActiveCategory(defaultCategory)
|
||||
|
|
@ -195,6 +209,7 @@ export function SelectSpacesModal({
|
|||
pluginId: string
|
||||
key: string
|
||||
} | null>(null)
|
||||
const { updateProjectMutation } = useProjectMutations()
|
||||
|
||||
const { data: availablePluginsData } = useQuery({
|
||||
queryKey: ["plugins"],
|
||||
|
|
@ -208,17 +223,17 @@ export function SelectSpacesModal({
|
|||
return (await res.json()) as { plugins: string[] }
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
enabled: isOpen,
|
||||
enabled: isOpen && !!activeDiscoverId,
|
||||
})
|
||||
|
||||
const { data: apiKeys = [] } = useQuery({
|
||||
queryKey: ["api-keys", org?.id],
|
||||
enabled: isOpen && !!org?.id,
|
||||
enabled: isOpen && !!activeDiscoverId && !!org?.id,
|
||||
queryFn: async () => {
|
||||
if (!org?.id) return []
|
||||
const data = await authClient.apiKey.list({
|
||||
const data = (await authClient.apiKey.list({
|
||||
fetchOptions: { query: { metadata: { organizationId: org.id } } },
|
||||
})
|
||||
})) as unknown as { metadata?: Record<string, unknown> | null }[]
|
||||
return data.filter((key) => key.metadata?.organizationId === org.id)
|
||||
},
|
||||
})
|
||||
|
|
@ -303,20 +318,80 @@ export function SelectSpacesModal({
|
|||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setNewKey(null)
|
||||
setEditingProject(null)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
onClose()
|
||||
setSearchQuery("")
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!editingContainerTag) return
|
||||
const frame = requestAnimationFrame(() => {
|
||||
editInputRef.current?.focus()
|
||||
editInputRef.current?.select()
|
||||
})
|
||||
return () => cancelAnimationFrame(frame)
|
||||
}, [editingContainerTag])
|
||||
|
||||
const handleSelect = (containerTag: string) => {
|
||||
onApply([containerTag])
|
||||
setSearchQuery("")
|
||||
}
|
||||
const handleOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
if (!open) {
|
||||
onClose()
|
||||
setSearchQuery("")
|
||||
setEditingProject(null)
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
)
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(containerTag: string) => {
|
||||
setEditingProject(null)
|
||||
onApply([containerTag])
|
||||
setSearchQuery("")
|
||||
},
|
||||
[onApply],
|
||||
)
|
||||
|
||||
const startEditing = useCallback((project: ContainerTagListType) => {
|
||||
const name = project.name ?? project.containerTag
|
||||
setEditingProject({
|
||||
id: project.id,
|
||||
containerTag: project.containerTag,
|
||||
originalName: name,
|
||||
name,
|
||||
})
|
||||
}, [])
|
||||
|
||||
const cancelEditing = useCallback(() => {
|
||||
setEditingProject(null)
|
||||
}, [])
|
||||
|
||||
const saveEditing = useCallback(() => {
|
||||
if (!editingProject) return
|
||||
const nextName = editingProject.name.trim()
|
||||
const currentName = editingProject.originalName.trim()
|
||||
if (!nextName || nextName === currentName) return
|
||||
|
||||
updateProjectMutation.mutate(
|
||||
{ containerTag: editingProject.containerTag, name: nextName },
|
||||
{
|
||||
onSuccess: () => setEditingProject(null),
|
||||
},
|
||||
)
|
||||
}, [editingProject, updateProjectMutation])
|
||||
|
||||
const handleEditKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
saveEditing()
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
cancelEditing()
|
||||
}
|
||||
},
|
||||
[cancelEditing, saveEditing],
|
||||
)
|
||||
|
||||
const filteredProjects = useMemo(() => {
|
||||
const byCategory = allSpaces.filter((p) => {
|
||||
|
|
@ -367,28 +442,31 @@ export function SelectSpacesModal({
|
|||
[filteredProjects, recentSet],
|
||||
)
|
||||
|
||||
const renderRow = (project: ContainerTagListType) => {
|
||||
const isSelected = currentSelection === project.containerTag
|
||||
const plugin = detectPluginSpace(project.containerTag)
|
||||
const pluginProjectName = pluginMetaMap.get(
|
||||
project.containerTag,
|
||||
)?.projectName
|
||||
const pluginIdLabel = pluginProjectName || plugin?.projectId
|
||||
const isDefault = project.containerTag === DEFAULT_PROJECT_ID
|
||||
return (
|
||||
<div
|
||||
key={project.containerTag}
|
||||
className={cn(
|
||||
"group flex min-w-0 max-w-full items-center gap-3 w-full px-3 py-2.5 rounded-[12px] transition-colors",
|
||||
isSelected
|
||||
? "bg-[#14161A] shadow-inside-out"
|
||||
: "hover:bg-[#14161A]/50",
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelect(project.containerTag)}
|
||||
className="flex min-w-0 flex-1 items-center gap-3 text-left cursor-pointer focus:outline-none focus:ring-0"
|
||||
const renderRow = useCallback(
|
||||
(project: ContainerTagListType) => {
|
||||
const isSelected = currentSelection === project.containerTag
|
||||
const plugin = detectPluginSpace(project.containerTag)
|
||||
const pluginProjectName = pluginMetaMap.get(
|
||||
project.containerTag,
|
||||
)?.projectName
|
||||
const pluginIdLabel = pluginProjectName || plugin?.projectId
|
||||
const isDefault = project.containerTag === DEFAULT_PROJECT_ID
|
||||
const canEdit = !isDefault && !plugin
|
||||
const isEditing = editingProject?.containerTag === project.containerTag
|
||||
const trimmedEditName = editingProject?.name.trim() ?? ""
|
||||
const isSaveDisabled =
|
||||
!trimmedEditName ||
|
||||
trimmedEditName === editingProject?.originalName.trim() ||
|
||||
updateProjectMutation.isPending
|
||||
return (
|
||||
<div
|
||||
key={project.containerTag}
|
||||
className={cn(
|
||||
"group flex min-w-0 max-w-full items-center gap-3 w-full px-3 py-2.5 rounded-[12px] transition-colors",
|
||||
isSelected
|
||||
? "bg-[#14161A] shadow-inside-out"
|
||||
: "hover:bg-[#14161A]/50",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -400,65 +478,139 @@ export function SelectSpacesModal({
|
|||
<div className="w-2 h-2 rounded-full bg-[#4BA0FA]" />
|
||||
)}
|
||||
</div>
|
||||
{plugin ? (
|
||||
plugin.iconSrc ? (
|
||||
<Image
|
||||
src={plugin.iconSrc}
|
||||
alt=""
|
||||
width={20}
|
||||
height={20}
|
||||
className="shrink-0 rounded-[4px]"
|
||||
aria-hidden
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="shrink-0 flex items-center justify-center w-5 h-5 rounded-[4px] bg-[#1E232B] text-[#FAFAFA] text-[11px] font-semibold uppercase"
|
||||
aria-hidden
|
||||
>
|
||||
{pluginInitial(plugin.label)}
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<span className="shrink-0 text-lg">{project.emoji || "📁"}</span>
|
||||
)}
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate text-[#fafafa] text-sm font-medium"
|
||||
title={project.containerTag}
|
||||
>
|
||||
{plugin ? (
|
||||
<>
|
||||
{plugin.label}
|
||||
{pluginIdLabel && (
|
||||
<span className="ml-1.5 text-[12px] text-[#737373]">
|
||||
· {pluginIdLabel}
|
||||
</span>
|
||||
{isEditing ? (
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span className="shrink-0 text-lg">{project.emoji || "📁"}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={editingProject.name}
|
||||
ref={editInputRef}
|
||||
onChange={(e) =>
|
||||
setEditingProject((prev) =>
|
||||
prev ? { ...prev, name: e.target.value } : prev,
|
||||
)
|
||||
}
|
||||
onKeyDown={handleEditKeyDown}
|
||||
className={cn(
|
||||
"min-w-0 flex-1 rounded-[9px] border border-[rgba(82,89,102,0.35)] bg-[#0D121A] px-2.5 py-1.5 text-sm font-medium text-[#fafafa] shadow-inside-out placeholder:text-[#737373] focus:outline-none focus:ring-1 focus:ring-[rgba(75,160,250,0.45)]",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
spaceSelectorDisplayName(project, project.containerTag)
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
{enableDelete && !isDefault && onDeleteRequest && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onDeleteRequest({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
containerTag: project.containerTag,
|
||||
})
|
||||
}}
|
||||
aria-label="Delete space"
|
||||
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1.5 rounded-full hover:bg-red-500/15 cursor-pointer focus:outline-none"
|
||||
>
|
||||
<Trash2 className="size-3.5 text-red-400" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
aria-label="Space name"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={saveEditing}
|
||||
disabled={isSaveDisabled}
|
||||
aria-label="Save space name"
|
||||
className="shrink-0 rounded-full p-1.5 text-[#4BA0FA] transition-colors hover:bg-[#4BA0FA]/15 disabled:cursor-not-allowed disabled:opacity-35"
|
||||
>
|
||||
<Check className="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancelEditing}
|
||||
disabled={updateProjectMutation.isPending}
|
||||
aria-label="Cancel editing space name"
|
||||
className="shrink-0 rounded-full p-1.5 text-[#737373] transition-colors hover:bg-[#737373]/15 hover:text-[#fafafa] disabled:cursor-not-allowed disabled:opacity-35"
|
||||
>
|
||||
<XIcon className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelect(project.containerTag)}
|
||||
className="flex min-w-0 flex-1 items-center gap-3 text-left cursor-pointer focus:outline-none focus:ring-0"
|
||||
>
|
||||
{plugin ? (
|
||||
plugin.iconSrc ? (
|
||||
<Image
|
||||
src={plugin.iconSrc}
|
||||
alt=""
|
||||
width={20}
|
||||
height={20}
|
||||
className="shrink-0 rounded-[4px]"
|
||||
aria-hidden
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="shrink-0 flex items-center justify-center w-5 h-5 rounded-[4px] bg-[#1E232B] text-[#FAFAFA] text-[11px] font-semibold uppercase"
|
||||
aria-hidden
|
||||
>
|
||||
{pluginInitial(plugin.label)}
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<span className="shrink-0 text-lg">
|
||||
{project.emoji || "📁"}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate text-[#fafafa] text-sm font-medium"
|
||||
title={project.containerTag}
|
||||
>
|
||||
{plugin ? (
|
||||
<>
|
||||
{plugin.label}
|
||||
{pluginIdLabel && (
|
||||
<span className="ml-1.5 text-[12px] text-[#737373]">
|
||||
· {pluginIdLabel}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
spaceSelectorDisplayName(project, project.containerTag)
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
{canEdit && !isEditing && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
startEditing(project)
|
||||
}}
|
||||
aria-label="Rename space"
|
||||
className="shrink-0 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity p-1.5 rounded-full text-[#737373] hover:bg-[#737373]/15 hover:text-[#fafafa] cursor-pointer focus:outline-none"
|
||||
>
|
||||
<Pencil className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{enableDelete && !isDefault && !isEditing && onDeleteRequest && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onDeleteRequest({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
containerTag: project.containerTag,
|
||||
})
|
||||
}}
|
||||
aria-label="Delete space"
|
||||
className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1.5 rounded-full hover:bg-red-500/15 cursor-pointer focus:outline-none"
|
||||
>
|
||||
<Trash2 className="size-3.5 text-red-400" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
[
|
||||
cancelEditing,
|
||||
currentSelection,
|
||||
editingProject,
|
||||
enableDelete,
|
||||
handleEditKeyDown,
|
||||
handleSelect,
|
||||
onDeleteRequest,
|
||||
pluginMetaMap,
|
||||
saveEditing,
|
||||
startEditing,
|
||||
updateProjectMutation.isPending,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||
|
|
@ -615,21 +767,14 @@ export function SelectSpacesModal({
|
|||
<div className="flex min-h-0 flex-1 flex-col gap-3">
|
||||
{activeCategory.startsWith("discover:") ? (
|
||||
<DiscoverPanel
|
||||
catalogId={activeCategory.slice("discover:".length)}
|
||||
isConnecting={
|
||||
connectingPluginId ===
|
||||
activeCategory.slice("discover:".length)
|
||||
}
|
||||
catalogId={activeDiscoverId ?? ""}
|
||||
isConnecting={connectingPluginId === activeDiscoverId}
|
||||
newKey={
|
||||
newKey?.pluginId === activeCategory.slice("discover:".length)
|
||||
? newKey.key
|
||||
: null
|
||||
}
|
||||
onConnect={() =>
|
||||
connectMutation.mutate(
|
||||
activeCategory.slice("discover:".length),
|
||||
)
|
||||
newKey?.pluginId === activeDiscoverId ? newKey.key : null
|
||||
}
|
||||
onConnect={() => {
|
||||
if (activeDiscoverId) connectMutation.mutate(activeDiscoverId)
|
||||
}}
|
||||
onDismissKey={() => setNewKey(null)}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -193,12 +193,15 @@ export function SpaceSelector({
|
|||
const handleSelectSpacesApply = useCallback(
|
||||
(selected: string[]) => {
|
||||
const next = selected.slice(0, 1)
|
||||
if (next[0]) {
|
||||
analytics.spaceSwitched({ space_id: next[0] })
|
||||
pushRecent(next[0])
|
||||
}
|
||||
onValueChange(next)
|
||||
const selectedTag = next[0]
|
||||
setShowSelectSpacesModal(false)
|
||||
onValueChange(next)
|
||||
if (selectedTag) {
|
||||
queueMicrotask(() => {
|
||||
analytics.spaceSwitched({ space_id: selectedTag })
|
||||
pushRecent(selectedTag)
|
||||
})
|
||||
}
|
||||
},
|
||||
[onValueChange, pushRecent],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { $fetch } from "@lib/api"
|
|||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { toast } from "sonner"
|
||||
import { useProject } from "@/stores"
|
||||
import type { ContainerTagListType } from "@lib/types"
|
||||
import type { ContainerTagListType, Project } from "@lib/types"
|
||||
|
||||
export function useProjectMutations() {
|
||||
const queryClient = useQueryClient()
|
||||
|
|
@ -89,6 +89,101 @@ export function useProjectMutations() {
|
|||
},
|
||||
})
|
||||
|
||||
const updateProjectMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
containerTag,
|
||||
name,
|
||||
}: {
|
||||
containerTag: string
|
||||
name: string
|
||||
}) => {
|
||||
const response = await $fetch(`@patch/container-tags/${containerTag}`, {
|
||||
body: { name },
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error?.message || "Failed to update project")
|
||||
}
|
||||
|
||||
const data = response.data as
|
||||
| { containerTag?: string; name?: string | null }
|
||||
| undefined
|
||||
|
||||
return {
|
||||
containerTag: data?.containerTag ?? containerTag,
|
||||
name: data?.name ?? name,
|
||||
}
|
||||
},
|
||||
onMutate: async (variables) => {
|
||||
await Promise.all([
|
||||
queryClient.cancelQueries({ queryKey: ["projects"] }),
|
||||
queryClient.cancelQueries({ queryKey: ["container-tags"] }),
|
||||
])
|
||||
|
||||
const previousProjects = queryClient.getQueryData<Project[]>(["projects"])
|
||||
const previousContainerTags = queryClient.getQueryData<
|
||||
ContainerTagListType[]
|
||||
>(["container-tags"])
|
||||
|
||||
queryClient.setQueryData<Project[]>(["projects"], (current) =>
|
||||
current?.map((project) =>
|
||||
project.containerTag === variables.containerTag
|
||||
? { ...project, name: variables.name }
|
||||
: project,
|
||||
),
|
||||
)
|
||||
queryClient.setQueryData<ContainerTagListType[]>(
|
||||
["container-tags"],
|
||||
(current) =>
|
||||
current?.map((project) =>
|
||||
project.containerTag === variables.containerTag
|
||||
? { ...project, name: variables.name }
|
||||
: project,
|
||||
),
|
||||
)
|
||||
|
||||
return { previousProjects, previousContainerTags }
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (!data) return
|
||||
queryClient.setQueryData<Project[]>(["projects"], (current) =>
|
||||
current?.map((project) =>
|
||||
project.containerTag === data.containerTag
|
||||
? { ...project, name: data.name }
|
||||
: project,
|
||||
),
|
||||
)
|
||||
queryClient.setQueryData<ContainerTagListType[]>(
|
||||
["container-tags"],
|
||||
(current) =>
|
||||
current?.map((project) =>
|
||||
project.containerTag === data.containerTag
|
||||
? { ...project, name: data.name }
|
||||
: project,
|
||||
),
|
||||
)
|
||||
toast.success("Space renamed")
|
||||
},
|
||||
onError: (error, _variables, context) => {
|
||||
if (context?.previousProjects) {
|
||||
queryClient.setQueryData(["projects"], context.previousProjects)
|
||||
}
|
||||
if (context?.previousContainerTags) {
|
||||
queryClient.setQueryData(
|
||||
["container-tags"],
|
||||
context.previousContainerTags,
|
||||
)
|
||||
}
|
||||
toast.error("Failed to rename space", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
})
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] })
|
||||
queryClient.invalidateQueries({ queryKey: ["container-tags"] })
|
||||
},
|
||||
})
|
||||
|
||||
const switchProject = (containerTag: string) => {
|
||||
setSelectedProject(containerTag)
|
||||
toast.success("Project switched successfully")
|
||||
|
|
@ -97,6 +192,7 @@ export function useProjectMutations() {
|
|||
return {
|
||||
createProjectMutation,
|
||||
deleteProjectMutation,
|
||||
updateProjectMutation,
|
||||
switchProject,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
BulkDeleteMemoriesResponseSchema,
|
||||
BulkDeleteMemoriesSchema,
|
||||
ConnectionResponseSchema,
|
||||
ContainerTagSettingsUpdateSchema,
|
||||
CreateProjectSchema,
|
||||
DeleteProjectResponseSchema,
|
||||
DeleteProjectSchema,
|
||||
|
|
@ -25,6 +26,7 @@ import {
|
|||
SearchResponseSchema,
|
||||
type SearchResult,
|
||||
SettingsRequestSchema,
|
||||
UpdateContainerTagSettingsRequestSchema,
|
||||
} from "../validation/api"
|
||||
|
||||
// Settings response schema - this is custom to console (not in shared validation)
|
||||
|
|
@ -252,6 +254,13 @@ export const apiSchema = createSchema({
|
|||
"@get/container-tags/list": {
|
||||
output: ListContainerTagsResponseSchema,
|
||||
},
|
||||
"@patch/container-tags/:containerTag": {
|
||||
input: UpdateContainerTagSettingsRequestSchema,
|
||||
output: ContainerTagSettingsUpdateSchema,
|
||||
params: z.object({
|
||||
containerTag: z.string(),
|
||||
}),
|
||||
},
|
||||
"@post/projects": {
|
||||
input: CreateProjectSchema,
|
||||
output: ProjectSchema,
|
||||
|
|
|
|||
|
|
@ -1280,6 +1280,51 @@ export const CreateProjectSchema = z
|
|||
description: "Request body for creating a new project",
|
||||
})
|
||||
|
||||
export const ContainerTagSettingsUpdateSchema = z
|
||||
.object({
|
||||
containerTag: z.string().openapi({
|
||||
description: "The container tag identifier",
|
||||
example: "sm_project_default",
|
||||
}),
|
||||
name: z.string().nullable().openapi({
|
||||
description: "Display name for this container tag",
|
||||
example: "Research Notes",
|
||||
}),
|
||||
entityContext: z.string().nullable().openapi({
|
||||
description: "Custom context prompt for this container tag",
|
||||
example: "This project contains research papers about machine learning.",
|
||||
}),
|
||||
memoryFilesystemPaths: z.array(z.string()).nullable(),
|
||||
updatedAt: z.string().datetime().openapi({
|
||||
description: "Last update timestamp",
|
||||
format: "datetime",
|
||||
}),
|
||||
})
|
||||
.openapi({
|
||||
description: "Response after updating container tag settings",
|
||||
})
|
||||
|
||||
export const UpdateContainerTagSettingsRequestSchema = z
|
||||
.object({
|
||||
name: z.string().trim().min(1).max(100).optional().openapi({
|
||||
description:
|
||||
"Display name for this container tag. This does not change the container tag identifier.",
|
||||
example: "Research Notes",
|
||||
minLength: 1,
|
||||
maxLength: 100,
|
||||
}),
|
||||
entityContext: z.string().max(1500).nullable().optional().openapi({
|
||||
description:
|
||||
"Custom context prompt for this container tag. Used to provide additional context when processing documents in this container. Maximum 1500 characters.",
|
||||
example: "This project contains research papers about machine learning.",
|
||||
maxLength: 1500,
|
||||
}),
|
||||
memoryFilesystemPaths: z.array(z.string()).nullable().optional(),
|
||||
})
|
||||
.openapi({
|
||||
description: "Request body for updating container tag settings",
|
||||
})
|
||||
|
||||
export const ListProjectsResponseSchema = z
|
||||
.object({
|
||||
projects: z.array(ProjectSchema).openapi({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue