repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | "use client" |
| #2 | |
| #3 | import { useState } from "react" |
| #4 | import { Eye, EyeOff, Download, Upload } from "lucide-react" |
| #5 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card" |
| #6 | import { Input } from "./ui/input" |
| #7 | import { Label } from "./ui/label" |
| #8 | import { Slider } from "./ui/slider" |
| #9 | import { Switch } from "./ui/switch" |
| #10 | import { Button } from "./ui/button" |
| #11 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" |
| #12 | import { Textarea } from "./ui/textarea" |
| #13 | import { useRef, useState as useReactState } from "react" |
| #14 | import { useSelector } from "react-redux" |
| #15 | import { RootState } from "@/store/store" |
| #16 | |
| #17 | interface FormViewProps { |
| #18 | settings: any |
| #19 | onChange: (settings: any) => void |
| #20 | } |
| #21 | |
| #22 | export function FormView({ settings, onChange }: FormViewProps) { |
| #23 | const [showLlmAdvanced, setShowLlmAdvanced] = useState(false) |
| #24 | const [showLlmApiKey, setShowLlmApiKey] = useState(false) |
| #25 | const [showEmbedderApiKey, setShowEmbedderApiKey] = useState(false) |
| #26 | const [isUploading, setIsUploading] = useReactState(false) |
| #27 | const [selectedImportFileName, setSelectedImportFileName] = useReactState("") |
| #28 | const fileInputRef = useRef<HTMLInputElement>(null) |
| #29 | const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8765" |
| #30 | const userId = useSelector((state: RootState) => state.profile.userId) |
| #31 | |
| #32 | const handleOpenMemoryChange = (key: string, value: any) => { |
| #33 | onChange({ |
| #34 | ...settings, |
| #35 | openmemory: { |
| #36 | ...settings.openmemory, |
| #37 | [key]: value, |
| #38 | }, |
| #39 | }) |
| #40 | } |
| #41 | |
| #42 | const handleLlmProviderChange = (value: string) => { |
| #43 | onChange({ |
| #44 | ...settings, |
| #45 | mem0: { |
| #46 | ...settings.mem0, |
| #47 | llm: { |
| #48 | ...settings.mem0.llm, |
| #49 | provider: value, |
| #50 | }, |
| #51 | }, |
| #52 | }) |
| #53 | } |
| #54 | |
| #55 | const handleLlmConfigChange = (key: string, value: any) => { |
| #56 | onChange({ |
| #57 | ...settings, |
| #58 | mem0: { |
| #59 | ...settings.mem0, |
| #60 | llm: { |
| #61 | ...settings.mem0.llm, |
| #62 | config: { |
| #63 | ...settings.mem0.llm.config, |
| #64 | [key]: value, |
| #65 | }, |
| #66 | }, |
| #67 | }, |
| #68 | }) |
| #69 | } |
| #70 | |
| #71 | const handleEmbedderProviderChange = (value: string) => { |
| #72 | onChange({ |
| #73 | ...settings, |
| #74 | mem0: { |
| #75 | ...settings.mem0, |
| #76 | embedder: { |
| #77 | ...settings.mem0.embedder, |
| #78 | provider: value, |
| #79 | }, |
| #80 | }, |
| #81 | }) |
| #82 | } |
| #83 | |
| #84 | const handleEmbedderConfigChange = (key: string, value: any) => { |
| #85 | onChange({ |
| #86 | ...settings, |
| #87 | mem0: { |
| #88 | ...settings.mem0, |
| #89 | embedder: { |
| #90 | ...settings.mem0.embedder, |
| #91 | config: { |
| #92 | ...settings.mem0.embedder.config, |
| #93 | [key]: value, |
| #94 | }, |
| #95 | }, |
| #96 | }, |
| #97 | }) |
| #98 | } |
| #99 | |
| #100 | const needsLlmApiKey = settings.mem0?.llm?.provider?.toLowerCase() !== "ollama" |
| #101 | const needsEmbedderApiKey = settings.mem0?.embedder?.provider?.toLowerCase() !== "ollama" |
| #102 | const isLlmOllama = settings.mem0?.llm?.provider?.toLowerCase() === "ollama" |
| #103 | const isEmbedderOllama = settings.mem0?.embedder?.provider?.toLowerCase() === "ollama" |
| #104 | |
| #105 | const LLM_PROVIDERS = { |
| #106 | "OpenAI": "openai", |
| #107 | "Anthropic": "anthropic", |
| #108 | "Azure OpenAI": "azure_openai", |
| #109 | "Ollama": "ollama", |
| #110 | "Together": "together", |
| #111 | "Groq": "groq", |
| #112 | "Litellm": "litellm", |
| #113 | "Mistral AI": "mistralai", |
| #114 | "Google AI": "google_ai", |
| #115 | "AWS Bedrock": "aws_bedrock", |
| #116 | "Gemini": "gemini", |
| #117 | "DeepSeek": "deepseek", |
| #118 | "xAI": "xai", |
| #119 | "LM Studio": "lmstudio", |
| #120 | "LangChain": "langchain", |
| #121 | } |
| #122 | |
| #123 | const EMBEDDER_PROVIDERS = { |
| #124 | "OpenAI": "openai", |
| #125 | "Azure OpenAI": "azure_openai", |
| #126 | "Ollama": "ollama", |
| #127 | "Hugging Face": "huggingface", |
| #128 | "Vertex AI": "vertexai", |
| #129 | "Gemini": "gemini", |
| #130 | "LM Studio": "lmstudio", |
| #131 | "Together": "together", |
| #132 | "LangChain": "langchain", |
| #133 | "AWS Bedrock": "aws_bedrock", |
| #134 | } |
| #135 | |
| #136 | return ( |
| #137 | <div className="space-y-8"> |
| #138 | {/* OpenMemory Settings */} |
| #139 | <Card> |
| #140 | <CardHeader> |
| #141 | <CardTitle>OpenMemory Settings</CardTitle> |
| #142 | <CardDescription>Configure your OpenMemory instance settings</CardDescription> |
| #143 | </CardHeader> |
| #144 | <CardContent className="space-y-6"> |
| #145 | <div className="space-y-2"> |
| #146 | <Label htmlFor="custom-instructions">Custom Instructions</Label> |
| #147 | <Textarea |
| #148 | id="custom-instructions" |
| #149 | placeholder="Enter custom instructions for memory management..." |
| #150 | value={settings.openmemory?.custom_instructions || ""} |
| #151 | onChange={(e) => handleOpenMemoryChange("custom_instructions", e.target.value)} |
| #152 | className="min-h-[100px]" |
| #153 | /> |
| #154 | <p className="text-xs text-muted-foreground mt-1"> |
| #155 | Custom instructions that will be used to guide memory processing and fact extraction. |
| #156 | </p> |
| #157 | </div> |
| #158 | </CardContent> |
| #159 | </Card> |
| #160 | |
| #161 | {/* LLM Settings */} |
| #162 | <Card> |
| #163 | <CardHeader> |
| #164 | <CardTitle>LLM Settings</CardTitle> |
| #165 | <CardDescription>Configure your Large Language Model provider and settings</CardDescription> |
| #166 | </CardHeader> |
| #167 | <CardContent className="space-y-6"> |
| #168 | <div className="space-y-2"> |
| #169 | <Label htmlFor="llm-provider">LLM Provider</Label> |
| #170 | <Select |
| #171 | value={settings.mem0?.llm?.provider || ""} |
| #172 | onValueChange={handleLlmProviderChange} |
| #173 | > |
| #174 | <SelectTrigger id="llm-provider"> |
| #175 | <SelectValue placeholder="Select a provider" /> |
| #176 | </SelectTrigger> |
| #177 | <SelectContent> |
| #178 | {Object.entries(LLM_PROVIDERS).map(([provider, value]) => ( |
| #179 | <SelectItem key={value} value={value}> |
| #180 | {provider} |
| #181 | </SelectItem> |
| #182 | ))} |
| #183 | </SelectContent> |
| #184 | </Select> |
| #185 | </div> |
| #186 | |
| #187 | <div className="space-y-2"> |
| #188 | <Label htmlFor="llm-model">Model</Label> |
| #189 | <Input |
| #190 | id="llm-model" |
| #191 | placeholder="Enter model name" |
| #192 | value={settings.mem0?.llm?.config?.model || ""} |
| #193 | onChange={(e) => handleLlmConfigChange("model", e.target.value)} |
| #194 | /> |
| #195 | </div> |
| #196 | |
| #197 | {isLlmOllama && ( |
| #198 | <div className="space-y-2"> |
| #199 | <Label htmlFor="llm-ollama-url">Ollama Base URL</Label> |
| #200 | <Input |
| #201 | id="llm-ollama-url" |
| #202 | placeholder="http://host.docker.internal:11434" |
| #203 | value={settings.mem0?.llm?.config?.ollama_base_url || ""} |
| #204 | onChange={(e) => handleLlmConfigChange("ollama_base_url", e.target.value)} |
| #205 | /> |
| #206 | <p className="text-xs text-muted-foreground mt-1"> |
| #207 | Leave empty to use default: http://host.docker.internal:11434 |
| #208 | </p> |
| #209 | </div> |
| #210 | )} |
| #211 | |
| #212 | {needsLlmApiKey && ( |
| #213 | <div className="space-y-2"> |
| #214 | <Label htmlFor="llm-api-key">API Key</Label> |
| #215 | <div className="relative"> |
| #216 | <Input |
| #217 | id="llm-api-key" |
| #218 | type={showLlmApiKey ? "text" : "password"} |
| #219 | placeholder="env:API_KEY" |
| #220 | value={settings.mem0?.llm?.config?.api_key || ""} |
| #221 | onChange={(e) => handleLlmConfigChange("api_key", e.target.value)} |
| #222 | /> |
| #223 | <Button |
| #224 | variant="ghost" |
| #225 | size="icon" |
| #226 | type="button" |
| #227 | className="absolute right-2 top-1/2 transform -translate-y-1/2 h-7 w-7" |
| #228 | onClick={() => setShowLlmApiKey(!showLlmApiKey)} |
| #229 | > |
| #230 | {showLlmApiKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} |
| #231 | </Button> |
| #232 | </div> |
| #233 | <p className="text-xs text-muted-foreground mt-1"> |
| #234 | Use "env:API_KEY" to load from environment variable, or enter directly |
| #235 | </p> |
| #236 | </div> |
| #237 | )} |
| #238 | |
| #239 | <div className="flex items-center space-x-2 pt-2"> |
| #240 | <Switch id="llm-advanced-settings" checked={showLlmAdvanced} onCheckedChange={setShowLlmAdvanced} /> |
| #241 | <Label htmlFor="llm-advanced-settings">Show advanced settings</Label> |
| #242 | </div> |
| #243 | |
| #244 | {showLlmAdvanced && ( |
| #245 | <div className="space-y-6 pt-2"> |
| #246 | <div className="space-y-2"> |
| #247 | <div className="flex justify-between"> |
| #248 | <Label htmlFor="temperature">Temperature: {settings.mem0?.llm?.config?.temperature}</Label> |
| #249 | </div> |
| #250 | <Slider |
| #251 | id="temperature" |
| #252 | min={0} |
| #253 | max={1} |
| #254 | step={0.1} |
| #255 | value={[settings.mem0?.llm?.config?.temperature || 0.7]} |
| #256 | onValueChange={(value) => handleLlmConfigChange("temperature", value[0])} |
| #257 | /> |
| #258 | </div> |
| #259 | |
| #260 | <div className="space-y-2"> |
| #261 | <Label htmlFor="max-tokens">Max Tokens</Label> |
| #262 | <Input |
| #263 | id="max-tokens" |
| #264 | type="number" |
| #265 | placeholder="2000" |
| #266 | value={settings.mem0?.llm?.config?.max_tokens || ""} |
| #267 | onChange={(e) => handleLlmConfigChange("max_tokens", Number.parseInt(e.target.value) || "")} |
| #268 | /> |
| #269 | </div> |
| #270 | </div> |
| #271 | )} |
| #272 | </CardContent> |
| #273 | </Card> |
| #274 | |
| #275 | {/* Embedder Settings */} |
| #276 | <Card> |
| #277 | <CardHeader> |
| #278 | <CardTitle>Embedder Settings</CardTitle> |
| #279 | <CardDescription>Configure your Embedding Model provider and settings</CardDescription> |
| #280 | </CardHeader> |
| #281 | <CardContent className="space-y-6"> |
| #282 | <div className="space-y-2"> |
| #283 | <Label htmlFor="embedder-provider">Embedder Provider</Label> |
| #284 | <Select |
| #285 | value={settings.mem0?.embedder?.provider || ""} |
| #286 | onValueChange={handleEmbedderProviderChange} |
| #287 | > |
| #288 | <SelectTrigger id="embedder-provider"> |
| #289 | <SelectValue placeholder="Select a provider" /> |
| #290 | </SelectTrigger> |
| #291 | <SelectContent> |
| #292 | {Object.entries(EMBEDDER_PROVIDERS).map(([provider, value]) => ( |
| #293 | <SelectItem key={value} value={value}> |
| #294 | {provider} |
| #295 | </SelectItem> |
| #296 | ))} |
| #297 | </SelectContent> |
| #298 | </Select> |
| #299 | </div> |
| #300 | |
| #301 | <div className="space-y-2"> |
| #302 | <Label htmlFor="embedder-model">Model</Label> |
| #303 | <Input |
| #304 | id="embedder-model" |
| #305 | placeholder="Enter model name" |
| #306 | value={settings.mem0?.embedder?.config?.model || ""} |
| #307 | onChange={(e) => handleEmbedderConfigChange("model", e.target.value)} |
| #308 | /> |
| #309 | </div> |
| #310 | |
| #311 | {isEmbedderOllama && ( |
| #312 | <div className="space-y-2"> |
| #313 | <Label htmlFor="embedder-ollama-url">Ollama Base URL</Label> |
| #314 | <Input |
| #315 | id="embedder-ollama-url" |
| #316 | placeholder="http://host.docker.internal:11434" |
| #317 | value={settings.mem0?.embedder?.config?.ollama_base_url || ""} |
| #318 | onChange={(e) => handleEmbedderConfigChange("ollama_base_url", e.target.value)} |
| #319 | /> |
| #320 | <p className="text-xs text-muted-foreground mt-1"> |
| #321 | Leave empty to use default: http://host.docker.internal:11434 |
| #322 | </p> |
| #323 | </div> |
| #324 | )} |
| #325 | |
| #326 | {needsEmbedderApiKey && ( |
| #327 | <div className="space-y-2"> |
| #328 | <Label htmlFor="embedder-api-key">API Key</Label> |
| #329 | <div className="relative"> |
| #330 | <Input |
| #331 | id="embedder-api-key" |
| #332 | type={showEmbedderApiKey ? "text" : "password"} |
| #333 | placeholder="env:API_KEY" |
| #334 | value={settings.mem0?.embedder?.config?.api_key || ""} |
| #335 | onChange={(e) => handleEmbedderConfigChange("api_key", e.target.value)} |
| #336 | /> |
| #337 | <Button |
| #338 | variant="ghost" |
| #339 | size="icon" |
| #340 | type="button" |
| #341 | className="absolute right-2 top-1/2 transform -translate-y-1/2 h-7 w-7" |
| #342 | onClick={() => setShowEmbedderApiKey(!showEmbedderApiKey)} |
| #343 | > |
| #344 | {showEmbedderApiKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} |
| #345 | </Button> |
| #346 | </div> |
| #347 | <p className="text-xs text-muted-foreground mt-1"> |
| #348 | Use "env:API_KEY" to load from environment variable, or enter directly |
| #349 | </p> |
| #350 | </div> |
| #351 | )} |
| #352 | </CardContent> |
| #353 | </Card> |
| #354 | |
| #355 | {/* Backup (Export / Import) */} |
| #356 | <Card> |
| #357 | <CardHeader> |
| #358 | <CardTitle>Backup</CardTitle> |
| #359 | <CardDescription>Export or import your memories</CardDescription> |
| #360 | </CardHeader> |
| #361 | <CardContent className="space-y-6"> |
| #362 | {/* Export Section */} |
| #363 | <div className="p-4 border border-zinc-800 rounded-lg space-y-2"> |
| #364 | <div className="text-sm font-medium">Export</div> |
| #365 | <p className="text-xs text-muted-foreground">Download a ZIP containing your memories.</p> |
| #366 | <div> |
| #367 | <Button |
| #368 | type="button" |
| #369 | className="bg-zinc-800 hover:bg-zinc-700" |
| #370 | onClick={async () => { |
| #371 | try { |
| #372 | const res = await fetch(`${API_URL}/api/v1/backup/export`, { |
| #373 | method: "POST", |
| #374 | headers: { "Content-Type": "application/json", Accept: "application/zip" }, |
| #375 | body: JSON.stringify({ user_id: userId }), |
| #376 | }) |
| #377 | if (!res.ok) throw new Error(`Export failed with status ${res.status}`) |
| #378 | const blob = await res.blob() |
| #379 | const url = window.URL.createObjectURL(blob) |
| #380 | const a = document.createElement("a") |
| #381 | a.href = url |
| #382 | a.download = `memories_export.zip` |
| #383 | document.body.appendChild(a) |
| #384 | a.click() |
| #385 | a.remove() |
| #386 | window.URL.revokeObjectURL(url) |
| #387 | } catch (e) { |
| #388 | console.error(e) |
| #389 | alert("Export failed. Check console for details.") |
| #390 | } |
| #391 | }} |
| #392 | > |
| #393 | <Download className="h-4 w-4 mr-2" /> Export Memories |
| #394 | </Button> |
| #395 | </div> |
| #396 | </div> |
| #397 | |
| #398 | {/* Import Section */} |
| #399 | <div className="p-4 border border-zinc-800 rounded-lg space-y-2"> |
| #400 | <div className="text-sm font-medium">Import</div> |
| #401 | <p className="text-xs text-muted-foreground">Upload a ZIP exported by OpenMemory. Default settings will be used.</p> |
| #402 | <div className="flex items-center gap-3 flex-wrap"> |
| #403 | <input |
| #404 | ref={fileInputRef} |
| #405 | type="file" |
| #406 | accept=".zip" |
| #407 | className="hidden" |
| #408 | onChange={(evt) => { |
| #409 | const f = evt.target.files?.[0] |
| #410 | if (!f) return |
| #411 | setSelectedImportFileName(f.name) |
| #412 | }} |
| #413 | /> |
| #414 | <Button |
| #415 | type="button" |
| #416 | className="bg-zinc-800 hover:bg-zinc-700" |
| #417 | onClick={() => { |
| #418 | if (fileInputRef.current) fileInputRef.current.click() |
| #419 | }} |
| #420 | > |
| #421 | <Upload className="h-4 w-4 mr-2" /> Choose ZIP |
| #422 | </Button> |
| #423 | <span className="text-xs text-muted-foreground truncate max-w-[220px]"> |
| #424 | {selectedImportFileName || "No file selected"} |
| #425 | </span> |
| #426 | <div className="ml-auto"> |
| #427 | <Button |
| #428 | type="button" |
| #429 | disabled={isUploading || !fileInputRef.current} |
| #430 | className="bg-primary hover:bg-primary/80 disabled:opacity-50" |
| #431 | onClick={async () => { |
| #432 | const file = fileInputRef.current?.files?.[0] |
| #433 | if (!file) return |
| #434 | try { |
| #435 | setIsUploading(true) |
| #436 | const form = new FormData() |
| #437 | form.append("file", file) |
| #438 | form.append("user_id", String(userId)) |
| #439 | const res = await fetch(`${API_URL}/api/v1/backup/import`, { method: "POST", body: form }) |
| #440 | if (!res.ok) throw new Error(`Import failed with status ${res.status}`) |
| #441 | await res.json() |
| #442 | if (fileInputRef.current) fileInputRef.current.value = "" |
| #443 | setSelectedImportFileName("") |
| #444 | } catch (e) { |
| #445 | console.error(e) |
| #446 | alert("Import failed. Check console for details.") |
| #447 | } finally { |
| #448 | setIsUploading(false) |
| #449 | } |
| #450 | }} |
| #451 | > |
| #452 | {isUploading ? "Uploading..." : "Import"} |
| #453 | </Button> |
| #454 | </div> |
| #455 | </div> |
| #456 | </div> |
| #457 | </CardContent> |
| #458 | </Card> |
| #459 | </div> |
| #460 | ) |
| #461 | } |