Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 282feaebf2 | |||
| f5dc825d47 | |||
| 32b74e18b7 | |||
| daa4495583 | |||
| cfb5f1776e | |||
| 573e5212a4 | |||
| 8ba225cec8 | |||
| 4b09943092 |
@@ -42,9 +42,6 @@ clean:
|
||||
test:
|
||||
hatch run test
|
||||
|
||||
test-py-3.9:
|
||||
hatch run dev_py_3_9:test
|
||||
|
||||
test-py-3.10:
|
||||
hatch run dev_py_3_10:test
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mem0/cli",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"description": "The official CLI for mem0 — the memory layer for AI agents",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface AddOptions {
|
||||
infer?: boolean;
|
||||
expires?: string;
|
||||
categories?: string[];
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
@@ -29,7 +28,6 @@ export interface SearchOptions {
|
||||
keyword?: boolean;
|
||||
filters?: Record<string, unknown>;
|
||||
fields?: string[];
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
@@ -42,7 +40,6 @@ export interface ListOptions {
|
||||
category?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
enableGraph?: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteOptions {
|
||||
|
||||
@@ -115,10 +115,9 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.infer === false) payload.infer = false;
|
||||
if (opts.expires) payload.expiration_date = opts.expires;
|
||||
if (opts.categories) payload.categories = opts.categories;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
return (await this._request("POST", "/v1/memories/", {
|
||||
return (await this._request("POST", "/v3/memories/add/", {
|
||||
json: payload,
|
||||
})) as Record<string, unknown>;
|
||||
}
|
||||
@@ -176,10 +175,9 @@ export class PlatformBackend implements Backend {
|
||||
if (opts.rerank) payload.rerank = true;
|
||||
if (opts.keyword) payload.keyword_search = true;
|
||||
if (opts.fields) payload.fields = opts.fields;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/search/", {
|
||||
const result = (await this._request("POST", "/v3/memories/search/", {
|
||||
json: payload,
|
||||
})) as unknown;
|
||||
if (Array.isArray(result)) return result;
|
||||
@@ -227,10 +225,9 @@ export class PlatformBackend implements Backend {
|
||||
extraFilters: Object.keys(extra).length > 0 ? extra : undefined,
|
||||
});
|
||||
if (apiFilters) payload.filters = apiFilters;
|
||||
if (opts.enableGraph) payload.enable_graph = true;
|
||||
payload.source = "CLI";
|
||||
|
||||
const result = (await this._request("POST", "/v2/memories/", {
|
||||
const result = (await this._request("POST", "/v3/memories/", {
|
||||
json: payload,
|
||||
params,
|
||||
})) as unknown;
|
||||
|
||||
@@ -29,7 +29,6 @@ export function cmdConfigShow(opts: { output?: string } = {}): void {
|
||||
agent_id: config.defaults.agentId || null,
|
||||
app_id: config.defaults.appId || null,
|
||||
run_id: config.defaults.runId || null,
|
||||
enable_graph: config.defaults.enableGraph,
|
||||
},
|
||||
platform: {
|
||||
api_key: redactKey(config.platform.apiKey),
|
||||
@@ -56,7 +55,6 @@ export function cmdConfigShow(opts: { output?: string } = {}): void {
|
||||
]);
|
||||
table.push(["defaults.app_id", config.defaults.appId || dim("(not set)")]);
|
||||
table.push(["defaults.run_id", config.defaults.runId || dim("(not set)")]);
|
||||
table.push(["defaults.enable_graph", String(config.defaults.enableGraph)]);
|
||||
table.push(["", ""]);
|
||||
|
||||
// Platform
|
||||
|
||||
@@ -49,7 +49,6 @@ export async function cmdAdd(
|
||||
noInfer: boolean;
|
||||
expires?: string;
|
||||
categories?: string;
|
||||
enableGraph: boolean;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
@@ -140,7 +139,6 @@ export async function cmdAdd(
|
||||
infer: !opts.noInfer,
|
||||
expires: opts.expires,
|
||||
categories: cats,
|
||||
enableGraph: opts.enableGraph,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -225,7 +223,6 @@ export async function cmdSearch(
|
||||
keyword: boolean;
|
||||
filterJson?: string;
|
||||
fields?: string;
|
||||
enableGraph: boolean;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
@@ -274,7 +271,6 @@ export async function cmdSearch(
|
||||
keyword: opts.keyword,
|
||||
filters,
|
||||
fields: fieldList,
|
||||
enableGraph: opts.enableGraph,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -368,7 +364,6 @@ export async function cmdList(
|
||||
category?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
enableGraph: boolean;
|
||||
output: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
@@ -396,7 +391,6 @@ export async function cmdList(
|
||||
category: opts.category,
|
||||
after: opts.after,
|
||||
before: opts.before,
|
||||
enableGraph: opts.enableGraph,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -28,7 +28,6 @@ export interface DefaultsConfig {
|
||||
agentId: string;
|
||||
appId: string;
|
||||
runId: string;
|
||||
enableGraph: boolean;
|
||||
}
|
||||
|
||||
export interface TelemetryConfig {
|
||||
@@ -50,7 +49,6 @@ export function createDefaultConfig(): Mem0Config {
|
||||
agentId: "",
|
||||
appId: "",
|
||||
runId: "",
|
||||
enableGraph: false,
|
||||
},
|
||||
platform: {
|
||||
apiKey: "",
|
||||
@@ -87,8 +85,6 @@ export function loadConfig(): Mem0Config {
|
||||
config.defaults.agentId = defaults.agent_id ?? "";
|
||||
config.defaults.appId = defaults.app_id ?? "";
|
||||
config.defaults.runId = defaults.run_id ?? "";
|
||||
config.defaults.enableGraph = defaults.enable_graph ?? false;
|
||||
|
||||
const telemetry = data.telemetry ?? {};
|
||||
config.telemetry.anonymousId = telemetry.anonymous_id ?? "";
|
||||
}
|
||||
@@ -104,12 +100,6 @@ export function loadConfig(): Mem0Config {
|
||||
config.defaults.agentId = process.env.MEM0_AGENT_ID;
|
||||
if (process.env.MEM0_APP_ID) config.defaults.appId = process.env.MEM0_APP_ID;
|
||||
if (process.env.MEM0_RUN_ID) config.defaults.runId = process.env.MEM0_RUN_ID;
|
||||
if (process.env.MEM0_ENABLE_GRAPH) {
|
||||
config.defaults.enableGraph = ["true", "1", "yes"].includes(
|
||||
process.env.MEM0_ENABLE_GRAPH.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -123,7 +113,6 @@ export function saveConfig(config: Mem0Config): void {
|
||||
agent_id: config.defaults.agentId,
|
||||
app_id: config.defaults.appId,
|
||||
run_id: config.defaults.runId,
|
||||
enable_graph: config.defaults.enableGraph,
|
||||
},
|
||||
platform: {
|
||||
api_key: config.platform.apiKey,
|
||||
@@ -154,7 +143,6 @@ const KEY_MAP: Record<string, [keyof Mem0Config, string]> = {
|
||||
"defaults.agent_id": ["defaults", "agentId"],
|
||||
"defaults.app_id": ["defaults", "appId"],
|
||||
"defaults.run_id": ["defaults", "runId"],
|
||||
"defaults.enable_graph": ["defaults", "enableGraph"],
|
||||
// Short-form aliases
|
||||
api_key: ["platform", "apiKey"],
|
||||
base_url: ["platform", "baseUrl"],
|
||||
@@ -163,7 +151,6 @@ const KEY_MAP: Record<string, [keyof Mem0Config, string]> = {
|
||||
agent_id: ["defaults", "agentId"],
|
||||
app_id: ["defaults", "appId"],
|
||||
run_id: ["defaults", "runId"],
|
||||
enable_graph: ["defaults", "enableGraph"],
|
||||
};
|
||||
|
||||
export function getNestedValue(config: Mem0Config, dottedKey: string): unknown {
|
||||
|
||||
@@ -134,18 +134,6 @@ function resolveIds(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve graph tri-state: --no-graph > --graph > config default.
|
||||
*/
|
||||
function resolveGraph(
|
||||
config: Mem0Config,
|
||||
opts: { graph?: boolean; noGraph?: boolean },
|
||||
): boolean {
|
||||
if (opts.noGraph) return false;
|
||||
if (opts.graph) return true;
|
||||
return config.defaults.enableGraph;
|
||||
}
|
||||
|
||||
// ── Main program ──────────────────────────────────────────────────────────
|
||||
|
||||
program
|
||||
@@ -236,8 +224,6 @@ program
|
||||
.option("--no-infer", "Skip inference, store raw.")
|
||||
.option("--expires <date>", "Expiration date (YYYY-MM-DD).")
|
||||
.option("--categories <value>", "Categories (JSON array or comma-separated).")
|
||||
.option("--graph", "Enable graph memory extraction.", false)
|
||||
.option("--no-graph", "Disable graph memory extraction.")
|
||||
.option("-o, --output <format>", "Output format: text, json, quiet.", "text")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -253,9 +239,8 @@ program
|
||||
opts.baseUrl,
|
||||
);
|
||||
const ids = resolveIds(config, opts);
|
||||
const enableGraph = resolveGraph(config, opts);
|
||||
const output = isAgent ? "agent" : opts.output;
|
||||
await cmdAdd(backend, text, { ...ids, ...opts, enableGraph, output });
|
||||
await cmdAdd(backend, text, { ...ids, ...opts, output });
|
||||
});
|
||||
|
||||
// ── Memory: search ────────────────────────────────────────────────────────
|
||||
@@ -285,8 +270,6 @@ program
|
||||
.option("--keyword", "Use keyword search.", false)
|
||||
.option("--filter <json>", "Advanced filter expression (JSON).")
|
||||
.option("--fields <list>", "Specific fields to return (comma-separated).")
|
||||
.option("--graph", "Enable graph in search.", false)
|
||||
.option("--no-graph", "Disable graph in search.")
|
||||
.option("-o, --output <format>", "Output: text, json, table.", "text")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -310,7 +293,6 @@ program
|
||||
opts.baseUrl,
|
||||
);
|
||||
const ids = resolveIds(config, opts);
|
||||
const enableGraph = resolveGraph(config, opts);
|
||||
const output = isAgent ? "agent" : opts.output;
|
||||
await cmdSearch(backend, resolvedQuery, {
|
||||
...ids,
|
||||
@@ -320,7 +302,6 @@ program
|
||||
keyword: opts.keyword,
|
||||
filterJson: opts.filter,
|
||||
fields: opts.fields,
|
||||
enableGraph,
|
||||
output,
|
||||
});
|
||||
});
|
||||
@@ -364,8 +345,6 @@ program
|
||||
.option("--category <name>", "Filter by category.")
|
||||
.option("--after <date>", "Created after (YYYY-MM-DD).")
|
||||
.option("--before <date>", "Created before (YYYY-MM-DD).")
|
||||
.option("--graph", "Enable graph in listing.", false)
|
||||
.option("--no-graph", "Disable graph in listing.")
|
||||
.option("-o, --output <format>", "Output: text, json, table.", "table")
|
||||
.option("--api-key <key>", "Override API key.")
|
||||
.option("--base-url <url>", "Override API base URL.")
|
||||
@@ -381,7 +360,6 @@ program
|
||||
opts.baseUrl,
|
||||
);
|
||||
const ids = resolveIds(config, opts);
|
||||
const enableGraph = resolveGraph(config, opts);
|
||||
const output = isAgent ? "agent" : opts.output;
|
||||
await cmdList(backend, {
|
||||
...ids,
|
||||
@@ -390,7 +368,6 @@ program
|
||||
category: opts.category,
|
||||
after: opts.after,
|
||||
before: opts.before,
|
||||
enableGraph,
|
||||
output,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,22 +107,22 @@ describe("CLI Integration — help and version", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("add help has --graph flag", () => {
|
||||
it("add help has --output flag", () => {
|
||||
const result = run(["add", "--help"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("--graph");
|
||||
expect(result.stdout).toContain("--output");
|
||||
});
|
||||
|
||||
it("search help has --graph flag", () => {
|
||||
it("search help has --rerank flag", () => {
|
||||
const result = run(["search", "--help"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("--graph");
|
||||
expect(result.stdout).toContain("--rerank");
|
||||
});
|
||||
|
||||
it("list help has --graph flag", () => {
|
||||
it("list help has --category flag", () => {
|
||||
const result = run(["list", "--help"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain("--graph");
|
||||
expect(result.stdout).toContain("--category");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("cmdAdd", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(mockBackend.add).toHaveBeenCalledOnce();
|
||||
@@ -55,7 +55,7 @@ describe("cmdAdd", () => {
|
||||
messages: JSON.stringify([{ role: "user", content: "I love Python" }]),
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(mockBackend.add).toHaveBeenCalledOnce();
|
||||
@@ -67,7 +67,7 @@ describe("cmdAdd", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "json",
|
||||
});
|
||||
expect(output).toContain("results");
|
||||
@@ -79,7 +79,7 @@ describe("cmdAdd", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "quiet",
|
||||
});
|
||||
expect(output).not.toContain("dark mode");
|
||||
@@ -101,7 +101,7 @@ describe("cmdAdd deduplicates PENDING", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(output.match(/Queued/g)?.length).toBe(1);
|
||||
@@ -114,7 +114,7 @@ describe("cmdAdd deduplicates PENDING", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "json",
|
||||
});
|
||||
const data = JSON.parse(output);
|
||||
@@ -130,7 +130,7 @@ describe("cmdAdd deduplicates PENDING", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "agent",
|
||||
});
|
||||
const data = JSON.parse(output);
|
||||
@@ -148,7 +148,7 @@ describe("cmdSearch", () => {
|
||||
threshold: 0.3,
|
||||
rerank: false,
|
||||
keyword: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(output).toContain("Found 2");
|
||||
@@ -162,7 +162,7 @@ describe("cmdSearch", () => {
|
||||
threshold: 0.3,
|
||||
rerank: false,
|
||||
keyword: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "json",
|
||||
});
|
||||
expect(output).toContain("memory");
|
||||
@@ -177,7 +177,7 @@ describe("cmdSearch", () => {
|
||||
threshold: 0.3,
|
||||
rerank: false,
|
||||
keyword: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(errOutput).toContain("No memories found");
|
||||
@@ -205,7 +205,7 @@ describe("cmdList", () => {
|
||||
userId: "alice",
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
enableGraph: false,
|
||||
|
||||
output: "table",
|
||||
});
|
||||
expect(output).toContain("dark mode");
|
||||
@@ -218,7 +218,7 @@ describe("cmdList", () => {
|
||||
userId: "alice",
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
enableGraph: false,
|
||||
|
||||
output: "text",
|
||||
});
|
||||
expect(errOutput).toContain("No memories found");
|
||||
@@ -316,7 +316,7 @@ describe("agent mode", () => {
|
||||
userId: "alice",
|
||||
immutable: false,
|
||||
noInfer: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "agent",
|
||||
});
|
||||
const parsed = JSON.parse(output.trim());
|
||||
@@ -336,7 +336,7 @@ describe("agent mode", () => {
|
||||
threshold: 0.3,
|
||||
rerank: false,
|
||||
keyword: false,
|
||||
enableGraph: false,
|
||||
|
||||
output: "agent",
|
||||
});
|
||||
const parsed = JSON.parse(output.trim());
|
||||
@@ -361,7 +361,7 @@ describe("agent mode", () => {
|
||||
userId: "alice",
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
enableGraph: false,
|
||||
|
||||
output: "agent",
|
||||
});
|
||||
const parsed = JSON.parse(output.trim());
|
||||
|
||||
@@ -64,7 +64,6 @@ describe("createDefaultConfig", () => {
|
||||
expect(config.platform.baseUrl).toBe("https://api.mem0.ai");
|
||||
expect(config.platform.apiKey).toBe("");
|
||||
expect(config.defaults.userId).toBe("");
|
||||
expect(config.defaults.enableGraph).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,9 +104,4 @@ describe("setNestedValue", () => {
|
||||
expect(config.defaults.userId).toBe("bob");
|
||||
});
|
||||
|
||||
it("coerces boolean for enable_graph", () => {
|
||||
const config = createDefaultConfig();
|
||||
expect(setNestedValue(config, "defaults.enable_graph", "true")).toBe(true);
|
||||
expect(config.defaults.enableGraph).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mem0-cli"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
description = "The official CLI for mem0 — the memory layer for AI agents"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""mem0 CLI — the command-line interface for the mem0 memory layer."""
|
||||
|
||||
__version__ = "0.2.3"
|
||||
__version__ = "0.2.4"
|
||||
|
||||
@@ -267,8 +267,6 @@ def add(
|
||||
categories: str | None = typer.Option(
|
||||
None, "--categories", help="Categories (JSON array or comma-separated)."
|
||||
),
|
||||
graph: bool = typer.Option(False, "--graph", help="Enable graph memory extraction."),
|
||||
no_graph: bool = typer.Option(False, "--no-graph", help="Disable graph memory extraction."),
|
||||
output: str = typer.Option(
|
||||
"text", "--output", "-o", help="Output format: text, json, quiet.", rich_help_panel="Output"
|
||||
),
|
||||
@@ -295,13 +293,6 @@ def add(
|
||||
backend, config = _get_backend_and_config(api_key, base_url)
|
||||
ids = _resolve_ids(config, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
|
||||
|
||||
if no_graph:
|
||||
graph_enabled = False
|
||||
elif graph:
|
||||
graph_enabled = True
|
||||
else:
|
||||
graph_enabled = config.defaults.enable_graph
|
||||
|
||||
cmd_add(
|
||||
backend,
|
||||
text,
|
||||
@@ -313,7 +304,6 @@ def add(
|
||||
no_infer=no_infer,
|
||||
expires=expires,
|
||||
categories=categories,
|
||||
enable_graph=graph_enabled,
|
||||
output=output,
|
||||
)
|
||||
|
||||
@@ -357,12 +347,6 @@ def search(
|
||||
help="Specific fields to return (comma-separated).",
|
||||
rich_help_panel="Search",
|
||||
),
|
||||
graph: bool = typer.Option(
|
||||
False, "--graph", help="Enable graph in search.", rich_help_panel="Search"
|
||||
),
|
||||
no_graph: bool = typer.Option(
|
||||
False, "--no-graph", help="Disable graph in search.", rich_help_panel="Search"
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"text", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
|
||||
),
|
||||
@@ -396,13 +380,6 @@ def search(
|
||||
backend, config = _get_backend_and_config(api_key, base_url)
|
||||
ids = _resolve_ids(config, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
|
||||
|
||||
if no_graph:
|
||||
graph_enabled = False
|
||||
elif graph:
|
||||
graph_enabled = True
|
||||
else:
|
||||
graph_enabled = config.defaults.enable_graph
|
||||
|
||||
cmd_search(
|
||||
backend,
|
||||
query,
|
||||
@@ -413,7 +390,6 @@ def search(
|
||||
keyword=keyword,
|
||||
filter_json=filter_json,
|
||||
fields=fields,
|
||||
enable_graph=graph_enabled,
|
||||
output=output,
|
||||
)
|
||||
|
||||
@@ -480,12 +456,6 @@ def list_cmd(
|
||||
before: str | None = typer.Option(
|
||||
None, "--before", help="Created before (YYYY-MM-DD).", rich_help_panel="Filters"
|
||||
),
|
||||
graph: bool = typer.Option(
|
||||
False, "--graph", help="Enable graph in listing.", rich_help_panel="Filters"
|
||||
),
|
||||
no_graph: bool = typer.Option(
|
||||
False, "--no-graph", help="Disable graph in listing.", rich_help_panel="Filters"
|
||||
),
|
||||
output: str = typer.Option(
|
||||
"table", "--output", "-o", help="Output: text, json, table.", rich_help_panel="Output"
|
||||
),
|
||||
@@ -511,13 +481,6 @@ def list_cmd(
|
||||
backend, config = _get_backend_and_config(api_key, base_url)
|
||||
ids = _resolve_ids(config, user_id=user_id, agent_id=agent_id, app_id=app_id, run_id=run_id)
|
||||
|
||||
if no_graph:
|
||||
graph_enabled = False
|
||||
elif graph:
|
||||
graph_enabled = True
|
||||
else:
|
||||
graph_enabled = config.defaults.enable_graph
|
||||
|
||||
cmd_list(
|
||||
backend,
|
||||
**ids,
|
||||
@@ -526,7 +489,6 @@ def list_cmd(
|
||||
category=category,
|
||||
after=after,
|
||||
before=before,
|
||||
enable_graph=graph_enabled,
|
||||
output=output,
|
||||
)
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ class Backend(ABC):
|
||||
infer: bool = True,
|
||||
expires: str | None = None,
|
||||
categories: list[str] | None = None,
|
||||
enable_graph: bool = False,
|
||||
) -> dict: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -44,7 +43,6 @@ class Backend(ABC):
|
||||
keyword: bool = False,
|
||||
filters: dict | None = None,
|
||||
fields: list[str] | None = None,
|
||||
enable_graph: bool = False,
|
||||
) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -63,7 +61,6 @@ class Backend(ABC):
|
||||
category: str | None = None,
|
||||
after: str | None = None,
|
||||
before: str | None = None,
|
||||
enable_graph: bool = False,
|
||||
) -> list[dict]: ...
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -64,7 +64,6 @@ class PlatformBackend(Backend):
|
||||
infer: bool = True,
|
||||
expires: str | None = None,
|
||||
categories: list[str] | None = None,
|
||||
enable_graph: bool = False,
|
||||
) -> dict:
|
||||
payload: dict[str, Any] = {}
|
||||
|
||||
@@ -91,11 +90,9 @@ class PlatformBackend(Backend):
|
||||
payload["expiration_date"] = expires
|
||||
if categories:
|
||||
payload["categories"] = categories
|
||||
if enable_graph:
|
||||
payload["enable_graph"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
return self._request("POST", "/v1/memories/", json=payload)
|
||||
return self._request("POST", "/v3/memories/add/", json=payload)
|
||||
|
||||
def _build_filters(
|
||||
self,
|
||||
@@ -106,7 +103,7 @@ class PlatformBackend(Backend):
|
||||
run_id: str | None = None,
|
||||
extra_filters: dict | None = None,
|
||||
) -> dict | None:
|
||||
"""Build a filters dict for v2 API endpoints.
|
||||
"""Build a filters dict for v3 API endpoints.
|
||||
|
||||
Entity IDs are ANDed (all provided IDs must match).
|
||||
Extra filters (date ranges, categories) are also ANDed.
|
||||
@@ -152,7 +149,6 @@ class PlatformBackend(Backend):
|
||||
keyword: bool = False,
|
||||
filters: dict | None = None,
|
||||
fields: list[str] | None = None,
|
||||
enable_graph: bool = False,
|
||||
) -> list[dict]:
|
||||
payload: dict[str, Any] = {"query": query, "top_k": top_k, "threshold": threshold}
|
||||
|
||||
@@ -171,11 +167,9 @@ class PlatformBackend(Backend):
|
||||
payload["keyword_search"] = True
|
||||
if fields:
|
||||
payload["fields"] = fields
|
||||
if enable_graph:
|
||||
payload["enable_graph"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
result = self._request("POST", "/v2/memories/search/", json=payload)
|
||||
result = self._request("POST", "/v3/memories/search/", json=payload)
|
||||
return (
|
||||
result
|
||||
if isinstance(result, list)
|
||||
@@ -197,12 +191,11 @@ class PlatformBackend(Backend):
|
||||
category: str | None = None,
|
||||
after: str | None = None,
|
||||
before: str | None = None,
|
||||
enable_graph: bool = False,
|
||||
) -> list[dict]:
|
||||
payload: dict[str, Any] = {}
|
||||
params = {"page": str(page), "page_size": str(page_size)}
|
||||
|
||||
# Build filters for v2 API — entity IDs and date filters go inside "filters"
|
||||
# Build filters — entity IDs and date filters go inside "filters"
|
||||
extra: dict[str, Any] = {}
|
||||
if category:
|
||||
extra["categories"] = {"contains": category}
|
||||
@@ -220,11 +213,9 @@ class PlatformBackend(Backend):
|
||||
)
|
||||
if api_filters:
|
||||
payload["filters"] = api_filters
|
||||
if enable_graph:
|
||||
payload["enable_graph"] = True
|
||||
payload["source"] = "CLI"
|
||||
|
||||
result = self._request("POST", "/v2/memories/", json=payload, params=params)
|
||||
result = self._request("POST", "/v3/memories/", json=payload, params=params)
|
||||
return (
|
||||
result
|
||||
if isinstance(result, list)
|
||||
|
||||
@@ -39,7 +39,6 @@ def cmd_config_show(*, output: str = "text") -> None:
|
||||
"agent_id": config.defaults.agent_id or None,
|
||||
"app_id": config.defaults.app_id or None,
|
||||
"run_id": config.defaults.run_id or None,
|
||||
"enable_graph": config.defaults.enable_graph,
|
||||
},
|
||||
"platform": {
|
||||
"api_key": redact_key(config.platform.api_key),
|
||||
@@ -73,10 +72,6 @@ def cmd_config_show(*, output: str = "text") -> None:
|
||||
"defaults.run_id",
|
||||
config.defaults.run_id or f"[{DIM_COLOR}](not set)[/]",
|
||||
)
|
||||
table.add_row(
|
||||
"defaults.enable_graph",
|
||||
str(config.defaults.enable_graph).lower(),
|
||||
)
|
||||
table.add_row("", "")
|
||||
|
||||
# Platform
|
||||
|
||||
@@ -62,7 +62,6 @@ def cmd_add(
|
||||
no_infer: bool,
|
||||
expires: str | None,
|
||||
categories: str | None,
|
||||
enable_graph: bool = False,
|
||||
output: str = "text",
|
||||
) -> None:
|
||||
"""Add a memory."""
|
||||
@@ -145,7 +144,6 @@ def cmd_add(
|
||||
infer=not no_infer,
|
||||
expires=expires,
|
||||
categories=cats,
|
||||
enable_graph=enable_graph,
|
||||
)
|
||||
except Exception as e:
|
||||
ts.error_msg = str(e)
|
||||
@@ -226,7 +224,6 @@ def cmd_search(
|
||||
keyword: bool,
|
||||
filter_json: str | None,
|
||||
fields: str | None,
|
||||
enable_graph: bool = False,
|
||||
output: str = "text",
|
||||
) -> None:
|
||||
"""Search memories."""
|
||||
@@ -269,7 +266,6 @@ def cmd_search(
|
||||
keyword=keyword,
|
||||
filters=filters,
|
||||
fields=field_list,
|
||||
enable_graph=enable_graph,
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(err_console, str(e))
|
||||
@@ -356,7 +352,6 @@ def cmd_list(
|
||||
category: str | None,
|
||||
after: str | None,
|
||||
before: str | None,
|
||||
enable_graph: bool = False,
|
||||
output: str = "table",
|
||||
) -> None:
|
||||
"""List memories."""
|
||||
@@ -385,7 +380,6 @@ def cmd_list(
|
||||
category=category,
|
||||
after=after,
|
||||
before=before,
|
||||
enable_graph=enable_graph,
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(err_console, str(e))
|
||||
|
||||
@@ -36,7 +36,6 @@ class DefaultsConfig:
|
||||
agent_id: str = ""
|
||||
app_id: str = ""
|
||||
run_id: str = ""
|
||||
enable_graph: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -60,7 +59,6 @@ SHORT_KEY_ALIASES: dict[str, str] = {
|
||||
"agent_id": "defaults.agent_id",
|
||||
"app_id": "defaults.app_id",
|
||||
"run_id": "defaults.run_id",
|
||||
"enable_graph": "defaults.enable_graph",
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +89,6 @@ def load_config() -> Mem0Config:
|
||||
config.defaults.agent_id = defaults.get("agent_id", "")
|
||||
config.defaults.app_id = defaults.get("app_id", "")
|
||||
config.defaults.run_id = defaults.get("run_id", "")
|
||||
config.defaults.enable_graph = defaults.get("enable_graph", False)
|
||||
|
||||
telemetry = data.get("telemetry", {})
|
||||
config.telemetry.anonymous_id = telemetry.get("anonymous_id", "")
|
||||
|
||||
@@ -121,10 +117,6 @@ def load_config() -> Mem0Config:
|
||||
if env_run_id:
|
||||
config.defaults.run_id = env_run_id
|
||||
|
||||
env_graph = os.environ.get("MEM0_ENABLE_GRAPH")
|
||||
if env_graph:
|
||||
config.defaults.enable_graph = env_graph.lower() in ("true", "1", "yes")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -139,7 +131,6 @@ def save_config(config: Mem0Config) -> None:
|
||||
"agent_id": config.defaults.agent_id,
|
||||
"app_id": config.defaults.app_id,
|
||||
"run_id": config.defaults.run_id,
|
||||
"enable_graph": config.defaults.enable_graph,
|
||||
},
|
||||
"platform": {
|
||||
"api_key": config.platform.api_key,
|
||||
|
||||
@@ -224,24 +224,13 @@ class TestCLIIsolated:
|
||||
|
||||
|
||||
class TestCLINewFeatures:
|
||||
"""Tests for MCP parity features: --graph, --limit, entities delete."""
|
||||
"""Tests for MCP parity features: --limit, entities delete."""
|
||||
|
||||
def test_add_help_has_graph(self):
|
||||
result = _run(["add", "--help"])
|
||||
assert result.returncode == 0
|
||||
assert "--graph" in result.stdout
|
||||
|
||||
def test_search_help_has_graph_and_limit(self):
|
||||
def test_search_help_has_limit(self):
|
||||
result = _run(["search", "--help"])
|
||||
assert result.returncode == 0
|
||||
assert "--graph" in result.stdout
|
||||
assert "--limit" in result.stdout
|
||||
|
||||
def test_list_help_has_graph(self):
|
||||
result = _run(["list", "--help"])
|
||||
assert result.returncode == 0
|
||||
assert "--graph" in result.stdout
|
||||
|
||||
def test_delete_entity_via_delete_flag(self):
|
||||
"""delete --entity should appear in help output."""
|
||||
result = _run(["delete", "--help"])
|
||||
|
||||
@@ -997,85 +997,6 @@ class TestEntitiesDeleteCommand:
|
||||
mock_backend.delete_entities.assert_not_called()
|
||||
|
||||
|
||||
class TestEnableGraph:
|
||||
def test_add_with_graph(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_add(
|
||||
mock_backend,
|
||||
"test",
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
messages=None,
|
||||
file=None,
|
||||
metadata=None,
|
||||
immutable=False,
|
||||
no_infer=False,
|
||||
expires=None,
|
||||
categories=None,
|
||||
enable_graph=True,
|
||||
output="text",
|
||||
)
|
||||
call_kwargs = mock_backend.add.call_args
|
||||
assert call_kwargs.kwargs.get("enable_graph") is True
|
||||
|
||||
def test_search_with_graph(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_search(
|
||||
mock_backend,
|
||||
"test",
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
top_k=10,
|
||||
threshold=0.3,
|
||||
rerank=False,
|
||||
keyword=False,
|
||||
filter_json=None,
|
||||
fields=None,
|
||||
enable_graph=True,
|
||||
output="text",
|
||||
)
|
||||
call_kwargs = mock_backend.search.call_args
|
||||
assert call_kwargs.kwargs.get("enable_graph") is True
|
||||
|
||||
def test_list_with_graph(self, mock_backend):
|
||||
console, _buf = _make_console()
|
||||
err_console, _err_buf = _make_err_console()
|
||||
with (
|
||||
patch("mem0_cli.commands.memory.console", console),
|
||||
patch("mem0_cli.commands.memory.err_console", err_console),
|
||||
):
|
||||
cmd_list(
|
||||
mock_backend,
|
||||
user_id="alice",
|
||||
agent_id=None,
|
||||
app_id=None,
|
||||
run_id=None,
|
||||
page=1,
|
||||
page_size=100,
|
||||
category=None,
|
||||
after=None,
|
||||
before=None,
|
||||
enable_graph=True,
|
||||
output="table",
|
||||
)
|
||||
call_kwargs = mock_backend.list_memories.call_args
|
||||
assert call_kwargs.kwargs.get("enable_graph") is True
|
||||
|
||||
|
||||
class TestEventCommands:
|
||||
def test_event_list_table(self, mock_backend):
|
||||
console, buf = _make_console()
|
||||
|
||||
@@ -121,46 +121,6 @@ class TestConfig:
|
||||
assert config.defaults.agent_id == ""
|
||||
assert config.defaults.app_id == ""
|
||||
assert config.defaults.run_id == ""
|
||||
assert config.defaults.enable_graph is False
|
||||
|
||||
def test_enable_graph_save_and_load(self, isolate_config):
|
||||
config = Mem0Config()
|
||||
config.defaults.enable_graph = True
|
||||
save_config(config)
|
||||
loaded = load_config()
|
||||
assert loaded.defaults.enable_graph is True
|
||||
|
||||
def test_enable_graph_env_var_true(self, isolate_config, monkeypatch):
|
||||
monkeypatch.setenv("MEM0_ENABLE_GRAPH", "true")
|
||||
loaded = load_config()
|
||||
assert loaded.defaults.enable_graph is True
|
||||
|
||||
def test_enable_graph_env_var_false(self, isolate_config, monkeypatch):
|
||||
config = Mem0Config()
|
||||
config.defaults.enable_graph = True
|
||||
save_config(config)
|
||||
monkeypatch.setenv("MEM0_ENABLE_GRAPH", "false")
|
||||
loaded = load_config()
|
||||
assert loaded.defaults.enable_graph is False
|
||||
|
||||
def test_backward_compat_no_enable_graph_key(self, isolate_config):
|
||||
"""Old config files without 'enable_graph' key should default to False."""
|
||||
import json
|
||||
|
||||
from mem0_cli.config import CONFIG_FILE, ensure_config_dir
|
||||
|
||||
ensure_config_dir()
|
||||
data = {
|
||||
"version": 1,
|
||||
"defaults": {"user_id": "alice"},
|
||||
"platform": {"api_key": "m0-test", "base_url": "https://api.mem0.ai"},
|
||||
}
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
loaded = load_config()
|
||||
assert loaded.defaults.enable_graph is False
|
||||
assert loaded.defaults.user_id == "alice"
|
||||
|
||||
|
||||
class TestNestedAccess:
|
||||
@@ -192,11 +152,6 @@ class TestNestedAccess:
|
||||
assert set_nested_value(config, "defaults.user_id", "bob")
|
||||
assert config.defaults.user_id == "bob"
|
||||
|
||||
def test_set_defaults_enable_graph(self):
|
||||
config = Mem0Config()
|
||||
assert set_nested_value(config, "defaults.enable_graph", "true")
|
||||
assert config.defaults.enable_graph is True
|
||||
|
||||
|
||||
class TestResolveIds:
|
||||
def test_cli_flag_overrides_default(self):
|
||||
|
||||
@@ -4,6 +4,43 @@ description: "Release notes for the OpenClaw plugin and agent harness."
|
||||
mode: "wide"
|
||||
---
|
||||
|
||||
<Update label="2026-04-22" description="v1.0.9">
|
||||
|
||||
**Security & Compliance:**
|
||||
- Added top-level `requiredEnvVars` to plugin manifest, declaring env vars per mode (platform, OSS OpenAI, OSS Anthropic, OSS Ollama). Fixes ClaHub scanner "required env vars: none" mismatch
|
||||
- Added `sensitive: true` and descriptions to `apiKey` and `userEmail` in `configSchema` — previously only declared in `uiHints`
|
||||
- Added `default: false` with descriptions to `autoCapture` and `autoRecall` in `configSchema` so scanner can confirm opt-in defaults
|
||||
- Added `dataLocations` field to manifest declaring all persistence paths (config, vectorStore, historyDb, dreamState)
|
||||
- Added `privacy` field to manifest documenting data flow for platform vs open-source mode and credential storage guidance
|
||||
- Added `externalEndpoints` to `setup` section declaring api.mem0.ai and app.mem0.ai with purpose and requirement context
|
||||
|
||||
**Tests:**
|
||||
- Replaced direct `process.env` access in `tests/cli-commands.test.ts` and `tests/fs-safe.test.ts` with `vi.stubEnv`/`vi.unstubAllEnvs`. Fixes ClaHub static analysis flag for "environment variable access combined with network send"
|
||||
- 421 tests across 15 test files
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-21" description="v1.0.8">
|
||||
|
||||
**New Features:**
|
||||
- **OSS Onboarding Wizard:** New guided 4-step interactive setup for open-source mode — walks through LLM provider, embedding provider, vector store, and user ID selection with prefilled defaults
|
||||
- **Agent-Friendly CLI:** Added `--json` flag to all 16 CLI commands for machine-readable output. Agents can call `openclaw mem0 help --json` to discover every command and flag
|
||||
- **Non-Interactive OSS Setup:** Added `--mode open-source` with `--oss-llm`, `--oss-embedder`, `--oss-vector` flags for fully automated OSS configuration without prompts
|
||||
- **JSON Helpers Module:** New `cli/json-helpers.ts` with `jsonOut`, `jsonErr`, and `redactSecrets` utilities for consistent structured output
|
||||
|
||||
**Improvements:**
|
||||
- **Init Flow Redesigned:** Replaced 3-option flat menu with 2-level structure: Platform (email login or API key) and Open Source (guided wizard)
|
||||
- **Provider Selection:** LLM providers: OpenAI, Ollama, Anthropic. Embedding providers: OpenAI, Ollama. Vector stores: Qdrant, PGVector
|
||||
- **Input Prefill:** All prompts with defaults (base URL, user ID) now prefill the input field instead of showing defaults in brackets
|
||||
- **Smart Reuse:** When LLM and embedder use the same provider, API key and base URL are automatically reused from the LLM step
|
||||
- **Default Model:** Updated default LLM model to `gpt-5-mini`
|
||||
- **Manifest Compliance:** Removed undocumented fields, aligned env var declarations between SKILL.md and manifest, fixed `configSchema.required` for clean installs
|
||||
|
||||
**Tests:**
|
||||
- 404 tests across 15 test files (+3 new: `json-helpers.test.ts`, `oss-wizard.test.ts`, `cli-commands.test.ts`)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-20" description="v1.0.7">
|
||||
|
||||
**New Features:**
|
||||
|
||||
@@ -1269,6 +1269,16 @@ See the [TypeScript SDK migration guide](https://docs.mem0.ai/migration/ts-v2-to
|
||||
|
||||
<Tab title="CLI">
|
||||
|
||||
<Update label="2026-04-22" description="Python v0.2.4 / Node v0.2.4">
|
||||
|
||||
**New Features:**
|
||||
- **V3 API Routes:** Migrated `add`, `search`, and `list` commands from v1/v2 to v3 API endpoints — `POST /v3/memories/add/`, `POST /v3/memories/search/`, `POST /v3/memories/`. Aligns both CLIs with the Python and TypeScript SDKs which already use v3 ([#4916](https://github.com/mem0ai/mem0/pull/4916))
|
||||
|
||||
**Breaking Changes:**
|
||||
- **`--graph` / `--no-graph` removed:** The `enable_graph` config option, `--graph` and `--no-graph` CLI flags, and `MEM0_ENABLE_GRAPH` environment variable have been removed from both CLIs. Graph memory is now a project-level setting on the Platform ([#4916](https://github.com/mem0ai/mem0/pull/4916))
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026-04-11" description="Python v0.2.3 / Node v0.2.3">
|
||||
|
||||
**Bug Fixes:**
|
||||
|
||||
@@ -216,7 +216,7 @@ memory.delete_all(user_id="alice")
|
||||
## Put it into practice
|
||||
|
||||
- Review the <Link href="/api-reference/memory/delete-memory">Delete Memory API reference</Link>, plus <Link href="/api-reference/memory/batch-delete">Batch Delete</Link> and <Link href="/api-reference/memory/delete-memories">Filtered Delete</Link>.
|
||||
- Pair deletes with <Link href="/platform/features/expiration-date">Expiration Policies</Link> to automate retention.
|
||||
- Pair deletes with <Link href="/platform/features/platform-overview">Expiration Policies</Link> to automate retention.
|
||||
|
||||
## See it live
|
||||
|
||||
@@ -236,6 +236,6 @@ memory.delete_all(user_id="alice")
|
||||
title="Enable Expiration Policies"
|
||||
description="Automate retention with the platform’s expiration feature."
|
||||
icon="clock"
|
||||
href="/platform/features/expiration-date"
|
||||
href="/platform/features/platform-overview"
|
||||
/>
|
||||
</CardGroup>
|
||||
|
||||
@@ -32,12 +32,19 @@ export MEM0_API_KEY="m0-your-api-key"
|
||||
|
||||
### Option A — Plugin Marketplace (Recommended)
|
||||
|
||||
Install the full plugin including MCP server, lifecycle hooks, and SDK skill:
|
||||
Install the full plugin including MCP server, lifecycle hooks, and SDK skill.
|
||||
|
||||
```
|
||||
/plugin marketplace add mem0ai/mem0
|
||||
/plugin install mem0@mem0-plugins
|
||||
```
|
||||
1. Add the Mem0 marketplace:
|
||||
|
||||
```
|
||||
/plugin marketplace add mem0ai/mem0
|
||||
```
|
||||
|
||||
2. Install the plugin:
|
||||
|
||||
```
|
||||
/plugin install mem0@mem0-plugins
|
||||
```
|
||||
|
||||
**Claude Cowork desktop app:** Open the Cowork tab, click **Customize** in the sidebar, click **Browse plugins**, and install Mem0.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ The plugin provides:
|
||||
2. **Auto-Capture** — After the agent responds, the exchange is sent to Mem0 which decides what's worth keeping
|
||||
3. **Agent Tools** — Eight tools for explicit memory operations during conversations
|
||||
|
||||
Both auto-recall and auto-capture run silently with no manual configuration required.
|
||||
Both auto-recall and auto-capture are opt-in (`autoRecall: true`, `autoCapture: true` in config). Once enabled, they run silently with no manual intervention required.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -147,7 +147,81 @@ OpenClaw treats memory plugins as an exclusive slot. Installing the plugin alone
|
||||
|
||||
### Open-Source Mode (Self-hosted)
|
||||
|
||||
No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
|
||||
No Mem0 key needed. Defaults use OpenAI (`gpt-5-mini` for LLM, `text-embedding-3-small` for embeddings) — requires `OPENAI_API_KEY`. For a fully local setup, use Ollama for both.
|
||||
|
||||
#### Option 1: Interactive Wizard (Recommended)
|
||||
|
||||
Run the guided 4-step wizard:
|
||||
|
||||
```bash
|
||||
openclaw mem0 init --mode open-source
|
||||
```
|
||||
|
||||
The wizard walks you through:
|
||||
|
||||
<Steps>
|
||||
<Step title="LLM provider">
|
||||
Choose OpenAI (`gpt-5-mini`), Ollama (`llama3.1:8b`, fully local), or Anthropic (`claude-sonnet-4-5-20250514`). Provide an API key or base URL as needed.
|
||||
</Step>
|
||||
<Step title="Embedding provider">
|
||||
Choose OpenAI (`text-embedding-3-small`) or Ollama (`nomic-embed-text`, local). If the same provider was chosen for LLM, the API key and URL are reused automatically.
|
||||
</Step>
|
||||
<Step title="Vector store">
|
||||
Choose Qdrant (`http://localhost:6333`) or PGVector (PostgreSQL). Connectivity is verified before proceeding.
|
||||
</Step>
|
||||
<Step title="User ID">
|
||||
Set your memory namespace identifier.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### Option 2: Non-Interactive Setup
|
||||
|
||||
For CI/CD, scripts, or agent-driven setup — pass all options as flags:
|
||||
|
||||
```bash
|
||||
# Fully local with Ollama + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm ollama --oss-embedder ollama --oss-vector qdrant
|
||||
|
||||
# OpenAI + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm openai --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector qdrant
|
||||
|
||||
# Anthropic LLM + OpenAI embeddings + PGVector
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm anthropic --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector pgvector --oss-vector-user postgres --oss-vector-password secret
|
||||
```
|
||||
|
||||
Add `--json` for machine-readable output (useful when an LLM agent is driving the setup).
|
||||
|
||||
<Accordion title="All --oss-* flags">
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--oss-llm <provider>` | `openai`, `ollama`, or `anthropic` |
|
||||
| `--oss-llm-key <key>` | API key for LLM provider |
|
||||
| `--oss-llm-model <model>` | Override default LLM model |
|
||||
| `--oss-llm-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-embedder <provider>` | `openai` or `ollama` |
|
||||
| `--oss-embedder-key <key>` | API key for embedder |
|
||||
| `--oss-embedder-model <model>` | Override default embedder model |
|
||||
| `--oss-embedder-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-vector <provider>` | `qdrant` or `pgvector` |
|
||||
| `--oss-vector-url <url>` | Qdrant server URL (default: `http://localhost:6333`) |
|
||||
| `--oss-vector-host <host>` | PGVector host |
|
||||
| `--oss-vector-port <port>` | PGVector port |
|
||||
| `--oss-vector-user <user>` | PGVector user |
|
||||
| `--oss-vector-password <pw>` | PGVector password |
|
||||
| `--oss-vector-dbname <db>` | PGVector database name |
|
||||
| `--oss-vector-dims <n>` | Override embedding dimensions |
|
||||
</Accordion>
|
||||
|
||||
#### Option 3: Manual Config
|
||||
|
||||
Minimal config — uses OpenAI defaults:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -168,7 +242,7 @@ No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
|
||||
}
|
||||
```
|
||||
|
||||
Sensible defaults work out of the box. To customize the embedder, vector store, or LLM:
|
||||
To customize providers:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -184,8 +258,8 @@ Sensible defaults work out of the box. To customize the embedder, vector store,
|
||||
"userId": "your-user-id",
|
||||
"oss": {
|
||||
"embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
|
||||
"vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-4o" } }
|
||||
"vectorStore": { "provider": "qdrant", "config": { "url": "http://localhost:6333" } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-5-mini" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +299,8 @@ The `memory_search` and `memory_list` tools accept a `scope` parameter (`"sessio
|
||||
|
||||
## CLI Commands
|
||||
|
||||
All commands support `--json` for machine-readable output — useful when an LLM agent drives the CLI programmatically. Run `openclaw mem0 help --json` to discover every command and flag.
|
||||
|
||||
```bash
|
||||
# Search all memories (long-term + session)
|
||||
openclaw mem0 search "what languages does the user know"
|
||||
@@ -238,6 +314,10 @@ openclaw mem0 search "what languages does the user know" --scope session
|
||||
# List all memories
|
||||
openclaw mem0 list
|
||||
openclaw mem0 list --user-id alice --top-k 20
|
||||
|
||||
# JSON output (any command)
|
||||
openclaw mem0 search "preferences" --json
|
||||
openclaw mem0 status --json
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
@@ -248,8 +328,8 @@ openclaw mem0 list --user-id alice --top-k 20
|
||||
|-----|------|---------|-------------|
|
||||
| `mode` | `"platform"` \| `"open-source"` | `"platform"` | Which backend to use |
|
||||
| `userId` | `string` | OS username | Scope memories per user |
|
||||
| `autoRecall` | `boolean` | `true` | Inject memories before each turn |
|
||||
| `autoCapture` | `boolean` | `true` | Store facts after each turn |
|
||||
| `autoRecall` | `boolean` | `false` | Inject memories before each turn (opt-in) |
|
||||
| `autoCapture` | `boolean` | `false` | Store facts after each turn (opt-in) |
|
||||
| `topK` | `number` | `5` | Max memories per recall |
|
||||
| `searchThreshold` | `number` | `0.3` | Min similarity (0–1) |
|
||||
|
||||
@@ -275,7 +355,7 @@ openclaw mem0 list --user-id alice --top-k 20
|
||||
| `oss.historyDbPath` | `string` | — | SQLite path for memory edit history |
|
||||
| `oss.disableHistory` | `boolean` | `false` | Disable memory edit history tracking |
|
||||
|
||||
Everything inside `oss` is optional — defaults use OpenAI embeddings (`text-embedding-3-small`), in-memory vector store, and OpenAI LLM.
|
||||
Everything inside `oss` is optional — defaults use OpenAI embeddings (`text-embedding-3-small`), in-memory vector store, and OpenAI LLM (`gpt-5-mini`).
|
||||
|
||||
## Plugin Management
|
||||
|
||||
@@ -338,6 +418,64 @@ If `openclaw plugins update` fails:
|
||||
openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
## Privacy & Security
|
||||
|
||||
### Data Flow
|
||||
|
||||
| Mode | Where data goes | Storage |
|
||||
|------|----------------|---------|
|
||||
| **Platform** | Conversations sent to `api.mem0.ai` for extraction and storage | Mem0 cloud |
|
||||
| **Open-source** | Embeddings generated via configured provider (default: OpenAI API). Vectors stored locally. | `~/.mem0/vector_store.db` (SQLite) |
|
||||
|
||||
### Enabling Auto-Capture and Auto-Recall
|
||||
|
||||
Auto-capture and auto-recall are disabled by default (opt-in). To enable either or both:
|
||||
|
||||
```json5
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"openclaw-mem0": {
|
||||
"config": {
|
||||
"autoCapture": true, // send conversations to Mem0 for extraction
|
||||
"autoRecall": true // inject relevant memories into context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without these enabled, the agent can still use memory tools (`memory_add`, `memory_search`, etc.) explicitly — only the automatic background behavior is off.
|
||||
|
||||
### Credential Protection
|
||||
|
||||
The plugin never stores API keys, tokens, or secrets as memories. Five independent layers enforce this:
|
||||
|
||||
1. **Triage gate** — The extraction prompt rejects values matching known credential patterns (`sk-`, `m0-`, `ghp_`, `AKIA`, `Bearer`, `password=`, `token=`, `secret=`)
|
||||
2. **Dream cleanup** — Periodic memory consolidation deletes any memories that slipped through containing credential patterns
|
||||
3. **Extraction instructions** — Default extraction rules explicitly instruct the model to store only that a credential was configured, never the value
|
||||
4. **Configurable patterns** — Add custom credential patterns via `skills.triage.credentialPatterns`
|
||||
5. **CLI redaction** — `openclaw mem0 config show` redacts sensitive fields (`apiKey`, `oss.*.config.apiKey`)
|
||||
|
||||
### API Key Storage
|
||||
|
||||
Plugin config is stored in `~/.openclaw/openclaw.json` with file permissions `0o600` (owner-read-only). For production deployments, use environment variable references (`${MEM0_API_KEY}`) or SecretRef objects instead of plaintext keys.
|
||||
|
||||
### Telemetry
|
||||
|
||||
Anonymous usage telemetry (PostHog) is enabled by default to help improve the plugin. No conversation content or memory values are included — only event counts (recall, capture, tool usage, CLI commands).
|
||||
|
||||
To opt out, set the environment variable:
|
||||
|
||||
```bash
|
||||
export MEM0_TELEMETRY=false
|
||||
```
|
||||
|
||||
### System Prompt Context
|
||||
|
||||
The plugin injects memory-related instructions into the agent's system context via OpenClaw's `prependSystemContext` mechanism. This includes the memory triage protocol and recalled memories. This is the standard OpenClaw plugin SDK pattern for memory backends — no user-facing prompts are modified.
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Zero Configuration** — Auto-recall and auto-capture work out of the box with no prompting required
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
title: 'Full Stack'
|
||||
---
|
||||
|
||||
The Full Stack app example can be found [here](https://github.com/mem0ai/mem0/tree/main/embedchain/examples/full_stack).
|
||||
|
||||
This guide will help you setup the full stack app on your local machine.
|
||||
|
||||
### 🐳 Docker Setup
|
||||
|
||||
- Create a `docker-compose.yml` file and paste the following code in it.
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
backend:
|
||||
container_name: embedchain-backend
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
image: embedchain/backend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
frontend:
|
||||
container_name: embedchain-frontend
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: frontend
|
||||
dockerfile: Dockerfile
|
||||
image: embedchain/frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- "backend"
|
||||
```
|
||||
|
||||
- Run the following command,
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
📝 Note: The build command might take a while to install all the packages depending on your system resources.
|
||||
|
||||

|
||||
|
||||
### 🚀 Usage Instructions
|
||||
|
||||
- Go to [http://localhost:3000/](http://localhost:3000/) in your browser to view the dashboard.
|
||||
- Add your `OpenAI API key` 🔑 in the Settings.
|
||||
- Create a new bot and you'll be navigated to its page.
|
||||
- Here you can add your data sources and then chat with the bot.
|
||||
|
||||
🎉 Happy Chatting! 🎉
|
||||
@@ -182,7 +182,6 @@
|
||||
"examples/rest-api/check-status"
|
||||
]
|
||||
},
|
||||
"examples/full_stack",
|
||||
"examples/openai-assistant",
|
||||
"examples/opensource-assistant",
|
||||
"examples/nextjs-assistant",
|
||||
|
||||
@@ -17,9 +17,6 @@ Chatbots, especially those powered by Large Language Models (LLMs), have a wide
|
||||
Embedchain provides the right set of tools to create chatbots for the above use cases. Refer to the following examples of chatbots on and you can built on top of these examples:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Full Stack Chatbot" href="/examples/full_stack" icon="link">
|
||||
Learn to integrate a chatbot within a full-stack application.
|
||||
</Card>
|
||||
<Card title="Custom GPT Creation" href="https://app.embedchain.ai/create-your-gpt/" target="_blank" icon="link">
|
||||
Build a tailored GPT chatbot suited for your specific needs.
|
||||
</Card>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
gradio==4.11.0
|
||||
gradio>=4.14.0
|
||||
embedchain
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
chainlit==0.7.700
|
||||
embedchain==0.1.31
|
||||
embedchain==0.1.57
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
discord==2.3.1
|
||||
embedchain==0.0.58
|
||||
embedchain==0.1.57
|
||||
python-dotenv==1.0.0
|
||||
@@ -1 +0,0 @@
|
||||
.git
|
||||
@@ -1,18 +0,0 @@
|
||||
## 🐳 Docker Setup
|
||||
|
||||
- To setup full stack app using docker, run the following command inside this folder using your terminal.
|
||||
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
📝 Note: The build command might take a while to install all the packages depending on your system resources.
|
||||
|
||||
## 🚀 Usage Instructions
|
||||
|
||||
- Go to [http://localhost:3000/](http://localhost:3000/) in your browser to view the dashboard.
|
||||
- Add your `OpenAI API key` 🔑 in the Settings.
|
||||
- Create a new bot and you'll be navigated to its page.
|
||||
- Here you can add your data sources and then chat with the bot.
|
||||
|
||||
🎉 Happy Chatting! 🎉
|
||||
@@ -1,7 +0,0 @@
|
||||
__pycache__/
|
||||
database
|
||||
pyenv
|
||||
venv
|
||||
.env
|
||||
.git
|
||||
trash_files/
|
||||
@@ -1,6 +0,0 @@
|
||||
__pycache__
|
||||
database
|
||||
pyenv
|
||||
venv
|
||||
.env
|
||||
trash_files/
|
||||
@@ -1,11 +0,0 @@
|
||||
FROM python:3.11-slim AS backend
|
||||
|
||||
WORKDIR /usr/src/app/backend
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["python", "server.py"]
|
||||
@@ -1,14 +0,0 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
class APIKey(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(255), nullable=False)
|
||||
|
||||
|
||||
class BotList(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255), nullable=False)
|
||||
slug = db.Column(db.String(255), nullable=False, unique=True)
|
||||
@@ -1,5 +0,0 @@
|
||||
import os
|
||||
|
||||
ROOT_DIRECTORY = os.getcwd()
|
||||
DB_DIRECTORY_OPEN_AI = os.path.join(os.getcwd(), "database", "open_ai")
|
||||
DB_DIRECTORY_OPEN_SOURCE = os.path.join(os.getcwd(), "database", "open_source")
|
||||
@@ -1,32 +0,0 @@
|
||||
import os
|
||||
|
||||
from flask import Blueprint, jsonify, make_response, request
|
||||
from models import APIKey
|
||||
from paths import DB_DIRECTORY_OPEN_AI
|
||||
|
||||
from embedchain import App
|
||||
|
||||
chat_response_bp = Blueprint("chat_response", __name__)
|
||||
|
||||
|
||||
# Chat Response for user query
|
||||
@chat_response_bp.route("/api/get_answer", methods=["POST"])
|
||||
def get_answer():
|
||||
try:
|
||||
data = request.get_json()
|
||||
query = data.get("query")
|
||||
embedding_model = data.get("embedding_model")
|
||||
app_type = data.get("app_type")
|
||||
|
||||
if embedding_model == "open_ai":
|
||||
os.chdir(DB_DIRECTORY_OPEN_AI)
|
||||
api_key = APIKey.query.first().key
|
||||
os.environ["OPENAI_API_KEY"] = api_key
|
||||
if app_type == "app":
|
||||
chat_bot = App()
|
||||
|
||||
response = chat_bot.chat(query)
|
||||
return make_response(jsonify({"response": response}), 200)
|
||||
|
||||
except Exception as e:
|
||||
return make_response(jsonify({"error": str(e)}), 400)
|
||||
@@ -1,72 +0,0 @@
|
||||
from flask import Blueprint, jsonify, make_response, request
|
||||
from models import APIKey, BotList, db
|
||||
|
||||
dashboard_bp = Blueprint("dashboard", __name__)
|
||||
|
||||
|
||||
# Set Open AI Key
|
||||
@dashboard_bp.route("/api/set_key", methods=["POST"])
|
||||
def set_key():
|
||||
data = request.get_json()
|
||||
api_key = data["openAIKey"]
|
||||
existing_key = APIKey.query.first()
|
||||
if existing_key:
|
||||
existing_key.key = api_key
|
||||
else:
|
||||
new_key = APIKey(key=api_key)
|
||||
db.session.add(new_key)
|
||||
db.session.commit()
|
||||
return make_response(jsonify(message="API key saved successfully"), 200)
|
||||
|
||||
|
||||
# Check OpenAI Key
|
||||
@dashboard_bp.route("/api/check_key", methods=["GET"])
|
||||
def check_key():
|
||||
existing_key = APIKey.query.first()
|
||||
if existing_key:
|
||||
return make_response(jsonify(status="ok", message="OpenAI Key exists"), 200)
|
||||
else:
|
||||
return make_response(jsonify(status="fail", message="No OpenAI Key present"), 200)
|
||||
|
||||
|
||||
# Create a bot
|
||||
@dashboard_bp.route("/api/create_bot", methods=["POST"])
|
||||
def create_bot():
|
||||
data = request.get_json()
|
||||
name = data["name"]
|
||||
slug = name.lower().replace(" ", "_")
|
||||
existing_bot = BotList.query.filter_by(slug=slug).first()
|
||||
if existing_bot:
|
||||
return (make_response(jsonify(message="Bot already exists"), 400),)
|
||||
new_bot = BotList(name=name, slug=slug)
|
||||
db.session.add(new_bot)
|
||||
db.session.commit()
|
||||
return make_response(jsonify(message="Bot created successfully"), 200)
|
||||
|
||||
|
||||
# Delete a bot
|
||||
@dashboard_bp.route("/api/delete_bot", methods=["POST"])
|
||||
def delete_bot():
|
||||
data = request.get_json()
|
||||
slug = data.get("slug")
|
||||
bot = BotList.query.filter_by(slug=slug).first()
|
||||
if bot:
|
||||
db.session.delete(bot)
|
||||
db.session.commit()
|
||||
return make_response(jsonify(message="Bot deleted successfully"), 200)
|
||||
return make_response(jsonify(message="Bot not found"), 400)
|
||||
|
||||
|
||||
# Get the list of bots
|
||||
@dashboard_bp.route("/api/get_bots", methods=["GET"])
|
||||
def get_bots():
|
||||
bots = BotList.query.all()
|
||||
bot_list = []
|
||||
for bot in bots:
|
||||
bot_list.append(
|
||||
{
|
||||
"name": bot.name,
|
||||
"slug": bot.slug,
|
||||
}
|
||||
)
|
||||
return jsonify(bot_list)
|
||||
@@ -1,27 +0,0 @@
|
||||
import os
|
||||
|
||||
from flask import Blueprint, jsonify, make_response, request
|
||||
from models import APIKey
|
||||
from paths import DB_DIRECTORY_OPEN_AI
|
||||
|
||||
from embedchain import App
|
||||
|
||||
sources_bp = Blueprint("sources", __name__)
|
||||
|
||||
|
||||
# API route to add data sources
|
||||
@sources_bp.route("/api/add_sources", methods=["POST"])
|
||||
def add_sources():
|
||||
try:
|
||||
embedding_model = request.json.get("embedding_model")
|
||||
name = request.json.get("name")
|
||||
value = request.json.get("value")
|
||||
if embedding_model == "open_ai":
|
||||
os.chdir(DB_DIRECTORY_OPEN_AI)
|
||||
api_key = APIKey.query.first().key
|
||||
os.environ["OPENAI_API_KEY"] = api_key
|
||||
chat_bot = App()
|
||||
chat_bot.add(name, value)
|
||||
return make_response(jsonify(message="Sources added successfully"), 200)
|
||||
except Exception as e:
|
||||
return make_response(jsonify(message=f"Error adding sources: {str(e)}"), 400)
|
||||
@@ -1,27 +0,0 @@
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from models import db
|
||||
from paths import DB_DIRECTORY_OPEN_AI, ROOT_DIRECTORY
|
||||
from routes.chat_response import chat_response_bp
|
||||
from routes.dashboard import dashboard_bp
|
||||
from routes.sources import sources_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(ROOT_DIRECTORY, "database", "user_data.db")
|
||||
app.register_blueprint(dashboard_bp)
|
||||
app.register_blueprint(sources_bp)
|
||||
app.register_blueprint(chat_response_bp)
|
||||
|
||||
|
||||
# Initialize the app on startup
|
||||
def load_app():
|
||||
os.makedirs(DB_DIRECTORY_OPEN_AI, exist_ok=True)
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
load_app()
|
||||
app.run(host="0.0.0.0", debug=True, port=8000)
|
||||
@@ -1,24 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
backend:
|
||||
container_name: embedchain-backend
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: backend
|
||||
dockerfile: Dockerfile
|
||||
image: embedchain/backend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
frontend:
|
||||
container_name: embedchain-frontend
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: frontend
|
||||
dockerfile: Dockerfile
|
||||
image: embedchain/frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- "backend"
|
||||
@@ -1,7 +0,0 @@
|
||||
node_modules/
|
||||
build
|
||||
dist
|
||||
.env
|
||||
.git
|
||||
.next/
|
||||
trash_files/
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["next/babel", "next/core-web-vitals"]
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
vscode/
|
||||
trash_files/
|
||||
@@ -1,14 +0,0 @@
|
||||
FROM node:18-slim AS frontend
|
||||
|
||||
WORKDIR /usr/src/app/frontend
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "start"]
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/api/:path*",
|
||||
destination: "http://backend:8000/api/:path*",
|
||||
},
|
||||
];
|
||||
},
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
proxyTimeout: 6000000,
|
||||
},
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ["@svgr/webpack"],
|
||||
});
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "8.44.0",
|
||||
"eslint-config-next": "13.4.9",
|
||||
"flowbite": "^1.7.0",
|
||||
"next": "13.4.9",
|
||||
"postcss": "8.4.25",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"tailwindcss": "3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "^8.0.1"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,20 +0,0 @@
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14 2C14 2.74028 13.5978 3.38663 13 3.73244V4H20C21.6569 4 23 5.34315 23 7V19C23 20.6569 21.6569 22 20 22H4C2.34315 22 1 20.6569 1 19V7C1 5.34315 2.34315 4 4 4H11V3.73244C10.4022 3.38663 10 2.74028 10 2C10 0.895431 10.8954 0 12 0C13.1046 0 14 0.895431 14 2ZM4 6H11H13H20C20.5523 6 21 6.44772 21 7V19C21 19.5523 20.5523 20 20 20H4C3.44772 20 3 19.5523 3 19V7C3 6.44772 3.44772 6 4 6ZM15 11.5C15 10.6716 15.6716 10 16.5 10C17.3284 10 18 10.6716 18 11.5C18 12.3284 17.3284 13 16.5 13C15.6716 13 15 12.3284 15 11.5ZM16.5 8C14.567 8 13 9.567 13 11.5C13 13.433 14.567 15 16.5 15C18.433 15 20 13.433 20 11.5C20 9.567 18.433 8 16.5 8ZM7.5 10C6.67157 10 6 10.6716 6 11.5C6 12.3284 6.67157 13 7.5 13C8.32843 13 9 12.3284 9 11.5C9 10.6716 8.32843 10 7.5 10ZM4 11.5C4 9.567 5.567 8 7.5 8C9.433 8 11 9.567 11 11.5C11 13.433 9.433 15 7.5 15C5.567 15 4 13.433 4 11.5ZM10.8944 16.5528C10.6474 16.0588 10.0468 15.8586 9.55279 16.1056C9.05881 16.3526 8.85858 16.9532 9.10557 17.4472C9.68052 18.5971 10.9822 19 12 19C13.0178 19 14.3195 18.5971 14.8944 17.4472C15.1414 16.9532 14.9412 16.3526 14.4472 16.1056C13.9532 15.8586 13.3526 16.0588 13.1056 16.5528C13.0139 16.7362 12.6488 17 12 17C11.3512 17 10.9861 16.7362 10.8944 16.5528Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,14 +0,0 @@
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 14 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 252 B |
@@ -1,15 +0,0 @@
|
||||
<svg
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M18.8,16l5.5-5.5c0.8-0.8,0.8-2,0-2.8l0,0C24,7.3,23.5,7,23,7c-0.5,0-1,0.2-1.4,0.6L16,13.2l-5.5-5.5 c-0.8-0.8-2.1-0.8-2.8,0C7.3,8,7,8.5,7,9.1s0.2,1,0.6,1.4l5.5,5.5l-5.5,5.5C7.3,21.9,7,22.4,7,23c0,0.5,0.2,1,0.6,1.4 C8,24.8,8.5,25,9,25c0.5,0,1-0.2,1.4-0.6l5.5-5.5l5.5,5.5c0.8,0.8,2.1,0.8,2.8,0c0.8-0.8,0.8-2.1,0-2.8L18.8,16z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 625 B |
@@ -1,9 +0,0 @@
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 22 21"
|
||||
>
|
||||
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z" />
|
||||
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 369 B |
@@ -1,15 +0,0 @@
|
||||
<svg
|
||||
fill="currentColor"
|
||||
viewBox="0 0 56 56"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M 15.5547 53.125 L 40.4453 53.125 C 45.2969 53.125 47.7109 50.6640 47.7109 45.7890 L 47.7109 24.5078 L 30.7422 24.5078 C 27.7422 24.5078 26.3359 23.0781 26.3359 20.0781 L 26.3359 2.8750 L 15.5547 2.8750 C 10.7266 2.8750 8.2891 5.3594 8.2891 10.2344 L 8.2891 45.7890 C 8.2891 50.6875 10.7266 53.125 15.5547 53.125 Z M 30.8125 21.2969 L 47.4531 21.2969 C 47.2891 20.3359 46.6094 19.3984 45.5078 18.2500 L 32.5703 5.1015 C 31.4922 3.9766 30.5078 3.2969 29.5234 3.1328 L 29.5234 20.0313 C 29.5234 20.875 29.9687 21.2969 30.8125 21.2969 Z M 18.9766 34.6562 C 18.0156 34.6562 17.3359 33.9766 17.3359 33.0625 C 17.3359 32.1484 18.0156 31.4687 18.9766 31.4687 L 37.0469 31.4687 C 37.9844 31.4687 38.7109 32.1484 38.7109 33.0625 C 38.7109 33.9766 37.9844 34.6562 37.0469 34.6562 Z M 18.9766 43.5859 C 18.0156 43.5859 17.3359 42.9062 17.3359 41.9922 C 17.3359 41.0781 18.0156 40.3984 18.9766 40.3984 L 37.0469 40.3984 C 37.9844 40.3984 38.7109 41.0781 38.7109 41.9922 C 38.7109 42.9062 37.9844 43.5859 37.0469 43.5859 Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,12 +0,0 @@
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
fill-rule="evenodd"
|
||||
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"
|
||||
></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 415 B |
@@ -1,14 +0,0 @@
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 10 6"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 4 4 4-4"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 230 B |
@@ -1,14 +0,0 @@
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 10 6"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5 5 1 1 5"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 254 B |
@@ -1,39 +0,0 @@
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<title>github [#142]</title> <desc>Created with Sketch.</desc>
|
||||
<defs> </defs>
|
||||
<g
|
||||
id="Page-1"
|
||||
stroke="none"
|
||||
stroke-width="1"
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
>
|
||||
<g
|
||||
id="Dribbble-Light-Preview"
|
||||
transform="translate(-140.000000, -7559.000000)"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||
<path
|
||||
d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399"
|
||||
id="github-[#142]"
|
||||
>
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,17 +0,0 @@
|
||||
<svg
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<title>linkedin</title>
|
||||
<path d="M28.778 1.004h-25.56c-0.008-0-0.017-0-0.027-0-1.199 0-2.172 0.964-2.186 2.159v25.672c0.014 1.196 0.987 2.161 2.186 2.161 0.010 0 0.019-0 0.029-0h25.555c0.008 0 0.018 0 0.028 0 1.2 0 2.175-0.963 2.194-2.159l0-0.002v-25.67c-0.019-1.197-0.994-2.161-2.195-2.161-0.010 0-0.019 0-0.029 0h0.001zM9.9 26.562h-4.454v-14.311h4.454zM7.674 10.293c-1.425 0-2.579-1.155-2.579-2.579s1.155-2.579 2.579-2.579c1.424 0 2.579 1.154 2.579 2.578v0c0 0.001 0 0.002 0 0.004 0 1.423-1.154 2.577-2.577 2.577-0.001 0-0.002 0-0.003 0h0zM26.556 26.562h-4.441v-6.959c0-1.66-0.034-3.795-2.314-3.795-2.316 0-2.669 1.806-2.669 3.673v7.082h-4.441v-14.311h4.266v1.951h0.058c0.828-1.395 2.326-2.315 4.039-2.315 0.061 0 0.121 0.001 0.181 0.003l-0.009-0c4.5 0 5.332 2.962 5.332 6.817v7.855z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,28 +0,0 @@
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path
|
||||
d="M3.5 8H3V7H3.5C3.77614 7 4 7.22386 4 7.5C4 7.77614 3.77614 8 3.5 8Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
d="M7 10V7H7.5C7.77614 7 8 7.22386 8 7.5V9.5C8 9.77614 7.77614 10 7.5 10H7Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M1 1.5C1 0.671573 1.67157 0 2.5 0H10.7071L14 3.29289V13.5C14 14.3284 13.3284 15 12.5 15H2.5C1.67157 15 1 14.3284 1 13.5V1.5ZM3.5 6H2V11H3V9H3.5C4.32843 9 5 8.32843 5 7.5C5 6.67157 4.32843 6 3.5 6ZM7.5 6H6V11H7.5C8.32843 11 9 10.3284 9 9.5V7.5C9 6.67157 8.32843 6 7.5 6ZM10 11V6H13V7H11V8H12V9H11V11H10Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 947 B |
@@ -1,13 +0,0 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 227 B |
@@ -1,57 +0,0 @@
|
||||
<svg
|
||||
viewBox="-0.5 0 25 25"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
{" "}
|
||||
<path
|
||||
d="M12 7.82001H22"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>{" "}
|
||||
<path
|
||||
d="M2 7.82001H4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>{" "}
|
||||
<path
|
||||
d="M20 16.82H22"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>{" "}
|
||||
<path
|
||||
d="M2 16.82H12"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>{" "}
|
||||
<path
|
||||
d="M8 11.82C10.2091 11.82 12 10.0291 12 7.82001C12 5.61087 10.2091 3.82001 8 3.82001C5.79086 3.82001 4 5.61087 4 7.82001C4 10.0291 5.79086 11.82 8 11.82Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>{" "}
|
||||
<path
|
||||
d="M16 20.82C18.2091 20.82 20 19.0291 20 16.82C20 14.6109 18.2091 12.82 16 12.82C13.7909 12.82 12 14.6109 12 16.82C12 19.0291 13.7909 20.82 16 20.82Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>{" "}
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,61 +0,0 @@
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<rect
|
||||
height="4"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
width="4"
|
||||
x="10"
|
||||
y="4"
|
||||
></rect>
|
||||
<rect
|
||||
height="4"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
width="4"
|
||||
x="10"
|
||||
y="16"
|
||||
></rect>
|
||||
<rect
|
||||
height="4"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
width="4"
|
||||
x="3"
|
||||
y="16"
|
||||
></rect>
|
||||
<rect
|
||||
height="4"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
width="4"
|
||||
x="17"
|
||||
y="16"
|
||||
></rect>
|
||||
<path
|
||||
d="M12 8V12M12 16V12M12 12H17C18.1046 12 19 12.8954 19 14V16M12 12H7C5.89543 12 5 12.8954 5 14V16"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,21 +0,0 @@
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path
|
||||
d="M12 3V21M9 21H15M19 6V3H5V6"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 445 B |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="svg5" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1668.56 1221.19"
|
||||
style="enable-background:new 0 0 1668.56 1221.19;" xml:space="preserve">
|
||||
<g id="layer1" transform="translate(52.390088,-25.058597)">
|
||||
<path id="path1009" d="M283.94,167.31l386.39,516.64L281.5,1104h87.51l340.42-367.76L984.48,1104h297.8L874.15,558.3l361.92-390.99
|
||||
h-87.51l-313.51,338.7l-253.31-338.7H283.94z M412.63,231.77h136.81l604.13,807.76h-136.81L412.63,231.77z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 722 B |
@@ -1,20 +0,0 @@
|
||||
<svg
|
||||
fill="currentColor"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 24 24"
|
||||
enable-background="new 0 0 24 24"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M18.41,0.01H4V9h2V2.01h10v6h6v14H4v2h20V5.6L18.41,0.01z M18,2.43l3.59,3.59H18V2.43z M21,18v2h-1h-1h-1v-8h2v6H21z M7,12 H6v2h1v6h2v-6h1v-2H9H7z M15,12l-1,1.61L13,12v0h-2v8h2v-4.21l1,1.61l1-1.61V20h2v-8L15,12L15,12z M3,15H2v-3H0v8h2v-3h1v3h2v-8H3 V15z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 689 B |
@@ -1,39 +0,0 @@
|
||||
<svg
|
||||
viewBox="0 -3 20 20"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<title>youtube [#168]</title> <desc>Created with Sketch.</desc>
|
||||
<defs> </defs>
|
||||
<g
|
||||
id="Page-1"
|
||||
stroke="none"
|
||||
stroke-width="1"
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
>
|
||||
<g
|
||||
id="Dribbble-Light-Preview"
|
||||
transform="translate(-300.000000, -7442.000000)"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||
<path
|
||||
d="M251.988432,7291.58588 L251.988432,7285.97425 C253.980638,7286.91168 255.523602,7287.8172 257.348463,7288.79353 C255.843351,7289.62824 253.980638,7290.56468 251.988432,7291.58588 M263.090998,7283.18289 C262.747343,7282.73013 262.161634,7282.37809 261.538073,7282.26141 C259.705243,7281.91336 248.270974,7281.91237 246.439141,7282.26141 C245.939097,7282.35515 245.493839,7282.58153 245.111335,7282.93357 C243.49964,7284.42947 244.004664,7292.45151 244.393145,7293.75096 C244.556505,7294.31342 244.767679,7294.71931 245.033639,7294.98558 C245.376298,7295.33761 245.845463,7295.57995 246.384355,7295.68865 C247.893451,7296.0008 255.668037,7296.17532 261.506198,7295.73552 C262.044094,7295.64178 262.520231,7295.39147 262.895762,7295.02447 C264.385932,7293.53455 264.28433,7285.06174 263.090998,7283.18289"
|
||||
id="youtube-[#168]"
|
||||
>
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,9 +0,0 @@
|
||||
export default function PageWrapper({ children }) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex pt-4 px-4 sm:ml-64 min-h-screen">
|
||||
<div className="flex-grow pt-4 px-4 rounded-lg">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
export default function BotWrapper({ children }) {
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-lg">
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex items-center justify-center h-10 w-10 rounded-full bg-black text-white flex-shrink-0">
|
||||
B
|
||||
</div>
|
||||
<div className="ml-3 text-sm bg-white py-2 px-4 shadow-lg rounded-xl">
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
export default function HumanWrapper({ children }) {
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-lg">
|
||||
<div className="flex items-center justify-start flex-row-reverse">
|
||||
<div className="flex items-center justify-center h-10 w-10 rounded-full bg-blue-800 text-white flex-shrink-0">
|
||||
H
|
||||
</div>
|
||||
<div className="mr-3 text-sm bg-blue-200 py-2 px-4 shadow-lg rounded-xl">
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function CreateBot() {
|
||||
const [botName, setBotName] = useState("");
|
||||
const [status, setStatus] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
const handleCreateBot = async (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
name: botName,
|
||||
};
|
||||
|
||||
const response = await fetch("/api/create_bot", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const botSlug = botName.toLowerCase().replace(/\s+/g, "_");
|
||||
router.push(`/${botSlug}/app`);
|
||||
} else {
|
||||
setBotName("");
|
||||
setStatus("fail");
|
||||
setTimeout(() => {
|
||||
setStatus("");
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
{/* Create Bot */}
|
||||
<h2 className="text-xl font-bold text-gray-800">CREATE BOT</h2>
|
||||
<form className="py-2" onSubmit={handleCreateBot}>
|
||||
<label
|
||||
htmlFor="bot_name"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Name of Bot
|
||||
</label>
|
||||
<div className="flex flex-col sm:flex-row gap-x-4 gap-y-4">
|
||||
<input
|
||||
type="text"
|
||||
id="bot_name"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="Eg. Naval Ravikant"
|
||||
required
|
||||
value={botName}
|
||||
onChange={(e) => setBotName(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="h-fit text-white bg-black hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
{status === "fail" && (
|
||||
<div className="text-red-600 text-sm font-bold py-1">
|
||||
An error occurred while creating your bot!
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function DeleteBot() {
|
||||
const [bots, setBots] = useState([]);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBots = async () => {
|
||||
const response = await fetch("/api/get_bots");
|
||||
const data = await response.json();
|
||||
setBots(data);
|
||||
};
|
||||
fetchBots();
|
||||
}, []);
|
||||
|
||||
const handleDeleteBot = async (event) => {
|
||||
event.preventDefault();
|
||||
const selectedBotSlug = event.target.bot_name.value;
|
||||
if (selectedBotSlug === "none") {
|
||||
return;
|
||||
}
|
||||
const response = await fetch("/api/delete_bot", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ slug: selectedBotSlug }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
router.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{bots.length !== 0 && (
|
||||
<div className="w-full">
|
||||
{/* Delete Bot */}
|
||||
<h2 className="text-xl font-bold text-gray-800">DELETE BOTS</h2>
|
||||
<form className="py-2" onSubmit={handleDeleteBot}>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">
|
||||
List of Bots
|
||||
</label>
|
||||
<div className="flex flex-col sm:flex-row gap-x-4 gap-y-4">
|
||||
<select
|
||||
name="bot_name"
|
||||
defaultValue="none"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
>
|
||||
<option value="none">Select a Bot</option>
|
||||
{bots.map((bot) => (
|
||||
<option key={bot.slug} value={bot.slug}>
|
||||
{bot.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
className="h-fit text-white bg-red-600 hover:bg-red-600/90 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export default function PurgeChats() {
|
||||
const [status, setStatus] = useState("");
|
||||
const handleChatsPurge = (event) => {
|
||||
event.preventDefault();
|
||||
localStorage.clear();
|
||||
setStatus("success");
|
||||
setTimeout(() => {
|
||||
setStatus(false);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
{/* Purge Chats */}
|
||||
<h2 className="text-xl font-bold text-gray-800">PURGE CHATS</h2>
|
||||
<form className="py-2" onSubmit={handleChatsPurge}>
|
||||
<label className="block mb-2 text-sm font-medium text-red-600">
|
||||
Warning
|
||||
</label>
|
||||
<div className="flex flex-col sm:flex-row gap-x-4 gap-y-4">
|
||||
<div
|
||||
type="text"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
>
|
||||
The following action will clear all your chat logs. Proceed with
|
||||
caution!
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="h-fit text-white bg-red-600 hover:bg-red-600/80 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center"
|
||||
>
|
||||
Purge
|
||||
</button>
|
||||
</div>
|
||||
{status === "success" && (
|
||||
<div className="text-green-600 text-sm font-bold py-1">
|
||||
Your chats have been purged!
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export default function SetOpenAIKey({ setIsKeyPresent }) {
|
||||
const [openAIKey, setOpenAIKey] = useState("");
|
||||
const [status, setStatus] = useState("");
|
||||
|
||||
const handleOpenAIKey = async (e) => {
|
||||
e.preventDefault();
|
||||
const response = await fetch("/api/set_key", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ openAIKey }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setOpenAIKey("");
|
||||
setStatus("success");
|
||||
setIsKeyPresent(true);
|
||||
} else {
|
||||
setStatus("fail");
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setStatus("");
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
{/* Set Open AI Key */}
|
||||
<h2 className="text-xl font-bold text-gray-800">SET OPENAI KEY</h2>
|
||||
<form className="py-2" onSubmit={handleOpenAIKey}>
|
||||
<label
|
||||
htmlFor="openai_key"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
OpenAI Key
|
||||
</label>
|
||||
<div className="flex flex-col sm:flex-row gap-x-4 gap-y-4">
|
||||
<input
|
||||
type="password"
|
||||
id="openai_key"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="Enter Open AI Key here"
|
||||
required
|
||||
value={openAIKey}
|
||||
onChange={(e) => setOpenAIKey(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="h-fit text-white bg-black hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
{status === "success" && (
|
||||
<div className="text-green-600 text-sm font-bold py-1">
|
||||
Your Open AI key has been saved successfully!
|
||||
</div>
|
||||
)}
|
||||
{status === "fail" && (
|
||||
<div className="text-red-600 text-sm font-bold py-1">
|
||||
An error occurred while saving your OpenAI Key!
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import BotWrapper from "@/components/chat/BotWrapper";
|
||||
import HumanWrapper from "@/components/chat/HumanWrapper";
|
||||
import SetSources from "@/containers/SetSources";
|
||||
|
||||
export default function ChatWindow({ embedding_model, app_type, setBotTitle }) {
|
||||
const [bot, setBot] = useState(null);
|
||||
const [chats, setChats] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectChat, setSelectChat] = useState(true);
|
||||
|
||||
const router = useRouter();
|
||||
const { bot_slug } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
if (bot_slug) {
|
||||
const fetchBots = async () => {
|
||||
const response = await fetch("/api/get_bots");
|
||||
const data = await response.json();
|
||||
const matchingBot = data.find((item) => item.slug === bot_slug);
|
||||
setBot(matchingBot);
|
||||
setBotTitle(matchingBot.name);
|
||||
};
|
||||
fetchBots();
|
||||
}
|
||||
}, [bot_slug]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedChats = localStorage.getItem(`chat_${bot_slug}_${app_type}`);
|
||||
if (storedChats) {
|
||||
const parsedChats = JSON.parse(storedChats);
|
||||
setChats(parsedChats.chats);
|
||||
}
|
||||
}, [app_type, bot_slug]);
|
||||
|
||||
const handleChatResponse = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
const queryInput = e.target.query.value;
|
||||
e.target.query.value = "";
|
||||
const chatEntry = {
|
||||
sender: "H",
|
||||
message: queryInput,
|
||||
};
|
||||
setChats((prevChats) => [...prevChats, chatEntry]);
|
||||
|
||||
const response = await fetch("/api/get_answer", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
query: queryInput,
|
||||
embedding_model,
|
||||
app_type,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
const botResponse = data.response;
|
||||
const botEntry = {
|
||||
sender: "B",
|
||||
message: botResponse,
|
||||
};
|
||||
setIsLoading(false);
|
||||
setChats((prevChats) => [...prevChats, botEntry]);
|
||||
const savedChats = {
|
||||
chats: [...chats, chatEntry, botEntry],
|
||||
};
|
||||
localStorage.setItem(
|
||||
`chat_${bot_slug}_${app_type}`,
|
||||
JSON.stringify(savedChats)
|
||||
);
|
||||
} else {
|
||||
router.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<div className="space-y-4 overflow-x-auto h-full pb-8">
|
||||
{/* Greeting Message */}
|
||||
<BotWrapper>
|
||||
Hi, I am {bot?.name}. How can I help you today?
|
||||
</BotWrapper>
|
||||
|
||||
{/* Chat Messages */}
|
||||
{chats.map((chat, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{chat.sender === "B" ? (
|
||||
<BotWrapper>{chat.message}</BotWrapper>
|
||||
) : (
|
||||
<HumanWrapper>{chat.message}</HumanWrapper>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
{/* Loader */}
|
||||
{isLoading && (
|
||||
<BotWrapper>
|
||||
<div className="flex items-center justify-center space-x-2 animate-pulse">
|
||||
<div className="w-2 h-2 bg-black rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-black rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-black rounded-full"></div>
|
||||
</div>
|
||||
</BotWrapper>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white fixed bottom-0 left-0 right-0 h-28 sm:h-16"></div>
|
||||
|
||||
{/* Query Form */}
|
||||
<div className="flex flex-row gap-x-2 sticky bottom-3">
|
||||
<SetSources
|
||||
setChats={setChats}
|
||||
embedding_model={embedding_model}
|
||||
setSelectChat={setSelectChat}
|
||||
/>
|
||||
{selectChat && (
|
||||
<form
|
||||
onSubmit={handleChatResponse}
|
||||
className="w-full flex flex-col sm:flex-row gap-y-2 gap-x-2"
|
||||
>
|
||||
<div className="w-full">
|
||||
<input
|
||||
id="query"
|
||||
name="query"
|
||||
type="text"
|
||||
placeholder="Enter your query..."
|
||||
className="text-sm w-full border-2 border-black rounded-xl focus:outline-none focus:border-blue-800 sm:pl-4 h-11"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full sm:w-fit">
|
||||
<button
|
||||
type="submit"
|
||||
id="sender"
|
||||
disabled={isLoading}
|
||||
className={`${
|
||||
isLoading ? "opacity-60" : ""
|
||||
} w-full bg-black hover:bg-blue-800 rounded-xl text-lg text-white px-6 h-11`}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import PlusIcon from "../../public/icons/plus.svg";
|
||||
import CrossIcon from "../../public/icons/cross.svg";
|
||||
import YoutubeIcon from "../../public/icons/youtube.svg";
|
||||
import PDFIcon from "../../public/icons/pdf.svg";
|
||||
import WebIcon from "../../public/icons/web.svg";
|
||||
import DocIcon from "../../public/icons/doc.svg";
|
||||
import SitemapIcon from "../../public/icons/sitemap.svg";
|
||||
import TextIcon from "../../public/icons/text.svg";
|
||||
|
||||
export default function SetSources({
|
||||
setChats,
|
||||
embedding_model,
|
||||
setSelectChat,
|
||||
}) {
|
||||
const [sourceName, setSourceName] = useState("");
|
||||
const [sourceValue, setSourceValue] = useState("");
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const dataTypes = {
|
||||
youtube_video: "YouTube Video",
|
||||
pdf_file: "PDF File",
|
||||
web_page: "Web Page",
|
||||
doc_file: "Doc File",
|
||||
sitemap: "Sitemap",
|
||||
text: "Text",
|
||||
};
|
||||
|
||||
const dataIcons = {
|
||||
youtube_video: <YoutubeIcon className="w-5 h-5 mr-3" />,
|
||||
pdf_file: <PDFIcon className="w-5 h-5 mr-3" />,
|
||||
web_page: <WebIcon className="w-5 h-5 mr-3" />,
|
||||
doc_file: <DocIcon className="w-5 h-5 mr-3" />,
|
||||
sitemap: <SitemapIcon className="w-5 h-5 mr-3" />,
|
||||
text: <TextIcon className="w-5 h-5 mr-3" />,
|
||||
};
|
||||
|
||||
const handleDropdownClose = () => {
|
||||
setIsDropdownOpen(false);
|
||||
setSourceName("");
|
||||
setSelectChat(true);
|
||||
};
|
||||
const handleDropdownSelect = (dataType) => {
|
||||
setSourceName(dataType);
|
||||
setSourceValue("");
|
||||
setIsDropdownOpen(false);
|
||||
setSelectChat(false);
|
||||
};
|
||||
|
||||
const handleAddDataSource = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
const addDataSourceEntry = {
|
||||
sender: "B",
|
||||
message: `Adding the following ${dataTypes[sourceName]}: ${sourceValue}`,
|
||||
};
|
||||
setChats((prevChats) => [...prevChats, addDataSourceEntry]);
|
||||
let name = sourceName;
|
||||
let value = sourceValue;
|
||||
setSourceValue("");
|
||||
const response = await fetch("/api/add_sources", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
embedding_model,
|
||||
name,
|
||||
value,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (response.ok) {
|
||||
const successEntry = {
|
||||
sender: "B",
|
||||
message: `Successfully added ${dataTypes[sourceName]}!`,
|
||||
};
|
||||
setChats((prevChats) => [...prevChats, successEntry]);
|
||||
} else {
|
||||
const errorEntry = {
|
||||
sender: "B",
|
||||
message: `Failed to add ${dataTypes[sourceName]}. Please try again.`,
|
||||
};
|
||||
setChats((prevChats) => [...prevChats, errorEntry]);
|
||||
}
|
||||
setSourceName("");
|
||||
setIsLoading(false);
|
||||
setSelectChat(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-fit">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||
className="w-fit p-2.5 rounded-xl text-white bg-black hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300"
|
||||
>
|
||||
<PlusIcon className="w-6 h-6" />
|
||||
</button>
|
||||
{isDropdownOpen && (
|
||||
<div className="absolute left-0 bottom-full bg-white border border-gray-300 rounded-lg shadow-lg mb-2">
|
||||
<ul className="py-1">
|
||||
<li
|
||||
className="block px-4 py-2 text-sm text-black cursor-pointer hover:bg-gray-200"
|
||||
onClick={handleDropdownClose}
|
||||
>
|
||||
<span className="flex items-center text-red-600">
|
||||
<CrossIcon className="w-5 h-5 mr-3" />
|
||||
Close
|
||||
</span>
|
||||
</li>
|
||||
{Object.entries(dataTypes).map(([key, value]) => (
|
||||
<li
|
||||
key={key}
|
||||
className="block px-4 py-2 text-sm text-black cursor-pointer hover:bg-gray-200"
|
||||
onClick={() => handleDropdownSelect(key)}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
{dataIcons[key]}
|
||||
{value}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{sourceName && (
|
||||
<form
|
||||
onSubmit={handleAddDataSource}
|
||||
className="w-full flex flex-col sm:flex-row gap-y-2 gap-x-2 items-center"
|
||||
>
|
||||
<div className="w-full">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter URL, Data or File path here..."
|
||||
className="text-sm w-full border-2 border-black rounded-xl focus:outline-none focus:border-blue-800 sm:pl-4 h-11"
|
||||
required
|
||||
value={sourceValue}
|
||||
onChange={(e) => setSourceValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full sm:w-fit">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`${
|
||||
isLoading ? "opacity-60" : ""
|
||||
} w-full bg-black hover:bg-blue-800 rounded-xl text-lg text-white px-6 h-11`}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import DrawerIcon from "../../public/icons/drawer.svg";
|
||||
import SettingsIcon from "../../public/icons/settings.svg";
|
||||
import BotIcon from "../../public/icons/bot.svg";
|
||||
import DropdownIcon from "../../public/icons/dropdown.svg";
|
||||
import TwitterIcon from "../../public/icons/twitter.svg";
|
||||
import GithubIcon from "../../public/icons/github.svg";
|
||||
import LinkedinIcon from "../../public/icons/linkedin.svg";
|
||||
|
||||
export default function Sidebar() {
|
||||
const [bots, setBots] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBots = async () => {
|
||||
const response = await fetch("/api/get_bots");
|
||||
const data = await response.json();
|
||||
setBots(data);
|
||||
};
|
||||
|
||||
fetchBots();
|
||||
}, []);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
const dropdown = document.getElementById("dropdown-toggle");
|
||||
dropdown.classList.toggle("hidden");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Toggle */}
|
||||
<button
|
||||
data-drawer-target="logo-sidebar"
|
||||
data-drawer-toggle="logo-sidebar"
|
||||
aria-controls="logo-sidebar"
|
||||
type="button"
|
||||
className="inline-flex items-center p-2 mt-2 ml-3 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-200"
|
||||
>
|
||||
<DrawerIcon className="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
id="logo-sidebar"
|
||||
className="fixed top-0 left-0 z-40 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0"
|
||||
>
|
||||
<div className="flex flex-col h-full px-3 py-4 overflow-y-auto bg-gray-100">
|
||||
<div className="pb-10">
|
||||
<Link href="/" className="flex items-center justify-evenly mb-5">
|
||||
<Image
|
||||
src="/images/embedchain.png"
|
||||
alt="Embedchain Logo"
|
||||
width={45}
|
||||
height={0}
|
||||
className="block h-auto w-auto"
|
||||
/>
|
||||
<span className="self-center text-2xl font-bold whitespace-nowrap">
|
||||
Embedchain
|
||||
</span>
|
||||
</Link>
|
||||
<ul className="space-y-2 font-medium text-lg">
|
||||
{/* Settings */}
|
||||
<li>
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-200 group"
|
||||
>
|
||||
<SettingsIcon className="w-6 h-6 text-gray-600 transition duration-75 group-hover:text-gray-900" />
|
||||
<span className="ml-3">Settings</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
{/* Bots */}
|
||||
{bots.length !== 0 && (
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-200"
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
<BotIcon className="w-6 h-6 text-gray-600 transition duration-75 group-hover:text-gray-900" />
|
||||
<span className="flex-1 ml-3 text-left whitespace-nowrap">
|
||||
Bots
|
||||
</span>
|
||||
<DropdownIcon className="w-3 h-3" />
|
||||
</button>
|
||||
<ul
|
||||
id="dropdown-toggle"
|
||||
className="hidden text-sm py-2 space-y-2"
|
||||
>
|
||||
{bots.map((bot, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${bot.slug}/app`}
|
||||
className="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-200"
|
||||
>
|
||||
{bot.name}
|
||||
</Link>
|
||||
</li>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-gray-200 absolute bottom-0 left-0 right-0 h-20"></div>
|
||||
|
||||
{/* Social Icons */}
|
||||
<div className="mt-auto mb-3 flex flex-row justify-evenly sticky bottom-3">
|
||||
<a href="https://twitter.com/embedchain" target="blank">
|
||||
<TwitterIcon className="w-6 h-6 text-gray-600 transition duration-75 hover:text-gray-900" />
|
||||
</a>
|
||||
<a href="https://github.com/embedchain/embedchain" target="blank">
|
||||
<GithubIcon className="w-6 h-6 text-gray-600 transition duration-75 hover:text-gray-900" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/company/embedchain"
|
||||
target="blank"
|
||||
>
|
||||
<LinkedinIcon className="w-6 h-6 text-gray-600 transition duration-75 hover:text-gray-900" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import Wrapper from "@/components/PageWrapper";
|
||||
import Sidebar from "@/containers/Sidebar";
|
||||
import ChatWindow from "@/containers/ChatWindow";
|
||||
import { useState } from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
export default function App() {
|
||||
const [botTitle, setBotTitle] = useState("");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{botTitle}</title>
|
||||
</Head>
|
||||
<Sidebar />
|
||||
<Wrapper>
|
||||
<ChatWindow
|
||||
embedding_model="open_ai"
|
||||
app_type="app"
|
||||
setBotTitle={setBotTitle}
|
||||
/>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import "@/styles/globals.css";
|
||||
import Script from "next/script";
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.7.0/flowbite.min.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<link
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.7.0/flowbite.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import Wrapper from "@/components/PageWrapper";
|
||||
import Sidebar from "@/containers/Sidebar";
|
||||
import CreateBot from "@/components/dashboard/CreateBot";
|
||||
import SetOpenAIKey from "@/components/dashboard/SetOpenAIKey";
|
||||
import PurgeChats from "@/components/dashboard/PurgeChats";
|
||||
import DeleteBot from "@/components/dashboard/DeleteBot";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Home() {
|
||||
const [isKeyPresent, setIsKeyPresent] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/check_key")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
setIsKeyPresent(true);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sidebar />
|
||||
<Wrapper>
|
||||
<div className="text-center">
|
||||
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl">
|
||||
Welcome to Embedchain Playground
|
||||
</h1>
|
||||
<p className="mb-6 text-lg font-normal text-gray-500 lg:text-xl">
|
||||
Embedchain is a Data Platform for LLMs - Load, index, retrieve, and sync any unstructured data
|
||||
dataset
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`pt-6 gap-y-4 gap-x-8 ${
|
||||
isKeyPresent ? "grid lg:grid-cols-2" : "w-[50%] mx-auto"
|
||||
}`}
|
||||
>
|
||||
<SetOpenAIKey setIsKeyPresent={setIsKeyPresent} />
|
||||
{isKeyPresent && (
|
||||
<>
|
||||
<CreateBot />
|
||||
<DeleteBot />
|
||||
<PurgeChats />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,15 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/containers/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./node_modules/flowbite/**/*.js",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [require("flowbite/plugin")],
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
fastapi==0.104.0
|
||||
uvicorn==0.23.2
|
||||
streamlit==1.29.0
|
||||
embedchain==0.1.3
|
||||
slack-sdk==3.21.3
|
||||
embedchain==0.1.57
|
||||
slack-sdk==3.21.3
|
||||
flask==2.3.3
|
||||
fastapi-poe==0.0.16
|
||||
discord==2.3.2
|
||||
twilio==8.5.0
|
||||
huggingface-hub==0.17.3
|
||||
embedchain[community, opensource, elasticsearch, opensearch, weaviate, pinecone, qdrant, images, cohere, together, milvus, vertexai, llama2, gmail, json]==0.1.3
|
||||
embedchain[community, opensource, elasticsearch, opensearch, weaviate, pinecone, qdrant, images, cohere, together, milvus, vertexai, llama2, gmail, json]==0.1.57
|
||||
sqlalchemy==2.0.22
|
||||
python-multipart==0.0.6
|
||||
youtube-transcript-api==0.6.1
|
||||
|
||||
@@ -108,7 +108,7 @@ pysbd = "^0.3.4"
|
||||
mem0ai = "^0.1.54"
|
||||
tiktoken = { version = "^0.7.0", optional = true }
|
||||
sentence-transformers = { version = "^2.2.2", optional = true }
|
||||
torch = { version = "2.3.0", optional = true }
|
||||
torch = { version = ">=2.6.0,<3", optional = true }
|
||||
# Torch 2.0.1 is not compatible with poetry (https://github.com/pytorch/pytorch/issues/100974)
|
||||
gpt4all = { version = "2.0.2", optional = true }
|
||||
# 1.0.9 is not working for some users (https://github.com/nomic-ai/gpt4all/issues/1394)
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.477.0",
|
||||
"next": "15.2.0",
|
||||
"next": "15.2.8",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.0.1",
|
||||
@@ -45,7 +45,7 @@
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.0",
|
||||
"eslint-config-next": "15.2.8",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
|
||||
@@ -1239,15 +1239,15 @@ class Memory(MemoryBase):
|
||||
def _process_metadata_filters(self, metadata_filters: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Process enhanced metadata filters and convert them to vector store compatible format.
|
||||
|
||||
|
||||
Args:
|
||||
metadata_filters: Enhanced metadata filters with operators
|
||||
|
||||
|
||||
Returns:
|
||||
Dict of processed filters compatible with vector store
|
||||
"""
|
||||
processed_filters = {}
|
||||
|
||||
|
||||
def process_condition(key: str, condition: Any) -> Dict[str, Any]:
|
||||
if not isinstance(condition, dict):
|
||||
# Simple equality: {"key": "value"}
|
||||
@@ -1255,7 +1255,7 @@ class Memory(MemoryBase):
|
||||
# Wildcard: match everything for this field (implementation depends on vector store)
|
||||
return {key: "*"}
|
||||
return {key: condition}
|
||||
|
||||
|
||||
result = {}
|
||||
for operator, value in condition.items():
|
||||
# Map platform operators to universal format that can be translated by each vector store
|
||||
@@ -1271,6 +1271,14 @@ class Memory(MemoryBase):
|
||||
raise ValueError(f"Unsupported metadata filter operator: {operator}")
|
||||
return result
|
||||
|
||||
def merge_filters(target: Dict[str, Any], source: Dict[str, Any]) -> None:
|
||||
"""Merge source into target, deep-merging nested operator dicts for the same key."""
|
||||
for key, value in source.items():
|
||||
if key in target and isinstance(target[key], dict) and isinstance(value, dict):
|
||||
target[key].update(value)
|
||||
else:
|
||||
target[key] = value
|
||||
|
||||
for key, value in metadata_filters.items():
|
||||
if key == "AND":
|
||||
# Logical AND: combine multiple conditions
|
||||
@@ -1278,7 +1286,7 @@ class Memory(MemoryBase):
|
||||
raise ValueError("AND operator requires a list of conditions")
|
||||
for condition in value:
|
||||
for sub_key, sub_value in condition.items():
|
||||
processed_filters.update(process_condition(sub_key, sub_value))
|
||||
merge_filters(processed_filters, process_condition(sub_key, sub_value))
|
||||
elif key == "OR":
|
||||
# Logical OR: Pass through to vector store for implementation-specific handling
|
||||
if not isinstance(value, list) or not value:
|
||||
@@ -1288,7 +1296,7 @@ class Memory(MemoryBase):
|
||||
for condition in value:
|
||||
or_condition = {}
|
||||
for sub_key, sub_value in condition.items():
|
||||
or_condition.update(process_condition(sub_key, sub_value))
|
||||
merge_filters(or_condition, process_condition(sub_key, sub_value))
|
||||
processed_filters["$or"].append(or_condition)
|
||||
elif key == "NOT":
|
||||
# Logical NOT: Pass through to vector store for implementation-specific handling
|
||||
@@ -1298,11 +1306,11 @@ class Memory(MemoryBase):
|
||||
for condition in value:
|
||||
not_condition = {}
|
||||
for sub_key, sub_value in condition.items():
|
||||
not_condition.update(process_condition(sub_key, sub_value))
|
||||
merge_filters(not_condition, process_condition(sub_key, sub_value))
|
||||
processed_filters["$not"].append(not_condition)
|
||||
else:
|
||||
processed_filters.update(process_condition(key, value))
|
||||
|
||||
merge_filters(processed_filters, process_condition(key, value))
|
||||
|
||||
return processed_filters
|
||||
|
||||
def _has_advanced_operators(self, filters: Dict[str, Any]) -> bool:
|
||||
@@ -2682,6 +2690,14 @@ class AsyncMemory(MemoryBase):
|
||||
raise ValueError(f"Unsupported metadata filter operator: {operator}")
|
||||
return result
|
||||
|
||||
def merge_filters(target: Dict[str, Any], source: Dict[str, Any]) -> None:
|
||||
"""Merge source into target, deep-merging nested operator dicts for the same key."""
|
||||
for key, value in source.items():
|
||||
if key in target and isinstance(target[key], dict) and isinstance(value, dict):
|
||||
target[key].update(value)
|
||||
else:
|
||||
target[key] = value
|
||||
|
||||
for key, value in metadata_filters.items():
|
||||
if key == "AND":
|
||||
# Logical AND: combine multiple conditions
|
||||
@@ -2689,7 +2705,7 @@ class AsyncMemory(MemoryBase):
|
||||
raise ValueError("AND operator requires a list of conditions")
|
||||
for condition in value:
|
||||
for sub_key, sub_value in condition.items():
|
||||
processed_filters.update(process_condition(sub_key, sub_value))
|
||||
merge_filters(processed_filters, process_condition(sub_key, sub_value))
|
||||
elif key == "OR":
|
||||
# Logical OR: Pass through to vector store for implementation-specific handling
|
||||
if not isinstance(value, list) or not value:
|
||||
@@ -2699,7 +2715,7 @@ class AsyncMemory(MemoryBase):
|
||||
for condition in value:
|
||||
or_condition = {}
|
||||
for sub_key, sub_value in condition.items():
|
||||
or_condition.update(process_condition(sub_key, sub_value))
|
||||
merge_filters(or_condition, process_condition(sub_key, sub_value))
|
||||
processed_filters["$or"].append(or_condition)
|
||||
elif key == "NOT":
|
||||
# Logical NOT: Pass through to vector store for implementation-specific handling
|
||||
@@ -2709,10 +2725,10 @@ class AsyncMemory(MemoryBase):
|
||||
for condition in value:
|
||||
not_condition = {}
|
||||
for sub_key, sub_value in condition.items():
|
||||
not_condition.update(process_condition(sub_key, sub_value))
|
||||
merge_filters(not_condition, process_condition(sub_key, sub_value))
|
||||
processed_filters["$not"].append(not_condition)
|
||||
else:
|
||||
processed_filters.update(process_condition(key, value))
|
||||
merge_filters(processed_filters, process_condition(key, value))
|
||||
|
||||
return processed_filters
|
||||
|
||||
|
||||
@@ -66,7 +66,14 @@ class ElasticsearchDB(VectorStoreBase):
|
||||
"index": True,
|
||||
"similarity": "cosine",
|
||||
},
|
||||
"metadata": {"type": "object", "properties": {"user_id": {"type": "keyword"}}},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "keyword"},
|
||||
"agent_id": {"type": "keyword"},
|
||||
"run_id": {"type": "keyword"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -55,7 +55,14 @@ class OpenSearchDB(VectorStoreBase):
|
||||
"dimension": self.embedding_model_dims,
|
||||
"method": {"engine": "nmslib", "name": "hnsw", "space_type": "cosinesimil"},
|
||||
},
|
||||
"metadata": {"type": "object", "properties": {"user_id": {"type": "keyword"}}},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "keyword"},
|
||||
"agent_id": {"type": "keyword"},
|
||||
"run_id": {"type": "keyword"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Long-term memory for [OpenClaw](https://github.com/openclaw/openclaw) agents, powered by [Mem0](https://mem0.ai).
|
||||
|
||||
Your agent forgets everything between sessions. This plugin fixes that — it watches conversations, extracts what matters, and brings it back when relevant. Automatically.
|
||||
Your agent forgets everything between sessions. This plugin fixes that — it stores conversations, extracts what matters, and brings it back when relevant. Enable `autoRecall` and `autoCapture` in config to run this automatically, or use agent tools for explicit control.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -19,46 +19,9 @@ openclaw --version
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way is to install directly from your OpenClaw chat — no CLI or config editing needed.
|
||||
|
||||
Copy and paste this into your OpenClaw chat (Telegram, WhatsApp, default chat, or any channel where your agent lives):
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#chat-setup-recommended) below for the full walkthrough.
|
||||
|
||||
If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#manual-config) and [Open-Source (Self-hosted)](#open-source-self-hosted) below.
|
||||
|
||||
### Platform (Mem0 Cloud)
|
||||
|
||||
There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform:
|
||||
|
||||
- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling.
|
||||
- **Manual config** — edit `openclaw.json` directly.
|
||||
|
||||
#### Chat Setup (Recommended)
|
||||
|
||||
You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself.
|
||||
|
||||
1. **Send the setup command to your OpenClaw agent.** Open any OpenClaw channel and paste:
|
||||
|
||||
```
|
||||
Setup Mem0 from mem0.ai/claw-setup
|
||||
```
|
||||
|
||||
OpenClaw responds with a Mem0 setup card and asks: *"What's your email address? I'll send you a verification code to connect your Mem0 account."*
|
||||
|
||||
2. **Enter your email.** Type your email address and send it. Mem0 replies: *"Check your email for a 6-digit code and paste it here."*
|
||||
|
||||
3. **Paste the OTP.** Copy the 6-digit code from your email inbox and paste it into the chat. You'll see: *"Connected to Mem0."*
|
||||
|
||||
That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn.
|
||||
|
||||
> The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward.
|
||||
|
||||
#### Manual Config
|
||||
#### Install and Configure
|
||||
|
||||
1. **Install the plugin via the OpenClaw CLI:**
|
||||
|
||||
@@ -99,9 +62,78 @@ That's it. No API key, no config file editing, no environment variables. The plu
|
||||
|
||||
### Open-Source (Self-hosted)
|
||||
|
||||
No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings and LLM. Vectors are stored locally in SQLite at `~/.mem0/vector_store.db` — no external database required.
|
||||
No Mem0 key needed. Vectors are stored locally in SQLite at `~/.mem0/vector_store.db` — no external database required.
|
||||
|
||||
Defaults: `text-embedding-3-small` for embeddings, `gpt-5.4` for fact extraction.
|
||||
Defaults: `text-embedding-3-small` (OpenAI) for embeddings, `gpt-5-mini` (OpenAI) for fact extraction — requires `OPENAI_API_KEY`. For a fully local setup, use Ollama for both LLM and embeddings.
|
||||
|
||||
#### Interactive Setup (Recommended)
|
||||
|
||||
Run the guided 4-step wizard:
|
||||
|
||||
```bash
|
||||
openclaw mem0 init --mode open-source
|
||||
```
|
||||
|
||||
The wizard walks you through:
|
||||
1. **LLM provider** — OpenAI (`gpt-5-mini`), Ollama (`llama3.1:8b`, local), or Anthropic (`claude-sonnet-4-5-20250514`)
|
||||
2. **Embedding provider** — OpenAI (`text-embedding-3-small`) or Ollama (`nomic-embed-text`, local)
|
||||
3. **Vector store** — Qdrant (`http://localhost:6333`) or PGVector (PostgreSQL)
|
||||
4. **User ID** — your memory namespace identifier
|
||||
|
||||
Each step tests connectivity (Ollama, Qdrant, PGVector) before proceeding.
|
||||
|
||||
#### Non-Interactive Setup
|
||||
|
||||
For CI/CD, scripts, or agent-driven setup — pass all options as flags:
|
||||
|
||||
```bash
|
||||
# Fully local with Ollama + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm ollama --oss-embedder ollama --oss-vector qdrant
|
||||
|
||||
# OpenAI + Qdrant
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm openai --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector qdrant
|
||||
|
||||
# Anthropic LLM + OpenAI embeddings + PGVector
|
||||
openclaw mem0 init --mode open-source \
|
||||
--oss-llm anthropic --oss-llm-key <key> \
|
||||
--oss-embedder openai --oss-embedder-key <key> \
|
||||
--oss-vector pgvector --oss-vector-user postgres --oss-vector-password secret
|
||||
|
||||
# JSON output (for LLM agents)
|
||||
openclaw mem0 init --mode open-source --oss-llm ollama --oss-embedder ollama --oss-vector qdrant --json
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>All <code>--oss-*</code> flags</summary>
|
||||
|
||||
| Flag | Description |
|
||||
| ---- | ----------- |
|
||||
| `--oss-llm <provider>` | `openai`, `ollama`, or `anthropic` |
|
||||
| `--oss-llm-key <key>` | API key for LLM provider |
|
||||
| `--oss-llm-model <model>` | Override default LLM model |
|
||||
| `--oss-llm-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-embedder <provider>` | `openai` or `ollama` |
|
||||
| `--oss-embedder-key <key>` | API key for embedder |
|
||||
| `--oss-embedder-model <model>` | Override default embedder model |
|
||||
| `--oss-embedder-url <url>` | Base URL (Ollama only) |
|
||||
| `--oss-vector <provider>` | `qdrant` or `pgvector` |
|
||||
| `--oss-vector-url <url>` | Qdrant server URL (default: `http://localhost:6333`) |
|
||||
| `--oss-vector-host <host>` | PGVector host |
|
||||
| `--oss-vector-port <port>` | PGVector port |
|
||||
| `--oss-vector-user <user>` | PGVector user |
|
||||
| `--oss-vector-password <pw>` | PGVector password |
|
||||
| `--oss-vector-dbname <db>` | PGVector database name |
|
||||
| `--oss-vector-dims <n>` | Override embedding dimensions |
|
||||
|
||||
</details>
|
||||
|
||||
#### Manual Config
|
||||
|
||||
Minimal config — uses OpenAI defaults:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -130,8 +162,8 @@ Customize the embedder, vector store, or LLM via the `oss` block:
|
||||
"userId": "alice",
|
||||
"oss": {
|
||||
"embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
|
||||
"vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-5.4" } }
|
||||
"vectorStore": { "provider": "qdrant", "config": { "url": "http://localhost:6333" } },
|
||||
"llm": { "provider": "openai", "config": { "model": "gpt-5-mini" } }
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -144,11 +176,11 @@ All `oss` fields are optional. See the [Mem0 OSS docs](https://docs.mem0.ai/open
|
||||
<img src="https://raw.githubusercontent.com/mem0ai/mem0/main/docs/images/openclaw-architecture.png" alt="Architecture" width="800" />
|
||||
</p>
|
||||
|
||||
**Auto-Recall** — Before the agent responds, the plugin searches Mem0 for relevant memories and injects them into context.
|
||||
**Auto-Recall** (`autoRecall: true`) — Before the agent responds, the plugin searches Mem0 for relevant memories and injects them into context.
|
||||
|
||||
**Auto-Capture** — After the agent responds, the conversation is filtered through a noise-removal pipeline and sent to Mem0. New facts get stored, stale ones updated, duplicates merged.
|
||||
**Auto-Capture** (`autoCapture: true`) — After the agent responds, the conversation is filtered through a noise-removal pipeline and sent to Mem0. New facts get stored, stale ones updated, duplicates merged.
|
||||
|
||||
Both run silently. No prompting, no manual calls required.
|
||||
Both are opt-in. Once enabled, they run silently — no prompting, no manual calls required. Without them, the agent can still use memory tools (`memory_add`, `memory_search`, etc.) explicitly.
|
||||
|
||||
### Memory Scopes
|
||||
|
||||
@@ -176,7 +208,7 @@ Eight tools are registered for agent use:
|
||||
|
||||
## CLI
|
||||
|
||||
All commands: `openclaw mem0 <command>`.
|
||||
All commands: `openclaw mem0 <command>`. All commands support `--json` for machine-readable output (for LLM agents).
|
||||
|
||||
```bash
|
||||
# Memory operations
|
||||
@@ -191,8 +223,9 @@ openclaw mem0 delete --all --user-id alice --confirm
|
||||
openclaw mem0 import memories.json
|
||||
|
||||
# Management
|
||||
openclaw mem0 init
|
||||
openclaw mem0 init --api-key <key> --user-id alice
|
||||
openclaw mem0 init # interactive setup
|
||||
openclaw mem0 init --mode open-source --oss-llm ollama # non-interactive OSS
|
||||
openclaw mem0 init --api-key <key> --user-id alice # non-interactive platform
|
||||
openclaw mem0 status
|
||||
openclaw mem0 config show
|
||||
openclaw mem0 config get api_key
|
||||
@@ -205,6 +238,12 @@ openclaw mem0 event status <event_id>
|
||||
# Memory consolidation
|
||||
openclaw mem0 dream
|
||||
openclaw mem0 dream --dry-run
|
||||
|
||||
# JSON output (any command)
|
||||
openclaw mem0 search "preferences" --json
|
||||
openclaw mem0 list --json
|
||||
openclaw mem0 status --json
|
||||
openclaw mem0 help --json # discover all commands + flags
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
@@ -215,8 +254,8 @@ openclaw mem0 dream --dry-run
|
||||
| --- | ---- | ------- | ----------- |
|
||||
| `mode` | `"platform"` \| `"open-source"` | `"platform"` | Backend mode |
|
||||
| `userId` | `string` | OS username | User identifier. All memories scoped to this value. |
|
||||
| `autoRecall` | `boolean` | `true` | Inject relevant memories before each turn |
|
||||
| `autoCapture` | `boolean` | `true` | Extract and store facts after each turn |
|
||||
| `autoRecall` | `boolean` | `false` | Inject relevant memories before each turn |
|
||||
| `autoCapture` | `boolean` | `false` | Extract and store facts after each turn |
|
||||
| `topK` | `number` | `5` | Max memories returned per recall |
|
||||
| `searchThreshold` | `number` | `0.5` | Minimum similarity score (0-1) |
|
||||
|
||||
@@ -230,7 +269,7 @@ openclaw mem0 dream --dry-run
|
||||
|
||||
### Open-Source Mode
|
||||
|
||||
All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite vector store (`~/.mem0/vector_store.db`), `gpt-5.4` LLM.
|
||||
All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite vector store (`~/.mem0/vector_store.db`), `gpt-5-mini` LLM.
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | ---- | ------- | ----------- |
|
||||
@@ -243,6 +282,41 @@ All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite
|
||||
| `oss.llm.config` | `object` | — | Provider config (`apiKey`, `model`, `baseURL`) |
|
||||
| `oss.historyDbPath` | `string` | — | SQLite path for edit history |
|
||||
|
||||
## Privacy & Security
|
||||
|
||||
### Data Flow
|
||||
|
||||
| Mode | Where data goes | Credentials needed |
|
||||
|------|----------------|-------------------|
|
||||
| **Platform** | Conversations sent to `api.mem0.ai` for memory extraction and retrieval | `MEM0_API_KEY` |
|
||||
| **Open-Source (OpenAI)** | LLM/embedding calls to OpenAI API; vectors stored locally at `~/.mem0/vector_store.db` | `OPENAI_API_KEY` |
|
||||
| **Open-Source (Ollama)** | Fully local — LLM, embeddings, and vectors all on your machine | None |
|
||||
|
||||
### Credential Storage
|
||||
|
||||
The plugin stores configuration in `~/.openclaw/openclaw.json`. If you use the chat setup flow or `openclaw mem0 init`, your API key and user ID are written to this file.
|
||||
|
||||
To avoid plaintext credentials:
|
||||
- Use env var references: `"apiKey": "${MEM0_API_KEY}"`
|
||||
- Use SecretRef: `"apiKey": {"source": "env", "provider": "default", "id": "MEM0_API_KEY"}`
|
||||
|
||||
### Auto-Capture & Auto-Recall
|
||||
|
||||
Both are **disabled by default** (`false`). When enabled:
|
||||
- `autoCapture`: sends conversation content to your configured backend (cloud or local) after each agent turn
|
||||
- `autoRecall`: queries your memory store before each agent turn and injects results into agent context
|
||||
|
||||
Do not enable `autoCapture` in platform mode if your conversations contain sensitive data you do not want stored on Mem0 cloud.
|
||||
|
||||
### Persistence Locations
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `~/.openclaw/openclaw.json` | Plugin configuration (API keys, user ID, settings) |
|
||||
| `~/.mem0/vector_store.db` | Local vector store (open-source mode only) |
|
||||
| `~/.mem0/history.db` | Memory edit history (open-source mode only) |
|
||||
| `<pluginStateDir>/dream-state.json` | Memory consolidation state |
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0](LICENSE)
|
||||
|
||||