feat(ai): update client SDK and enhance AI settings management
- Upgraded '@follow-app/client-sdk' to version 0.3.40, reflecting the latest changes in AI model management. - Introduced new sections for managing AI shortcuts, token usage, and MCP services within the settings interface. - Added components for personalizing prompts and managing panel styles, improving user interaction with AI features. - Enhanced the overall layout and organization of AI settings for better usability. These changes collectively improve the user experience by providing a more intuitive interface for managing AI settings and enhancing the overall functionality of the AI chat feature. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
a3a6a2aedb
commit
f086dc43f6
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,34 @@
|
|||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { AIChatPanelStyle, setAIChatPanelStyle, useAIChatPanelStyle } from "~/atoms/settings/ai"
|
||||
|
||||
import { SettingTabbedSegment } from "../../control"
|
||||
|
||||
export const PanelStyleSection = () => {
|
||||
const { t } = useTranslation("ai")
|
||||
const panelStyle = useAIChatPanelStyle()
|
||||
|
||||
return (
|
||||
<SettingTabbedSegment
|
||||
key="panel-style"
|
||||
label={t("settings.panel_style.label")}
|
||||
description={t("settings.panel_style.description")}
|
||||
value={panelStyle}
|
||||
values={[
|
||||
{
|
||||
value: AIChatPanelStyle.Fixed,
|
||||
label: t("settings.panel_style.fixed"),
|
||||
icon: <i className="i-mingcute-rectangle-vertical-line" />,
|
||||
},
|
||||
{
|
||||
value: AIChatPanelStyle.Floating,
|
||||
label: t("settings.panel_style.floating"),
|
||||
icon: <i className="i-mingcute-layout-right-line" />,
|
||||
},
|
||||
]}
|
||||
onValueChanged={(value) => {
|
||||
setAIChatPanelStyle(value as AIChatPanelStyle)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import { Spring } from "@follow/components/constants/spring.js"
|
||||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { TextArea } from "@follow/components/ui/input/index.js"
|
||||
import { Label } from "@follow/components/ui/label/index.jsx"
|
||||
import { AnimatePresence } from "motion/react"
|
||||
import * as React from "react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { setAISetting, useAISettingValue } from "~/atoms/settings/ai"
|
||||
import { m } from "~/components/common/Motion"
|
||||
|
||||
import { SettingDescription } from "../../control"
|
||||
import { SettingModalContentPortal } from "../../modal/layout"
|
||||
|
||||
export const PersonalizePromptSection = () => {
|
||||
const { t } = useTranslation("ai")
|
||||
const aiSettings = useAISettingValue()
|
||||
const [prompt, setPrompt] = useState(aiSettings.personalizePrompt)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const MAX_CHARACTERS = 500
|
||||
const currentLength = prompt.length
|
||||
const isOverLimit = currentLength > MAX_CHARACTERS
|
||||
const hasChanges = prompt !== aiSettings.personalizePrompt
|
||||
|
||||
const handlePromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const { value } = e.target
|
||||
// Allow typing but show validation error if over limit
|
||||
setPrompt(value)
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (isOverLimit) {
|
||||
toast.error(`Prompt must be ${MAX_CHARACTERS} characters or less`)
|
||||
return
|
||||
}
|
||||
|
||||
setIsSaving(true)
|
||||
try {
|
||||
setAISetting("personalizePrompt", prompt)
|
||||
toast.success(t("personalize.saved"))
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text text-sm font-medium">{t("personalize.prompt.label")}</Label>
|
||||
<div className="relative -mx-3">
|
||||
<TextArea
|
||||
value={prompt}
|
||||
onChange={handlePromptChange}
|
||||
placeholder={t("personalize.prompt.placeholder")}
|
||||
className={`min-h-[80px] resize-none text-sm ${
|
||||
isOverLimit ? "border-red focus:border-red" : ""
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`absolute bottom-2 right-2 text-xs ${
|
||||
isOverLimit
|
||||
? "text-red"
|
||||
: currentLength > MAX_CHARACTERS * 0.8
|
||||
? "text-yellow"
|
||||
: "text-text-tertiary"
|
||||
}`}
|
||||
>
|
||||
{currentLength}/{MAX_CHARACTERS}
|
||||
</div>
|
||||
</div>
|
||||
<SettingDescription>
|
||||
{t("personalize.prompt.help")}
|
||||
{isOverLimit && (
|
||||
<span className="text-red mt-1 block">
|
||||
Prompt exceeds {MAX_CHARACTERS} character limit
|
||||
</span>
|
||||
)}
|
||||
</SettingDescription>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{hasChanges && (
|
||||
<SettingModalContentPortal>
|
||||
<m.div
|
||||
initial={{ y: 20, scale: 0.95 }}
|
||||
animate={{ y: 0, scale: 1 }}
|
||||
exit={{ y: 20, scale: 0.95 }}
|
||||
transition={Spring.presets.snappy}
|
||||
className="absolute inset-x-0 bottom-3 z-10 flex justify-center px-3"
|
||||
>
|
||||
<div className="backdrop-blur-background bg-material-medium border-border shadow-perfect flex w-fit max-w-[92%] items-center justify-between gap-3 rounded-full border py-2 pl-5 pr-2">
|
||||
<span className="text-text-secondary text-xs sm:text-sm">Unsaved changes</span>
|
||||
<Button
|
||||
buttonClassName="bg-accent rounded-full"
|
||||
size="sm"
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || isOverLimit}
|
||||
>
|
||||
{isSaving ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
</div>
|
||||
</m.div>
|
||||
</SettingModalContentPortal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import { Progress } from "@follow/components/ui/progress/index.jsx"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { apiFetch } from "~/lib/api-fetch"
|
||||
|
||||
import { SettingDescription } from "../../control"
|
||||
|
||||
const useTokenUsage = () => {
|
||||
return useQuery({
|
||||
queryKey: ["aiTokenUsage"],
|
||||
queryFn: async () => {
|
||||
// TODO: replace with api client call
|
||||
return (
|
||||
(await apiFetch("/ai/usage")) as {
|
||||
code: 0
|
||||
data: {
|
||||
total: number
|
||||
used: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
}
|
||||
).data
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const TokenUsageSection = () => {
|
||||
const { t } = useTranslation("ai")
|
||||
|
||||
const tokenUsage = useTokenUsage().data || {
|
||||
total: 0,
|
||||
used: 0,
|
||||
remaining: 0,
|
||||
resetAt: new Date(),
|
||||
}
|
||||
|
||||
const usagePercentage = tokenUsage.total === 0 ? 0 : (tokenUsage.used / tokenUsage.total) * 100
|
||||
const resetDate = new Date(tokenUsage.resetAt)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SettingDescription>{t("token_usage.description")}</SettingDescription>
|
||||
|
||||
<div className="bg-material-medium border-border space-y-4 rounded-lg border p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-text text-sm font-medium">
|
||||
{t("token_usage.tokens_used", {
|
||||
used: tokenUsage.used.toLocaleString(),
|
||||
total: tokenUsage.total.toLocaleString(),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-text-secondary text-xs">
|
||||
{tokenUsage.remaining.toLocaleString()} {t("token_usage.tokens_remaining")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="text-text text-sm font-medium">{Math.round(usagePercentage)}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Progress value={usagePercentage} className="h-2" />
|
||||
|
||||
<div className="text-text-tertiary flex items-center justify-between text-xs">
|
||||
<span>0</span>
|
||||
<span>
|
||||
{t("token_usage.resets_at")}: {resetDate.toLocaleDateString()}
|
||||
</span>
|
||||
<span>{tokenUsage.total.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 pt-2">
|
||||
<div className="bg-accent size-3 rounded-full" />
|
||||
<span className="text-text-secondary text-xs">Current usage</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export { MCPServicesSection } from "./mcp"
|
||||
export { PanelStyleSection } from "./PanelStyleSection"
|
||||
export { PersonalizePromptSection } from "./PersonalizePromptSection"
|
||||
export { AIShortcutsSection } from "./shortcuts"
|
||||
export { TokenUsageSection } from "./TokenUsageSection"
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import type { MCPService } from "@follow/shared/settings/interface"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
interface MCPServiceItemProps {
|
||||
service: MCPService
|
||||
onDelete: (id: string) => void
|
||||
onRefresh: (connectionId: string) => void
|
||||
onEdit: (service: MCPService) => void
|
||||
isDeleting?: boolean
|
||||
isRefreshing?: boolean
|
||||
}
|
||||
|
||||
export const MCPServiceItem = ({
|
||||
service,
|
||||
onDelete,
|
||||
onRefresh,
|
||||
onEdit,
|
||||
isDeleting = false,
|
||||
isRefreshing = false,
|
||||
}: MCPServiceItemProps) => {
|
||||
const { t } = useTranslation("ai")
|
||||
|
||||
const getConnectionStatusColor = (isConnected: boolean) => {
|
||||
return isConnected ? "bg-green/10 text-green" : "bg-gray/10 text-text-tertiary"
|
||||
}
|
||||
|
||||
const getConnectionStatusText = (isConnected: boolean) => {
|
||||
return isConnected
|
||||
? t("integration.mcp.service.connected")
|
||||
: t("integration.mcp.service.disconnected")
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string | null) => {
|
||||
if (!dateString) return "Never"
|
||||
return new Date(dateString).toLocaleDateString()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hover:bg-material-medium border-border group rounded-lg border p-4 transition-colors">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-text text-sm font-medium">{service.name}</h4>
|
||||
<div
|
||||
className={`rounded-full px-2 py-1 text-xs ${getConnectionStatusColor(service.isConnected)}`}
|
||||
>
|
||||
{getConnectionStatusText(service.isConnected)}
|
||||
</div>
|
||||
<div className="bg-blue/10 text-blue rounded-full px-2 py-1 text-xs">
|
||||
{service.transportType}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{service.url && (
|
||||
<p className="text-text-secondary text-xs">
|
||||
<span className="text-text-tertiary">URL:</span> {service.url}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="text-text-secondary text-xs">
|
||||
<span className="text-text-tertiary">Tools:</span> {service.toolCount}
|
||||
<span className="text-text-tertiary ml-4">Created:</span>{" "}
|
||||
{formatDate(service.createdAt)}
|
||||
<span className="text-text-tertiary ml-4">Last Used:</span>{" "}
|
||||
{formatDate(service.lastUsed)}
|
||||
</p>
|
||||
{service.lastError && (
|
||||
<p className="text-red text-xs">
|
||||
<span className="text-text-tertiary">Error:</span> {service.lastError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-4 flex items-center gap-1 opacity-60 transition-opacity group-hover:opacity-100">
|
||||
<Button variant="ghost" size="sm" onClick={() => onEdit(service)} title="Edit connection">
|
||||
<i className="i-mgc-edit-cute-re size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onRefresh(service.id)}
|
||||
title="Refresh tools"
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<i className="i-mgc-loading-3-cute-re size-4 animate-spin" />
|
||||
) : (
|
||||
<i className="i-mgc-refresh-2-cute-re size-4" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onDelete(service.id)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? (
|
||||
<i className="i-mgc-loading-3-cute-re size-4 animate-spin" />
|
||||
) : (
|
||||
<i className="i-mgc-delete-2-cute-re size-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { Input } from "@follow/components/ui/input/index.js"
|
||||
import { KeyValueEditor } from "@follow/components/ui/key-value-editor/index.js"
|
||||
import { Label } from "@follow/components/ui/label/index.jsx"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@follow/components/ui/select/index.js"
|
||||
import type { MCPService } from "@follow/shared/settings/interface"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
interface MCPServiceModalContentProps {
|
||||
service?: MCPService | null
|
||||
onSave: (service: {
|
||||
name: string
|
||||
transportType: "streamable-http" | "sse"
|
||||
url: string
|
||||
headers?: Record<string, string>
|
||||
}) => void
|
||||
onCancel: () => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export const MCPServiceModalContent = ({
|
||||
service,
|
||||
onSave,
|
||||
onCancel,
|
||||
isLoading = false,
|
||||
}: MCPServiceModalContentProps) => {
|
||||
const { t } = useTranslation("ai")
|
||||
const [name, setName] = useState(service?.name || "")
|
||||
const [url, setUrl] = useState(service?.url || "")
|
||||
const [transportType, setTransportType] = useState<"streamable-http" | "sse">(
|
||||
service?.transportType || "streamable-http",
|
||||
)
|
||||
const [headers, setHeaders] = useState<Record<string, string>>(service?.headers || {})
|
||||
|
||||
const handleSave = () => {
|
||||
if (!name.trim()) {
|
||||
toast.error(t("integration.mcp.service.validation.name_required"))
|
||||
return
|
||||
}
|
||||
|
||||
if (!url.trim()) {
|
||||
toast.error(t("integration.mcp.service.validation.baseUrl_required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Basic URL validation
|
||||
try {
|
||||
new URL(url.trim())
|
||||
} catch {
|
||||
toast.error(t("integration.mcp.service.validation.invalid_url"))
|
||||
return
|
||||
}
|
||||
|
||||
onSave({
|
||||
name: name.trim(),
|
||||
transportType,
|
||||
url: url.trim(),
|
||||
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text text-xs">{t("integration.mcp.service.name")}</Label>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder={t("integration.mcp.service.name_placeholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text text-xs">Transport Type</Label>
|
||||
<Select
|
||||
value={transportType}
|
||||
onValueChange={(value) => setTransportType(value as "streamable-http" | "sse")}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select transport type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
<SelectItem value="streamable-http">Streamable HTTP</SelectItem>
|
||||
<SelectItem value="sse">Server-Sent Events</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text text-xs">URL</Label>
|
||||
<Input
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder="https://example.com/mcp"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-w-[500px] space-y-2">
|
||||
<Label className="text-text text-xs">Headers (Optional)</Label>
|
||||
<KeyValueEditor
|
||||
value={headers}
|
||||
onChange={setHeaders}
|
||||
keyPlaceholder="Header name"
|
||||
valuePlaceholder="Header value"
|
||||
addButtonText="Add Header"
|
||||
minRows={0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div />
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={onCancel} disabled={isLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<i className="i-mgc-loading-3-cute-re mr-2 size-4 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
"Save"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { Label } from "@follow/components/ui/label/index.jsx"
|
||||
import { Switch } from "@follow/components/ui/switch/index.jsx"
|
||||
import type { MCPService } from "@follow/shared/settings/interface"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import * as React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { setMCPEnabled, useMCPEnabled } from "~/atoms/settings/ai"
|
||||
import { useDialog, useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
import {
|
||||
createMCPConnection,
|
||||
deleteMCPConnection,
|
||||
fetchMCPConnections,
|
||||
mcpQueryKeys,
|
||||
refreshMCPTools,
|
||||
updateMCPConnection,
|
||||
} from "~/queries/mcp"
|
||||
|
||||
import { MCPServiceItem } from "./MCPServiceItem"
|
||||
import { MCPServiceModalContent } from "./MCPServiceModalContent"
|
||||
|
||||
export const MCPServicesSection = () => {
|
||||
const { t } = useTranslation("ai")
|
||||
const mcpEnabled = useMCPEnabled()
|
||||
const queryClient = useQueryClient()
|
||||
const dialog = useDialog()
|
||||
|
||||
// Reusable OAuth authorization handler using dialog
|
||||
const handleOAuthAuthorization = async (authorizationUrl: string, connectionId?: string) => {
|
||||
const confirmed = await dialog.ask({
|
||||
title: t("integration.mcp.service.auth_required"),
|
||||
message: t("integration.mcp.service.auth_message"),
|
||||
confirmText: t("integration.mcp.service.open_auth"),
|
||||
cancelText: t("words.cancel", { ns: "common" }),
|
||||
variant: "ask",
|
||||
})
|
||||
|
||||
if (confirmed) {
|
||||
const popup = window.open(
|
||||
authorizationUrl,
|
||||
"_blank",
|
||||
"width=600,height=700,scrollbars=yes,resizable=yes",
|
||||
)
|
||||
|
||||
if (!popup) {
|
||||
toast.error(t("integration.mcp.service.popup_blocked"))
|
||||
} else {
|
||||
popup.onclose = () => {
|
||||
// FIXME
|
||||
setTimeout(() => {
|
||||
refreshToolsMutation.mutate(connectionId ? [connectionId] : undefined)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Query for MCP connections
|
||||
const {
|
||||
data: mcpServices = [],
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: mcpQueryKeys.connections(),
|
||||
queryFn: fetchMCPConnections,
|
||||
enabled: mcpEnabled,
|
||||
refetchInterval: 30_000,
|
||||
refetchOnWindowFocus: true,
|
||||
retry: 2,
|
||||
})
|
||||
|
||||
// Mutation for creating MCP connection
|
||||
const createConnectionMutation = useMutation({
|
||||
mutationFn: createMCPConnection,
|
||||
onSuccess: async (result) => {
|
||||
queryClient.invalidateQueries({ queryKey: mcpQueryKeys.connections() })
|
||||
|
||||
// Handle OAuth authorization if needed
|
||||
if (result.authorizationUrl) {
|
||||
await handleOAuthAuthorization(result.authorizationUrl, result.connectionId)
|
||||
} else {
|
||||
toast.success(t("integration.mcp.service.added"))
|
||||
refreshToolsMutation.mutate([result.connectionId])
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(t("integration.mcp.service.discovery_failed"))
|
||||
console.error("Failed to create MCP connection:", error)
|
||||
},
|
||||
})
|
||||
|
||||
// Mutation for updating MCP connection
|
||||
const updateConnectionMutation = useMutation({
|
||||
mutationFn: ({
|
||||
connectionId,
|
||||
updateData,
|
||||
}: {
|
||||
connectionId: string
|
||||
updateData: Parameters<typeof updateMCPConnection>[1]
|
||||
}) => updateMCPConnection(connectionId, updateData),
|
||||
onSuccess: async (result) => {
|
||||
queryClient.invalidateQueries({ queryKey: mcpQueryKeys.connections() })
|
||||
|
||||
// Handle OAuth authorization if needed
|
||||
if (result.authorizationUrl) {
|
||||
await handleOAuthAuthorization(result.authorizationUrl, result.connectionId)
|
||||
} else {
|
||||
toast.success(t("integration.mcp.service.updated"))
|
||||
refreshToolsMutation.mutate([result.connectionId])
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to update MCP connection")
|
||||
console.error("Failed to update MCP connection:", error)
|
||||
},
|
||||
})
|
||||
|
||||
// Mutation for deleting MCP connection
|
||||
const deleteConnectionMutation = useMutation({
|
||||
mutationFn: deleteMCPConnection,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: mcpQueryKeys.connections() })
|
||||
toast.success(t("integration.mcp.service.deleted"))
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to delete MCP connection")
|
||||
console.error("Failed to delete MCP connection:", error)
|
||||
},
|
||||
})
|
||||
|
||||
// Mutation for refreshing MCP tools
|
||||
const refreshToolsMutation = useMutation({
|
||||
mutationFn: (connectionIds?: string[]) => refreshMCPTools(connectionIds),
|
||||
onSuccess: () => {
|
||||
// Invalidate both connections (for updated counts) and tools queries
|
||||
queryClient.invalidateQueries({ queryKey: mcpQueryKeys.connections() })
|
||||
queryClient.invalidateQueries({ queryKey: mcpQueryKeys.all })
|
||||
toast.success("MCP tools refreshed successfully")
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to refresh MCP tools")
|
||||
console.error("Failed to refresh MCP tools:", error)
|
||||
},
|
||||
})
|
||||
|
||||
const { present } = useModalStack()
|
||||
const handleAddService = () => {
|
||||
present({
|
||||
title: "Add MCP Service",
|
||||
content: ({ dismiss }: { dismiss: () => void }) => (
|
||||
<MCPServiceModalContent
|
||||
service={null}
|
||||
onSave={(service) => {
|
||||
createConnectionMutation.mutate(service)
|
||||
dismiss()
|
||||
}}
|
||||
onCancel={dismiss}
|
||||
isLoading={createConnectionMutation.isPending}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const handleEditService = (service: MCPService) => {
|
||||
present({
|
||||
title: "Edit MCP Service",
|
||||
content: ({ dismiss }: { dismiss: () => void }) => (
|
||||
<MCPServiceModalContent
|
||||
service={service}
|
||||
onSave={(updatedService) => {
|
||||
updateConnectionMutation.mutate({
|
||||
connectionId: service.id,
|
||||
updateData: updatedService,
|
||||
})
|
||||
dismiss()
|
||||
}}
|
||||
onCancel={dismiss}
|
||||
isLoading={updateConnectionMutation.isPending}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeleteService = (id: string) => {
|
||||
deleteConnectionMutation.mutate(id)
|
||||
}
|
||||
|
||||
const handleRefreshTools = (connectionId?: string) => {
|
||||
refreshToolsMutation.mutate(connectionId ? [connectionId] : undefined)
|
||||
}
|
||||
|
||||
// Show error message if query failed
|
||||
React.useEffect(() => {
|
||||
if (error) {
|
||||
toast.error("Failed to load MCP connections")
|
||||
console.error("Failed to load MCP connections:", error)
|
||||
}
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-text text-sm font-medium">{t("integration.mcp.enabled")}</Label>
|
||||
<div className="text-text-secondary text-xs">{t("integration.mcp.description")}</div>
|
||||
</div>
|
||||
<Switch checked={mcpEnabled} onCheckedChange={setMCPEnabled} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mcpEnabled && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-text text-sm font-medium">
|
||||
{t("integration.mcp.services.title")}
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => refetch()}
|
||||
disabled={isLoading}
|
||||
title="Refresh connections"
|
||||
>
|
||||
{isLoading ? (
|
||||
<i className="i-mgc-loading-3-cute-re size-4 animate-spin" />
|
||||
) : (
|
||||
<i className="i-mgc-refresh-2-cute-re size-4" />
|
||||
)}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={handleAddService}>
|
||||
<i className="i-mgc-add-cute-re mr-2 size-4" />
|
||||
{t("integration.mcp.services.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mcpServices.length === 0 && (
|
||||
<div className="py-8 text-center">
|
||||
<div className="bg-fill-secondary mx-auto mb-3 flex size-12 items-center justify-center rounded-full">
|
||||
<i className="i-mgc-plugin-2-cute-re text-text size-6" />
|
||||
</div>
|
||||
<h4 className="text-text mb-1 text-sm font-medium">
|
||||
{t("integration.mcp.services.empty.title")}
|
||||
</h4>
|
||||
<p className="text-text-secondary text-xs">
|
||||
{t("integration.mcp.services.empty.description")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<i className="i-mgc-loading-3-cute-re size-6 animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mcpServices.map((service) => (
|
||||
<MCPServiceItem
|
||||
key={service.id}
|
||||
service={service}
|
||||
onDelete={handleDeleteService}
|
||||
onRefresh={handleRefreshTools}
|
||||
onEdit={handleEditService}
|
||||
isDeleting={
|
||||
deleteConnectionMutation.isPending &&
|
||||
deleteConnectionMutation.variables === service.id
|
||||
}
|
||||
isRefreshing={
|
||||
refreshToolsMutation.isPending && refreshToolsMutation.variables?.[0] === service.id
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { MCPServiceItem } from "./MCPServiceItem"
|
||||
export { MCPServiceModalContent } from "./MCPServiceModalContent"
|
||||
export { MCPServicesSection } from "./MCPServicesSection"
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
import type { AIShortcut } from "@follow/shared/settings/interface"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { setAISetting, useAISettingValue } from "~/atoms/settings/ai"
|
||||
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
|
||||
import { SettingActionItem } from "../../../control"
|
||||
import { ShortcutItem } from "./ShortcutItem"
|
||||
import { ShortcutModalContent } from "./ShortcutModalContent"
|
||||
|
||||
export const AIShortcutsSection = () => {
|
||||
const { t } = useTranslation("ai")
|
||||
const { shortcuts } = useAISettingValue()
|
||||
const { present } = useModalStack()
|
||||
|
||||
const handleAddShortcut = () => {
|
||||
present({
|
||||
title: "Add AI Shortcut",
|
||||
content: ({ dismiss }: { dismiss: () => void }) => (
|
||||
<ShortcutModalContent
|
||||
shortcut={null}
|
||||
onSave={(shortcut) => {
|
||||
const newShortcut: AIShortcut = {
|
||||
...shortcut,
|
||||
id: Date.now().toString(),
|
||||
}
|
||||
setAISetting("shortcuts", [...shortcuts, newShortcut])
|
||||
toast.success(t("shortcuts.added"))
|
||||
dismiss()
|
||||
}}
|
||||
onCancel={dismiss}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const handleEditShortcut = (shortcut: AIShortcut) => {
|
||||
present({
|
||||
title: "Edit AI Shortcut",
|
||||
content: ({ dismiss }: { dismiss: () => void }) => (
|
||||
<ShortcutModalContent
|
||||
shortcut={shortcut}
|
||||
onSave={(updatedShortcut) => {
|
||||
setAISetting(
|
||||
"shortcuts",
|
||||
shortcuts.map((s) =>
|
||||
s.id === shortcut.id ? { ...updatedShortcut, id: shortcut.id } : s,
|
||||
),
|
||||
)
|
||||
toast.success(t("shortcuts.updated"))
|
||||
dismiss()
|
||||
}}
|
||||
onCancel={dismiss}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeleteShortcut = (id: string) => {
|
||||
setAISetting(
|
||||
"shortcuts",
|
||||
shortcuts.filter((s) => s.id !== id),
|
||||
)
|
||||
toast.success(t("shortcuts.deleted"))
|
||||
}
|
||||
|
||||
const handleToggleShortcut = (id: string, enabled: boolean) => {
|
||||
setAISetting(
|
||||
"shortcuts",
|
||||
shortcuts.map((s) => (s.id === id ? { ...s, enabled } : s)),
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SettingActionItem
|
||||
label={t("shortcuts.add")}
|
||||
action={handleAddShortcut}
|
||||
buttonText={t("shortcuts.add")}
|
||||
/>
|
||||
|
||||
{shortcuts.length === 0 && (
|
||||
<div className="py-8 text-center">
|
||||
<div className="bg-fill-secondary mx-auto mb-3 flex size-12 items-center justify-center rounded-full">
|
||||
<i className="i-mgc-magic-2-cute-re text-text size-6" />
|
||||
</div>
|
||||
<h4 className="text-text mb-1 text-sm font-medium">{t("shortcuts.empty.title")}</h4>
|
||||
<p className="text-text-secondary text-xs">{t("shortcuts.empty.description")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shortcuts.map((shortcut) => (
|
||||
<ShortcutItem
|
||||
key={shortcut.id}
|
||||
shortcut={shortcut}
|
||||
onDelete={handleDeleteShortcut}
|
||||
onToggle={handleToggleShortcut}
|
||||
onEdit={handleEditShortcut}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { KbdCombined } from "@follow/components/ui/kbd/Kbd.js"
|
||||
import { Switch } from "@follow/components/ui/switch/index.jsx"
|
||||
import type { AIShortcut } from "@follow/shared/settings/interface"
|
||||
|
||||
interface ShortcutItemProps {
|
||||
shortcut: AIShortcut
|
||||
onDelete: (id: string) => void
|
||||
onToggle: (id: string, enabled: boolean) => void
|
||||
onEdit: (shortcut: AIShortcut) => void
|
||||
}
|
||||
|
||||
export const ShortcutItem = ({ shortcut, onDelete, onToggle, onEdit }: ShortcutItemProps) => {
|
||||
return (
|
||||
<div className="hover:bg-material-medium border-border group rounded-lg border p-4 transition-colors">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-text text-sm font-medium">{shortcut.name}</h4>
|
||||
{shortcut.hotkey && (
|
||||
<KbdCombined kbdProps={{ wrapButton: false }} joint={false}>
|
||||
{shortcut.hotkey}
|
||||
</KbdCombined>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-text-secondary line-clamp-2 text-xs leading-relaxed">
|
||||
{shortcut.prompt}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="ml-4 flex items-center gap-3">
|
||||
<div className="flex items-center gap-1 opacity-60 transition-opacity group-hover:opacity-100">
|
||||
<Button variant="ghost" size="sm" onClick={() => onEdit(shortcut)}>
|
||||
<i className="i-mgc-edit-cute-re size-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => onDelete(shortcut.id)}>
|
||||
<i className="i-mgc-delete-2-cute-re size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border-fill-tertiary flex items-center gap-2 border-l pl-3">
|
||||
<span className="text-text-tertiary text-xs font-medium">
|
||||
{shortcut.enabled ? "ON" : "OFF"}
|
||||
</span>
|
||||
<Switch
|
||||
checked={shortcut.enabled}
|
||||
onCheckedChange={(enabled) => onToggle(shortcut.id, enabled)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import { Input, TextArea } from "@follow/components/ui/input/index.js"
|
||||
import { Label } from "@follow/components/ui/label/index.jsx"
|
||||
import { Switch } from "@follow/components/ui/switch/index.jsx"
|
||||
import type { AIShortcut } from "@follow/shared/settings/interface"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
interface ShortcutModalContentProps {
|
||||
shortcut?: AIShortcut | null
|
||||
onSave: (shortcut: Omit<AIShortcut, "id">) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export const ShortcutModalContent = ({ shortcut, onSave, onCancel }: ShortcutModalContentProps) => {
|
||||
const { t } = useTranslation("ai")
|
||||
const [name, setName] = useState(shortcut?.name || "")
|
||||
const [prompt, setPrompt] = useState(shortcut?.prompt || "")
|
||||
const [enabled, setEnabled] = useState(shortcut?.enabled ?? true)
|
||||
|
||||
const handleSave = () => {
|
||||
if (!name.trim() || !prompt.trim()) {
|
||||
toast.error(t("shortcuts.validation.required"))
|
||||
return
|
||||
}
|
||||
|
||||
onSave({
|
||||
name: name.trim(),
|
||||
prompt: prompt.trim(),
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-[400px] space-y-4">
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
<div className="col-span-6 space-y-2">
|
||||
<Label className="text-text text-xs">{t("shortcuts.name")}</Label>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder={t("shortcuts.name_placeholder")}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="col-span-2 space-y-2">
|
||||
<Label className="text-text text-xs">{t("shortcuts.hotkey")}</Label>
|
||||
<button
|
||||
type="button"
|
||||
className="border-border hover:bg-material-medium flex h-9 w-full items-center rounded-md border bg-transparent px-3 py-2 text-sm transition-colors focus:outline-none"
|
||||
onClick={() => setIsRecording(!isRecording)}
|
||||
>
|
||||
{isRecording ? (
|
||||
<KeyRecorder
|
||||
onBlur={() => setIsRecording(false)}
|
||||
onChange={(keys) => {
|
||||
setHotkey(Array.isArray(keys) ? keys.join("+") : "")
|
||||
setIsRecording(false)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{hotkey ? (
|
||||
<KbdCombined kbdProps={{ wrapButton: false }} joint={false}>
|
||||
{hotkey}
|
||||
</KbdCombined>
|
||||
) : (
|
||||
<span className="text-text-tertiary text-xs">Click to record</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-text text-xs">{t("shortcuts.prompt")}</Label>
|
||||
<TextArea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder={t("shortcuts.prompt_placeholder")}
|
||||
className="min-h-[60px] resize-none text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch checked={enabled} onCheckedChange={setEnabled} />
|
||||
<Label className="text-text text-xs">{t("shortcuts.enabled")}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { AIShortcutsSection } from "./AIShortcutsSection"
|
||||
export { ShortcutItem } from "./ShortcutItem"
|
||||
export { ShortcutModalContent } from "./ShortcutModalContent"
|
||||
|
|
@ -2,15 +2,13 @@ import type { MCPService } from "@follow/shared/settings/interface"
|
|||
|
||||
import { followApi } from "~/lib/api-client"
|
||||
|
||||
// MCP Service API calls - these connect to the actual server endpoints via followClient
|
||||
export const createMCPConnection = async (connectionData: {
|
||||
name: string
|
||||
transportType: "streamable-http" | "sse"
|
||||
url: string
|
||||
headers?: Record<string, string>
|
||||
}): Promise<{ authorizationUrl?: string }> => {
|
||||
const res = await followApi.mcp.createConnection(connectionData)
|
||||
return { authorizationUrl: res.authorizationUrl }
|
||||
}) => {
|
||||
return followApi.mcp.createConnection(connectionData)
|
||||
}
|
||||
|
||||
export const fetchMCPConnections = async (): Promise<MCPService[]> => {
|
||||
|
|
@ -26,9 +24,8 @@ export const updateMCPConnection = async (
|
|||
url?: string
|
||||
headers?: Record<string, string>
|
||||
},
|
||||
): Promise<{ authorizationUrl?: string }> => {
|
||||
const res = await followApi.mcp.updateConnection({ connectionId, ...updateData })
|
||||
return { authorizationUrl: res.authorizationUrl }
|
||||
) => {
|
||||
return followApi.mcp.updateConnection({ connectionId, ...updateData })
|
||||
}
|
||||
|
||||
export const deleteMCPConnection = async (connectionId: string): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ settings:
|
|||
catalogs:
|
||||
default:
|
||||
'@follow-app/client-sdk':
|
||||
specifier: 0.3.39
|
||||
version: 0.3.39
|
||||
specifier: 0.3.40
|
||||
version: 0.3.40
|
||||
tailwindcss-uikit-colors:
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
|
|
@ -465,7 +465,7 @@ importers:
|
|||
version: 3.0.2(electron@37.2.0)
|
||||
'@follow-app/client-sdk':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.39(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
version: 0.3.40(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@follow/database':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/internal/database
|
||||
|
|
@ -1673,7 +1673,7 @@ importers:
|
|||
dependencies:
|
||||
'@follow-app/client-sdk':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.39(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
version: 0.3.40(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@follow/configs':
|
||||
specifier: workspace:*
|
||||
version: link:../../configs
|
||||
|
|
@ -1685,7 +1685,7 @@ importers:
|
|||
dependencies:
|
||||
'@follow-app/client-sdk':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.39(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
version: 0.3.40(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@follow/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../constants
|
||||
|
|
@ -1763,7 +1763,7 @@ importers:
|
|||
dependencies:
|
||||
'@follow-app/client-sdk':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.39(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
version: 0.3.40(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@follow/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../constants
|
||||
|
|
@ -1827,7 +1827,7 @@ importers:
|
|||
dependencies:
|
||||
'@follow-app/client-sdk':
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.39(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
version: 0.3.40(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@follow/configs':
|
||||
specifier: workspace:*
|
||||
version: link:../../configs
|
||||
|
|
@ -4051,20 +4051,20 @@ packages:
|
|||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@follow-app/client-sdk@0.3.39':
|
||||
resolution: {integrity: sha512-NnjLOzrQ2UD6LgwH6ItK0SJts5UinzLCXJFJf74X096a6qd9xhRuHJ3X+Ea3qNw/6WXcK4l3dnkHBHDaDfthRA==}
|
||||
'@follow-app/client-sdk@0.3.40':
|
||||
resolution: {integrity: sha512-AsSZlh10NvmskdVU1XuUh3S2rVBmz0qoiMRUtdbdU5/JARtwNM1UiIo1WfaGpinht/ZVsKLsR40p03XovRynmQ==}
|
||||
|
||||
'@folo-services/ai-tools@0.2.20':
|
||||
resolution: {integrity: sha512-1AJI48H2g64JhuTruSqQKUrn4Tc353hMFdKAbohh65AxTAMN/1pIw9w17Dtjm+g5BVlZUXYKaiIvaN9tHsXLNA==}
|
||||
|
||||
'@folo-services/constants@0.1.22':
|
||||
resolution: {integrity: sha512-zF3yX7lxTpAsZQjp8+P56nEIa7IqDVIyqMZcNpFLm8UHTS2ltPeM/S9KdNbacz9lxDmWjjCmZdEfuBbs0B5b8Q==}
|
||||
'@folo-services/constants@0.1.23':
|
||||
resolution: {integrity: sha512-2tK244KD/RhR6KoLarEPHkMoco3/JvAbL05TCr5pM3pEem7hqCzHNLLDFwAdEl2m1ByTTQHnqqhx/rEZx1BSgA==}
|
||||
|
||||
'@folo-services/drizzle@0.1.13':
|
||||
resolution: {integrity: sha512-h6tndGnTLrAmfvxK5XsXGdZaMi2GBmFZBFiBqtabC7Y05A1ZSOaWg/d9f60Ydh586LmVonz5VGrhvzTdn5ukhg==}
|
||||
|
||||
'@folo-services/drizzle@0.1.17':
|
||||
resolution: {integrity: sha512-g0BgfW+hn6vmQyz9SSTOV5KJVexgFoyEgEgQCeSVM6KxufH88VxVpJzkGA1zb7CxEuWrxTuHmpBEolcNFfABTQ==}
|
||||
'@folo-services/drizzle@0.1.18':
|
||||
resolution: {integrity: sha512-mCpxdUGpkLJ6YoohpYarkQR0fobCY1ZUZmDqyHxk9p6PtzyD/WziXYUAs9IcCuICCUiIt3dyiPie+lx/Nx3hMw==}
|
||||
|
||||
'@folo-services/exceptions@0.1.11':
|
||||
resolution: {integrity: sha512-OMKat8KcIxjw9pBfG3pC1kRcIs0YiuaYmMDA2MRy8AEGcki8F9u5e5QH7OKgP/LlNR7Fs2Csz1XetvcC4nB+RQ==}
|
||||
|
|
@ -4072,8 +4072,8 @@ packages:
|
|||
'@folo-services/exceptions@0.1.14':
|
||||
resolution: {integrity: sha512-VFVRkzXg7BytEvdg7iyUXc6qaAudTTtOU8PrTkRUG4ZB9udLE/uVT5q+6poS1CkwMi2GroH27AFC4fnfVp6Fvw==}
|
||||
|
||||
'@folo-services/shared@0.0.12':
|
||||
resolution: {integrity: sha512-CHBdwFrke6Mbwxu5MWw9tB7q9CICG2Vh/9IZ8BIa1M65sF/IBZi1sE2roHayuDix0TXXJbceaD+Tv84ROFdO3g==}
|
||||
'@folo-services/shared@0.0.13':
|
||||
resolution: {integrity: sha512-bge+IHFzCjr/QonZqQnDudlLc+P8OuimWR0bdRXUgnNjZ9R6jzIxSB5xYqYVY6DnHM+YyIOYnHAIT09Y3la4fw==}
|
||||
|
||||
'@fontsource/sn-pro@5.2.5':
|
||||
resolution: {integrity: sha512-rBdBv/0ygj6bkO7xDMMFpwobLdSrcQ2Jncb6DIwdeYGoAgeWkQRwVYhGDKasfLjEYCNYxrqr6wsXsM9+aU39RA==}
|
||||
|
|
@ -18933,12 +18933,12 @@ snapshots:
|
|||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@follow-app/client-sdk@0.3.39(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)':
|
||||
'@follow-app/client-sdk@0.3.40(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)':
|
||||
dependencies:
|
||||
'@folo-services/constants': 0.1.22
|
||||
'@folo-services/drizzle': 0.1.17(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)
|
||||
'@folo-services/constants': 0.1.23
|
||||
'@folo-services/drizzle': 0.1.18(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)
|
||||
'@folo-services/exceptions': 0.1.14
|
||||
'@folo-services/shared': 0.0.12(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
'@folo-services/shared': 0.0.13(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
zod: 3.25.76
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-rds-data'
|
||||
|
|
@ -19011,7 +19011,7 @@ snapshots:
|
|||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/constants@0.1.22':
|
||||
'@folo-services/constants@0.1.23':
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
|
|
@ -19054,7 +19054,7 @@ snapshots:
|
|||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/drizzle@0.1.17(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)':
|
||||
'@folo-services/drizzle@0.1.18(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)':
|
||||
dependencies:
|
||||
'@folo-services/exceptions': 0.1.14
|
||||
drizzle-orm: 0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
|
|
@ -19097,9 +19097,9 @@ snapshots:
|
|||
|
||||
'@folo-services/exceptions@0.1.14': {}
|
||||
|
||||
'@folo-services/shared@0.0.12(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)':
|
||||
'@folo-services/shared@0.0.13(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)':
|
||||
dependencies:
|
||||
'@folo-services/drizzle': 0.1.17(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)
|
||||
'@folo-services/drizzle': 0.1.18(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)
|
||||
drizzle-orm: 0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3)
|
||||
drizzle-zod: 0.7.1(drizzle-orm@0.44.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.15.0(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(react-native@0.79.1(@babel/core@7.28.0)(@types/react@19.1.8)(bufferutil@4.0.9)(react@19.0.0))(react@19.0.0))(kysely@0.28.2)(pg@8.16.3))(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ packages:
|
|||
|
||||
catalog:
|
||||
typescript: 5.8.3
|
||||
"@follow-app/client-sdk": 0.3.39
|
||||
"@follow-app/client-sdk": 0.3.40
|
||||
tailwindcss-uikit-colors: 1.0.0
|
||||
|
||||
onlyBuiltDependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue