mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-09 22:31:10 +00:00
Merge branch 'main' of https://github.com/dhravya/supermemory
This commit is contained in:
commit
e0461696fc
28 changed files with 1056 additions and 3576 deletions
1
apps/cf-ai-backend/src/env.d.ts
vendored
1
apps/cf-ai-backend/src/env.d.ts
vendored
|
|
@ -6,6 +6,7 @@ interface Env {
|
|||
GOOGLE_AI_API_KEY: string;
|
||||
MY_QUEUE: Queue<TweetData[]>;
|
||||
KV: KVNamespace;
|
||||
MYBROWSER: BrowserWorker;
|
||||
}
|
||||
|
||||
interface TweetData {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import * as apiQuery from './routes/query';
|
|||
import * as apiAsk from './routes/ask';
|
||||
import * as apiChat from './routes/chat';
|
||||
import * as apiBatchUploadTweets from './routes/batchUploadTweets';
|
||||
import * as apiGetPageContent from './routes/getPageContent';
|
||||
import { OpenAIEmbeddings } from './OpenAIEmbedder';
|
||||
import { GenerativeModel } from '@google/generative-ai';
|
||||
import { Request } from '@cloudflare/workers-types';
|
||||
|
|
@ -28,6 +29,7 @@ routeMap.set('/ask', apiAsk);
|
|||
routeMap.set('/chat', apiChat);
|
||||
|
||||
routeMap.set('/batchUploadTweets', apiBatchUploadTweets);
|
||||
routeMap.set('/getPageContent', apiGetPageContent);
|
||||
|
||||
// Add more route mappings as needed
|
||||
// routeMap.set('/api/otherRoute', { ... });
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { Content, GenerativeModel } from '@google/generative-ai';
|
|||
import { OpenAIEmbeddings } from '../OpenAIEmbedder';
|
||||
import { CloudflareVectorizeStore } from '@langchain/cloudflare';
|
||||
import { Request } from '@cloudflare/workers-types';
|
||||
import { AiTextGenerationOutput } from '@cloudflare/ai/dist/ai/tasks/text-generation';
|
||||
import { Ai } from '@cloudflare/ai';
|
||||
|
||||
export async function POST(request: Request, _: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env?: Env) {
|
||||
const queryparams = new URL(request.url).searchParams;
|
||||
|
|
@ -112,28 +114,40 @@ export async function POST(request: Request, _: CloudflareVectorizeStore, embedd
|
|||
},
|
||||
] as Content[];
|
||||
|
||||
const chat = model.startChat({
|
||||
history: [...defaultHistory, ...(body.chatHistory ?? [])],
|
||||
});
|
||||
// const chat = model.startChat({
|
||||
// history: [...defaultHistory, ...(body.chatHistory ?? [])],
|
||||
// });
|
||||
|
||||
const prompt = `Context:\n${preparedContext ?? ''}\n\nQuestion: ${query}\nAnswer:`;
|
||||
|
||||
const output = await chat.sendMessageStream(prompt);
|
||||
// const output = await chat.sendMessageStream(prompt);
|
||||
|
||||
const response = new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
const converter = new TextEncoder();
|
||||
for await (const chunk of output.stream) {
|
||||
const chunkText = await chunk.text();
|
||||
const encodedChunk = converter.encode('data: ' + JSON.stringify({ response: chunkText }) + '\n\n');
|
||||
controller.enqueue(encodedChunk);
|
||||
}
|
||||
const doneChunk = converter.encode('data: [DONE]');
|
||||
controller.enqueue(doneChunk);
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
);
|
||||
return response;
|
||||
// const response = new Response(
|
||||
// new ReadableStream({
|
||||
// async start(controller) {
|
||||
// const converter = new TextEncoder();
|
||||
// for await (const chunk of output.stream) {
|
||||
// const chunkText = await chunk.text();
|
||||
// const encodedChunk = converter.encode('data: ' + JSON.stringify({ response: chunkText }) + '\n\n');
|
||||
// controller.enqueue(encodedChunk);
|
||||
// }
|
||||
// const doneChunk = converter.encode('data: [DONE]');
|
||||
// controller.enqueue(doneChunk);
|
||||
// controller.close();
|
||||
// },
|
||||
// }),
|
||||
// );
|
||||
// return response;
|
||||
const ai = new Ai(env?.AI);
|
||||
// @ts-ignore
|
||||
const output: AiTextGenerationOutput = (await ai.run('@hf/mistralai/mistral-7b-instruct-v0.2', {
|
||||
prompt,
|
||||
stream: true,
|
||||
})) as ReadableStream;
|
||||
|
||||
return new Response(output, {
|
||||
headers: {
|
||||
'content-type': 'text/event-stream',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
31
apps/cf-ai-backend/src/routes/getPageContent.ts
Normal file
31
apps/cf-ai-backend/src/routes/getPageContent.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { GenerativeModel } from '@google/generative-ai';
|
||||
import { OpenAIEmbeddings } from '../OpenAIEmbedder';
|
||||
import { CloudflareVectorizeStore } from '@langchain/cloudflare';
|
||||
import { Request } from '@cloudflare/workers-types';
|
||||
import puppeteer from '@cloudflare/puppeteer';
|
||||
|
||||
export async function GET(request: Request, _: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env?: Env) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
let url = searchParams.get('url');
|
||||
let img: Buffer;
|
||||
if (url) {
|
||||
url = new URL(url).toString(); // normalize
|
||||
const browser = await puppeteer.launch(env?.MYBROWSER);
|
||||
const page = await browser.newPage();
|
||||
await page.goto(url);
|
||||
|
||||
// Innertext of content
|
||||
const contentElement = await page.$('body');
|
||||
const content = await page.evaluate((element) => element.innerText, contentElement);
|
||||
|
||||
await browser.close();
|
||||
|
||||
return new Response(content, {
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return new Response('Please add an ?url=https://example.com/ parameter');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
name = "cf-ai-backend"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-02-23"
|
||||
compatibility_flags = ['nodejs_compat']
|
||||
|
||||
[[vectorize]]
|
||||
binding = "VECTORIZE_INDEX"
|
||||
|
|
@ -19,6 +20,13 @@ binding = "AI"
|
|||
[[kv_namespaces]]
|
||||
binding = "KV"
|
||||
id = "37a90353da63401e84e20e71165531d0"
|
||||
preview_id = "c58b6202814f4224acea97627d0c18aa"
|
||||
|
||||
[browser]
|
||||
binding = "MYBROWSER"
|
||||
|
||||
[placement]
|
||||
mode = "smart"
|
||||
|
||||
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
|
||||
# Note: Use secrets to store sensitive data.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "SuperMemory",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"action": {
|
||||
"default_popup": "index.html"
|
||||
},
|
||||
|
|
|
|||
2713
apps/extension/pnpm-lock.yaml
generated
2713
apps/extension/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -47,13 +47,12 @@ function SideBar({ jwt }: { jwt: string }) {
|
|||
// }
|
||||
// });
|
||||
|
||||
|
||||
const [savedWebsites, setSavedWebsites] = useState<string[]>([]);
|
||||
|
||||
const [isSendingData, setIsSendingData] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [spaces, setSpaces] = useState<Space[]>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [spaces, setSpaces] = useState<Space[]>();
|
||||
const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
|
||||
|
||||
const [isImportingTweets, setIsImportingTweets] = useState(false);
|
||||
|
|
@ -77,14 +76,14 @@ function SideBar({ jwt }: { jwt: string }) {
|
|||
});
|
||||
}
|
||||
|
||||
const fetchSpaces = async () => {
|
||||
setLoading(true)
|
||||
const fetchSpaces = async () => {
|
||||
setLoading(true);
|
||||
chrome.runtime.sendMessage({ type: "fetchSpaces" }, (resp) => {
|
||||
console.log('response',resp)
|
||||
setSpaces(resp)
|
||||
setLoading(false)
|
||||
});
|
||||
}
|
||||
console.log("response", resp);
|
||||
setSpaces(resp);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchBookmarks = () => {
|
||||
const tweets: TweetData[] = []; // Initialize an empty array to hold all tweet elements
|
||||
|
|
@ -266,7 +265,7 @@ function SideBar({ jwt }: { jwt: string }) {
|
|||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Dialog onOpenChange={open => open === true && fetchSpaces()}>
|
||||
<Dialog onOpenChange={(open) => open === true && fetchSpaces()}>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger
|
||||
className="anycontext-bg-transparent
|
||||
|
|
@ -326,7 +325,7 @@ function SideBar({ jwt }: { jwt: string }) {
|
|||
</DialogHeader>
|
||||
|
||||
<FilterSpaces
|
||||
loading={loading}
|
||||
loading={loading}
|
||||
className="anycontext-mr-auto"
|
||||
selectedSpaces={selectedSpaces}
|
||||
setSelectedSpaces={setSelectedSpaces}
|
||||
|
|
@ -335,18 +334,20 @@ function SideBar({ jwt }: { jwt: string }) {
|
|||
/>
|
||||
<DialogFooter className="anycontext-w-full anycontext-text-sm">
|
||||
<DialogClose
|
||||
onClick={() => {
|
||||
sendUrlToAPI(selectedSpaces);
|
||||
setIsSendingData(true);
|
||||
setTimeout(() => {
|
||||
setIsSendingData(false);
|
||||
setSavedWebsites([
|
||||
...savedWebsites,
|
||||
window.location.href,
|
||||
]);
|
||||
}, 1000);
|
||||
}}
|
||||
>Add</DialogClose>
|
||||
onClick={() => {
|
||||
sendUrlToAPI(selectedSpaces);
|
||||
setIsSendingData(true);
|
||||
setTimeout(() => {
|
||||
setIsSendingData(false);
|
||||
setSavedWebsites([
|
||||
...savedWebsites,
|
||||
window.location.href,
|
||||
]);
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</DialogClose>
|
||||
<DialogClose>Cancel</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getEnv } from "./util";
|
||||
import { Space } from "./types/memory"
|
||||
import { Space } from "./types/memory";
|
||||
|
||||
const backendUrl =
|
||||
getEnv() === "development"
|
||||
|
|
@ -47,53 +47,49 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|||
|
||||
return true;
|
||||
} else if (request.type === "urlChange") {
|
||||
|
||||
const content = request.content;
|
||||
const url = request.url;
|
||||
const spaces = request.spaces
|
||||
|
||||
(async () => {
|
||||
chrome.storage.local.get(["jwt"], ({ jwt }) => {
|
||||
if (!jwt) {
|
||||
console.error("No JWT found");
|
||||
return;
|
||||
}
|
||||
fetch(`${backendUrl}/api/store`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
body: JSON.stringify({ pageContent: content, url, spaces }),
|
||||
}).then((ers) => console.log(ers.status));
|
||||
});
|
||||
})();
|
||||
const spaces = request.spaces(
|
||||
// eslint-disable-next-line no-unexpected-multiline
|
||||
async () => {
|
||||
chrome.storage.local.get(["jwt"], ({ jwt }) => {
|
||||
if (!jwt) {
|
||||
console.error("No JWT found");
|
||||
return;
|
||||
}
|
||||
fetch(`${backendUrl}/api/store`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
body: JSON.stringify({ pageContent: content, url, spaces }),
|
||||
}).then((ers) => console.log(ers.status));
|
||||
});
|
||||
},
|
||||
)();
|
||||
} else if (request.type === "fetchSpaces") {
|
||||
chrome.storage.local.get(["jwt"], async ({ jwt }) => {
|
||||
if (!jwt) {
|
||||
console.error("No JWT found");
|
||||
return;
|
||||
}
|
||||
const resp = await fetch(`${backendUrl}/api/spaces`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
|
||||
chrome.storage.local.get(["jwt"], async ({ jwt }) => {
|
||||
if (!jwt) {
|
||||
console.error("No JWT found");
|
||||
return;
|
||||
}
|
||||
const resp = await fetch(`${backendUrl}/api/spaces`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
})
|
||||
|
||||
const data: {
|
||||
message: "OK" | string;
|
||||
data: Space[] | undefined;
|
||||
} = await resp.json();
|
||||
|
||||
if (data.message === "OK" && data.data) {
|
||||
sendResponse(data.data)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return true;
|
||||
const data: {
|
||||
message: "OK" | string;
|
||||
data: Space[] | undefined;
|
||||
} = await resp.json();
|
||||
|
||||
if (data.message === "OK" && data.data) {
|
||||
sendResponse(data.data);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
} else if (request.type === "queryApi") {
|
||||
const input = request.input;
|
||||
const jwt = request.jwt;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import * as React from "react";
|
||||
import { PlusCircleIcon, X } from "lucide-react";
|
||||
import { Space } from "../types/memory";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "./ui/dropdown-menu";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
} from "./ui/dropdown-menu";
|
||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
|
||||
|
||||
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
|
|
@ -11,57 +15,76 @@ export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|||
) => void;
|
||||
name: string;
|
||||
spaces: Space[];
|
||||
loading: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export function FilterSpaces({
|
||||
loading,
|
||||
loading,
|
||||
selectedSpaces,
|
||||
setSelectedSpaces,
|
||||
spaces,
|
||||
}: Props) {
|
||||
|
||||
console.log(selectedSpaces, spaces);
|
||||
|
||||
const filteredSpaces = spaces.filter(space => selectedSpaces.includes(space.id))
|
||||
const leftSpaces = spaces.filter(space => !selectedSpaces.includes(space.id))
|
||||
const filteredSpaces = spaces.filter((space) =>
|
||||
selectedSpaces.includes(space.id),
|
||||
);
|
||||
const leftSpaces = spaces.filter(
|
||||
(space) => !selectedSpaces.includes(space.id),
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return "Loading..."
|
||||
}
|
||||
if (loading) {
|
||||
return "Loading...";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="anycontext-flex anycontext-flex-wrap anycontext-gap-1 anycontext-text-sm anycontext-">
|
||||
{filteredSpaces.length < 1 && "Add to a space"}
|
||||
{filteredSpaces.map((space) => (
|
||||
<SpaceItem {...space} key={space.id} onRemove={() => setSelectedSpaces(prev => prev.filter(s => s !== space.id))} />
|
||||
{filteredSpaces.length < 1 && "Add to a space"}
|
||||
{filteredSpaces.map((space) => (
|
||||
<SpaceItem
|
||||
{...space}
|
||||
key={space.id}
|
||||
onRemove={() =>
|
||||
setSelectedSpaces((prev) => prev.filter((s) => s !== space.id))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{leftSpaces.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="anycontext-rounded-full">
|
||||
<PlusCircleIcon className="anycontext-w-5 anycontext-h-5 [--anycontext-icon-stroke:white] dark:[--anycontext-icon-stroke:black]" stroke='var(--anycontext-icon-stroke)' fill='currentColor' />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{leftSpaces.map(space => (
|
||||
<>
|
||||
{loading && "Loading..."}
|
||||
<DropdownMenuItem onClick={() => setSelectedSpaces(prev => [...prev, space.id])}>
|
||||
{space.name}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
{leftSpaces.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="anycontext-rounded-full">
|
||||
<PlusCircleIcon
|
||||
className="anycontext-w-5 anycontext-h-5 [--anycontext-icon-stroke:white] dark:[--anycontext-icon-stroke:black]"
|
||||
stroke="var(--anycontext-icon-stroke)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{leftSpaces.map((space) => (
|
||||
<>
|
||||
{loading && "Loading..."}
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
setSelectedSpaces((prev) => [...prev, space.id])
|
||||
}
|
||||
>
|
||||
{space.name}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function SpaceItem({ name, onRemove }: { onRemove: () => void } & Space) {
|
||||
function SpaceItem({ name, onRemove }: Space & { onRemove: () => void }) {
|
||||
return (
|
||||
<div className="anycontext-flex anycontext-justify-center anycontext-items-center anycontext-gap-2 anycontext-p-1 anycontext-pl-2 anycontext-pr-3 anycontext-rounded-full anycontext-bg-black/5 dark:anycontext-bg-white/5 anycontext-border-white/20 dark:anycontext-border-black/20 border">
|
||||
<button onClick={onRemove} className="anycontext-flex hover:anycontext-bg-transparent anycontext-justify-center anycontext-scale-110 anycontext-items-center focus-visible:anycontext-outline-none anycontext-rounded-full anycontext-w-3 anycontext-bg-black/5 dark:anycontext-bg-white/5 anycontext-h-3 anycontext-text-transparent hover:anycontext-text-black dark:hover:anycontext-text-white">
|
||||
<button
|
||||
onClick={onRemove}
|
||||
className="anycontext-flex hover:anycontext-bg-transparent anycontext-justify-center anycontext-scale-110 anycontext-items-center focus-visible:anycontext-outline-none anycontext-rounded-full anycontext-w-3 anycontext-bg-black/5 dark:anycontext-bg-white/5 anycontext-h-3 anycontext-text-transparent hover:anycontext-text-black dark:hover:anycontext-text-white"
|
||||
>
|
||||
<X className="anycontext-w-3 anycontext-h-3" />
|
||||
</button>
|
||||
{name}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
|
||||
import { cn } from "../../lib/utils"
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
|
|
@ -27,16 +27,16 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||
className={cn(
|
||||
"anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-px-2 anycontext-py-1.5 anycontext-text-sm anycontext-outline-none focus:anycontext-bg-stone-100 data-[state=open]:anycontext-bg-stone-100 dark:focus:anycontext-bg-stone-800 dark:data-[state=open]:anycontext-bg-stone-800",
|
||||
inset && "anycontext-pl-8",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="anycontext-ml-auto anycontext-h-4 anycontext-w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
));
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
|
|
@ -46,13 +46,13 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"anycontext-z-50 anycontext-min-w-[8rem] anycontext-overflow-hidden anycontext-rounded-md anycontext-border anycontext-border-stone-200 anycontext-bg-white anycontext-p-1 anycontext-text-stone-950 anycontext-shadow-lg data-[state=open]:anycontext-animate-in data-[state=closed]:anycontext-animate-out data-[state=closed]:anycontext-fade-out-0 data-[state=open]:anycontext-fade-in-0 data-[state=closed]:anycontext-zoom-out-95 data-[state=open]:anycontext-zoom-in-95 data-[side=bottom]:anycontext-slide-in-from-top-2 data-[side=left]:anycontext-slide-in-from-right-2 data-[side=right]:anycontext-slide-in-from-left-2 data-[side=top]:anycontext-slide-in-from-bottom-2 dark:anycontext-border-stone-800 dark:anycontext-bg-stone-950 dark:anycontext-text-stone-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
));
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
DropdownMenuPrimitive.SubContent.displayName;
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
|
|
@ -64,18 +64,18 @@ const DropdownMenuContent = React.forwardRef<
|
|||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"anycontext-z-50 anycontext-min-w-[8rem] anycontext-overflow-hidden anycontext-rounded-md anycontext-border anycontext-border-stone-200 anycontext-bg-white anycontext-p-1 anycontext-text-stone-950 anycontext-shadow-md data-[state=open]:anycontext-animate-in data-[state=closed]:anycontext-animate-out data-[state=closed]:anycontext-fade-out-0 data-[state=open]:anycontext-fade-in-0 data-[state=closed]:anycontext-zoom-out-95 data-[state=open]:anycontext-zoom-in-95 data-[side=bottom]:anycontext-slide-in-from-top-2 data-[side=left]:anycontext-slide-in-from-right-2 data-[side=right]:anycontext-slide-in-from-left-2 data-[side=top]:anycontext-slide-in-from-bottom-2 dark:anycontext-border-stone-800 dark:anycontext-bg-stone-950 dark:anycontext-text-stone-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
));
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
|
|
@ -83,12 +83,12 @@ const DropdownMenuItem = React.forwardRef<
|
|||
className={cn(
|
||||
"anycontext-relative anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-px-2 anycontext-py-1.5 anycontext-text-sm anycontext-outline-none anycontext-transition-colors focus:anycontext-bg-stone-100 focus:anycontext-text-stone-900 data-[disabled]:anycontext-pointer-events-none data-[disabled]:anycontext-opacity-50 dark:focus:anycontext-bg-stone-800 dark:focus:anycontext-text-stone-50",
|
||||
inset && "anycontext-pl-8",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
));
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
|
|
@ -98,7 +98,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"anycontext-relative anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-py-1.5 anycontext-pl-8 anycontext-pr-2 anycontext-text-sm anycontext-outline-none anycontext-transition-colors focus:anycontext-bg-stone-100 focus:anycontext-text-stone-900 data-[disabled]:anycontext-pointer-events-none data-[disabled]:anycontext-opacity-50 dark:focus:anycontext-bg-stone-800 dark:focus:anycontext-text-stone-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
|
|
@ -110,9 +110,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
));
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
|
|
@ -122,7 +122,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"anycontext-relative anycontext-flex anycontext-cursor-default anycontext-select-none anycontext-items-center anycontext-rounded-sm anycontext-py-1.5 anycontext-pl-8 anycontext-pr-2 anycontext-text-sm anycontext-outline-none anycontext-transition-colors focus:anycontext-bg-stone-100 focus:anycontext-text-stone-900 data-[disabled]:anycontext-pointer-events-none data-[disabled]:anycontext-opacity-50 dark:focus:anycontext-bg-stone-800 dark:focus:anycontext-text-stone-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
|
@ -133,13 +133,13 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
));
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
|
|
@ -147,12 +147,12 @@ const DropdownMenuLabel = React.forwardRef<
|
|||
className={cn(
|
||||
"anycontext-px-2 anycontext-py-1.5 anycontext-text-sm anycontext-font-semibold",
|
||||
inset && "anycontext-pl-8",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
));
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
|
|
@ -160,11 +160,14 @@ const DropdownMenuSeparator = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("anycontext--mx-1 anycontext-my-1 anycontext-h-px anycontext-bg-stone-100 dark:anycontext-bg-stone-800", className)}
|
||||
className={cn(
|
||||
"anycontext--mx-1 anycontext-my-1 anycontext-h-px anycontext-bg-stone-100 dark:anycontext-bg-stone-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
));
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
|
|
@ -172,12 +175,15 @@ const DropdownMenuShortcut = ({
|
|||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("anycontext-ml-auto anycontext-text-xs anycontext-tracking-widest anycontext-opacity-60", className)}
|
||||
className={cn(
|
||||
"anycontext-ml-auto anycontext-text-xs anycontext-tracking-widest anycontext-opacity-60",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
);
|
||||
};
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
|
|
@ -195,4 +201,4 @@ export {
|
|||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,66 +6,79 @@ import {
|
|||
sessions,
|
||||
StoredContent,
|
||||
storedContent,
|
||||
StoredSpace,
|
||||
StoredSpace,
|
||||
users,
|
||||
space,
|
||||
space,
|
||||
} from "@/server/db/schema";
|
||||
import { SearchResult } from "@/contexts/MemoryContext";
|
||||
import { like, eq, and, sql, exists, asc, notExists } from "drizzle-orm";
|
||||
import { union } from "drizzle-orm/sqlite-core"
|
||||
import { union } from "drizzle-orm/sqlite-core";
|
||||
|
||||
// @todo: (future) pagination not yet needed
|
||||
export async function searchMemoriesAndSpaces(query: string, opts?: { filter?: { memories?: boolean, spaces?: boolean }, range?: { offset: number, limit: number } }): Promise<SearchResult[]> {
|
||||
export async function searchMemoriesAndSpaces(
|
||||
query: string,
|
||||
opts?: {
|
||||
filter?: { memories?: boolean; spaces?: boolean };
|
||||
range?: { offset: number; limit: number };
|
||||
},
|
||||
): Promise<SearchResult[]> {
|
||||
const user = await getUser();
|
||||
|
||||
const user = await getUser()
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const searchMemoriesQuery = db
|
||||
.select({
|
||||
type: sql<string>`'memory'`,
|
||||
space: sql`NULL`,
|
||||
memory: storedContent as any,
|
||||
})
|
||||
.from(storedContent)
|
||||
.where(
|
||||
and(
|
||||
eq(storedContent.user, user.id),
|
||||
like(storedContent.title, `%${query}%`),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(storedContent.savedAt));
|
||||
|
||||
try {
|
||||
const searchMemoriesQuery = db.select({
|
||||
type: sql<string>`'memory'`,
|
||||
space: sql`NULL`,
|
||||
memory: storedContent as any
|
||||
}).from(storedContent).where(and(
|
||||
eq(storedContent.user, user.id),
|
||||
like(storedContent.title, `%${query}%`)
|
||||
)).orderBy(asc(storedContent.savedAt));
|
||||
const searchSpacesQuery = db
|
||||
.select({
|
||||
type: sql<string>`'space'`,
|
||||
space: space as any,
|
||||
memory: sql`NULL`,
|
||||
})
|
||||
.from(space)
|
||||
.where(and(eq(space.user, user.id), like(space.name, `%${query}%`)))
|
||||
.orderBy(asc(space.name));
|
||||
|
||||
const searchSpacesQuery = db.select({
|
||||
type: sql<string>`'space'`,
|
||||
space: space as any,
|
||||
memory: sql`NULL`,
|
||||
}).from(space).where(
|
||||
and(
|
||||
eq(space.user, user.id),
|
||||
like(space.name, `%${query}%`)
|
||||
)
|
||||
).orderBy(asc(space.name));
|
||||
|
||||
let queries = [];
|
||||
let queries = [];
|
||||
|
||||
console.log('adding');
|
||||
console.log("adding");
|
||||
|
||||
[undefined, true].includes(opts?.filter?.memories) && queries.push(searchMemoriesQuery);
|
||||
[undefined, true].includes(opts?.filter?.spaces) && queries.push(searchSpacesQuery);
|
||||
[undefined, true].includes(opts?.filter?.memories) &&
|
||||
queries.push(searchMemoriesQuery);
|
||||
[undefined, true].includes(opts?.filter?.spaces) &&
|
||||
queries.push(searchSpacesQuery);
|
||||
|
||||
if (opts?.range) {
|
||||
queries = queries.map(q => q.offset(opts.range!.offset).limit(opts.range!.limit))
|
||||
} else {
|
||||
queries = queries.map(q => q.all())
|
||||
}
|
||||
if (opts?.range) {
|
||||
queries = queries.map((q) =>
|
||||
q.offset(opts.range!.offset).limit(opts.range!.limit),
|
||||
);
|
||||
} else {
|
||||
queries = queries.map((q) => q.all());
|
||||
}
|
||||
|
||||
const data = await Promise.all(queries)
|
||||
const data = await Promise.all(queries);
|
||||
|
||||
console.log('resp', data)
|
||||
|
||||
return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
console.log("resp", data);
|
||||
|
||||
return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getUser() {
|
||||
|
|
@ -76,7 +89,7 @@ async function getUser() {
|
|||
headers().get("Authorization")?.replace("Bearer ", "");
|
||||
|
||||
if (!token) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = await db
|
||||
|
|
@ -85,7 +98,7 @@ async function getUser() {
|
|||
.where(eq(sessions.sessionToken, token!));
|
||||
|
||||
if (!session || session.length === 0) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const [userData] = await db
|
||||
|
|
@ -95,17 +108,17 @@ async function getUser() {
|
|||
.limit(1);
|
||||
|
||||
if (!userData) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return userData
|
||||
return userData;
|
||||
}
|
||||
|
||||
export async function getMemory(title: string) {
|
||||
const user = await getUser();
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return await db
|
||||
|
|
@ -119,35 +132,40 @@ export async function getMemory(title: string) {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
export async function addSpace(name: string, memories: number[]) {
|
||||
const user = await getUser();
|
||||
|
||||
const user = await getUser();
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
const [addedSpace] = await db
|
||||
.insert(space)
|
||||
.values({
|
||||
name: name,
|
||||
user: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const [addedSpace] = await db
|
||||
.insert(space)
|
||||
.values({
|
||||
name: name,
|
||||
user: user.id
|
||||
}).returning();
|
||||
|
||||
const addedMemories = memories.length > 0 ? await db.insert(contentToSpace)
|
||||
.values(memories.map(m => ({
|
||||
contentId: m,
|
||||
spaceId: addedSpace.id
|
||||
}))).returning() : []
|
||||
|
||||
return {
|
||||
space: addedSpace,
|
||||
addedMemories
|
||||
}
|
||||
const addedMemories =
|
||||
memories.length > 0
|
||||
? await db
|
||||
.insert(contentToSpace)
|
||||
.values(
|
||||
memories.map((m) => ({
|
||||
contentId: m,
|
||||
spaceId: addedSpace.id,
|
||||
})),
|
||||
)
|
||||
.returning()
|
||||
: [];
|
||||
|
||||
return {
|
||||
space: addedSpace,
|
||||
addedMemories,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export async function fetchContentForSpace(
|
||||
spaceId: number,
|
||||
range?: {
|
||||
|
|
@ -155,115 +173,127 @@ export async function fetchContentForSpace(
|
|||
limit: number;
|
||||
},
|
||||
) {
|
||||
|
||||
const query = db
|
||||
.select()
|
||||
.from(storedContent)
|
||||
.where(
|
||||
exists(
|
||||
db.select().from(contentToSpace).where(and(eq(contentToSpace.spaceId, spaceId), eq(contentToSpace.contentId, storedContent.id))),
|
||||
db
|
||||
.select()
|
||||
.from(contentToSpace)
|
||||
.where(
|
||||
and(
|
||||
eq(contentToSpace.spaceId, spaceId),
|
||||
eq(contentToSpace.contentId, storedContent.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
).orderBy(asc(storedContent.savedAt))
|
||||
)
|
||||
.orderBy(asc(storedContent.savedAt));
|
||||
|
||||
return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
|
||||
return range
|
||||
? await query.limit(range.limit).offset(range.offset)
|
||||
: await query.all();
|
||||
}
|
||||
|
||||
export async function fetchFreeMemories(
|
||||
range?: {
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
) {
|
||||
export async function fetchFreeMemories(range?: {
|
||||
offset: number;
|
||||
limit: number;
|
||||
}) {
|
||||
const user = await getUser();
|
||||
|
||||
const user = await getUser()
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return []
|
||||
}
|
||||
|
||||
const query = db
|
||||
const query = db
|
||||
.select()
|
||||
.from(storedContent)
|
||||
.where(
|
||||
and(
|
||||
notExists(
|
||||
db.select().from(contentToSpace).where(eq(contentToSpace.contentId, storedContent.id)),
|
||||
),
|
||||
eq(storedContent.user, user.id),
|
||||
)
|
||||
|
||||
).orderBy(asc(storedContent.savedAt))
|
||||
|
||||
return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
|
||||
and(
|
||||
notExists(
|
||||
db
|
||||
.select()
|
||||
.from(contentToSpace)
|
||||
.where(eq(contentToSpace.contentId, storedContent.id)),
|
||||
),
|
||||
eq(storedContent.user, user.id),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(storedContent.savedAt));
|
||||
|
||||
return range
|
||||
? await query.limit(range.limit).offset(range.offset)
|
||||
: await query.all();
|
||||
}
|
||||
|
||||
export async function addMemory(content: typeof storedContent.$inferInsert, spaces: number[]) {
|
||||
export async function addMemory(
|
||||
content: typeof storedContent.$inferInsert,
|
||||
spaces: number[],
|
||||
) {
|
||||
const user = await getUser();
|
||||
|
||||
const user = await getUser()
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
const [addedMemory] = await db
|
||||
.insert(storedContent)
|
||||
.values({
|
||||
user: user.id,
|
||||
...content,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const [addedMemory] = await db.insert(storedContent)
|
||||
.values({
|
||||
user: user.id,
|
||||
...content
|
||||
})
|
||||
.returning();
|
||||
|
||||
const addedToSpaces = spaces.length > 0 ? await db.insert(contentToSpace)
|
||||
.values(spaces.map(s => ({
|
||||
contentId: addedMemory.id,
|
||||
spaceId: s,
|
||||
})))
|
||||
.returning() : [];
|
||||
|
||||
return {
|
||||
memory: addedMemory,
|
||||
addedToSpaces
|
||||
}
|
||||
const addedToSpaces =
|
||||
spaces.length > 0
|
||||
? await db
|
||||
.insert(contentToSpace)
|
||||
.values(
|
||||
spaces.map((s) => ({
|
||||
contentId: addedMemory.id,
|
||||
spaceId: s,
|
||||
})),
|
||||
)
|
||||
.returning()
|
||||
: [];
|
||||
|
||||
return {
|
||||
memory: addedMemory,
|
||||
addedToSpaces,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteSpace(id: number) {
|
||||
const user = await getUser();
|
||||
|
||||
const user = await getUser()
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
await db.delete(contentToSpace).where(eq(contentToSpace.spaceId, id));
|
||||
|
||||
await db.delete(contentToSpace)
|
||||
.where(eq(contentToSpace.spaceId, id));
|
||||
|
||||
const [deleted] = await db.delete(space)
|
||||
.where(and(eq(space.user, user.id), eq(space.id, id)))
|
||||
.returning();
|
||||
|
||||
|
||||
return deleted
|
||||
const [deleted] = await db
|
||||
.delete(space)
|
||||
.where(and(eq(space.user, user.id), eq(space.id, id)))
|
||||
.returning();
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
|
||||
export async function deleteMemory(id: number) {
|
||||
const user = await getUser();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await getUser()
|
||||
await db.delete(contentToSpace).where(eq(contentToSpace.contentId, id));
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
await db.delete(contentToSpace)
|
||||
.where(eq(contentToSpace.contentId, id));
|
||||
|
||||
const [deleted] = await db.delete(storedContent)
|
||||
.where(and(eq(storedContent.user, user.id), eq(storedContent.id, id)))
|
||||
.returning();
|
||||
|
||||
return deleted
|
||||
const [deleted] = await db
|
||||
.delete(storedContent)
|
||||
.where(and(eq(storedContent.user, user.id), eq(storedContent.id, id)))
|
||||
.returning();
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,7 @@ import { sessions, space, users } from "@/server/db/schema";
|
|||
import { eq } from "drizzle-orm";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
<<<<<<< HEAD
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
=======
|
||||
export const runtime = "edge";
|
||||
>>>>>>> 7648bdaa8cbe42a90f05865f8c555c9a3911af9b
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const token =
|
||||
|
|
@ -40,7 +34,6 @@ export async function GET(req: NextRequest) {
|
|||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, token!));
|
||||
|
||||
|
||||
if (!sessionData || sessionData.length === 0) {
|
||||
return new Response(
|
||||
JSON.stringify({ message: "Invalid Key, session not found." }),
|
||||
|
|
@ -63,27 +56,11 @@ export async function GET(req: NextRequest) {
|
|||
|
||||
const user = userData[0];
|
||||
|
||||
<<<<<<< HEAD
|
||||
const spaces = await db
|
||||
.select()
|
||||
.from(space)
|
||||
.where(eq(space.user, user.id))
|
||||
.all();
|
||||
|
||||
|
||||
console.log('data', spaces)
|
||||
|
||||
return NextResponse.json({
|
||||
message: "OK",
|
||||
data: spaces
|
||||
}, { status: 200 })
|
||||
=======
|
||||
const spaces = await db
|
||||
.select()
|
||||
.from(space)
|
||||
.where(eq(space.user, user.id))
|
||||
.all();
|
||||
>>>>>>> 7648bdaa8cbe42a90f05865f8c555c9a3911af9b
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { db } from "@/server/db";
|
||||
import {
|
||||
ChachedSpaceContent,
|
||||
ChachedSpaceContent,
|
||||
contentToSpace,
|
||||
sessions,
|
||||
space,
|
||||
|
|
@ -57,37 +57,36 @@ export default async function Home() {
|
|||
.select()
|
||||
.from(space)
|
||||
.where(eq(space.user, userData.id))
|
||||
.all();
|
||||
.all();
|
||||
|
||||
console.log(collectedSpaces)
|
||||
console.log(collectedSpaces);
|
||||
|
||||
// Fetch only first 3 content of each spaces
|
||||
let contents: ChachedSpaceContent[] = [];
|
||||
|
||||
//console.log(await db.select().from(storedContent).)
|
||||
|
||||
//console.log(await db.select().from(storedContent).)
|
||||
|
||||
await Promise.all([
|
||||
collectedSpaces.forEach(async (space) => {
|
||||
console.log("fetching ")
|
||||
const data = (await fetchContentForSpace(space.id, {
|
||||
offset: 0,
|
||||
limit: 3,
|
||||
})).map(data => ({
|
||||
...data,
|
||||
space: space.id
|
||||
}))
|
||||
contents = [
|
||||
...contents,
|
||||
console.log("fetching ");
|
||||
const data = (
|
||||
await fetchContentForSpace(space.id, {
|
||||
offset: 0,
|
||||
limit: 3,
|
||||
})
|
||||
).map((data) => ({
|
||||
...data,
|
||||
];
|
||||
space: space.id,
|
||||
}));
|
||||
contents = [...contents, ...data];
|
||||
}),
|
||||
]);
|
||||
|
||||
console.log(contents)
|
||||
console.log(contents);
|
||||
|
||||
// freeMemories
|
||||
const freeMemories = await fetchFreeMemories(userData.id);
|
||||
console.log('free',freeMemories)
|
||||
console.log("free", freeMemories);
|
||||
|
||||
return (
|
||||
<MemoryProvider
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import React from "react";
|
|||
import Markdown from "react-markdown";
|
||||
import { policy } from "./privacy";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -380,7 +380,10 @@ export function Chat({
|
|||
loading={i === chatHistory.length - 1 ? isLoading : false}
|
||||
sources={msg.answer.sources}
|
||||
>
|
||||
{msg.answer.parts.map((part) => part.text).join(" ")}
|
||||
{msg.answer.parts
|
||||
.map((part) => part.text)
|
||||
.join("")
|
||||
.replace("</s>", "")}
|
||||
</ChatAnswer>
|
||||
</ChatMessage>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { useMemory } from "@/contexts/MemoryContext";
|
|||
import { Loader, Plus, X } from "lucide-react";
|
||||
import { StoredContent } from "@/server/db/schema";
|
||||
import { cleanUrl } from "@/lib/utils";
|
||||
import { motion } from "framer-motion"
|
||||
import { motion } from "framer-motion";
|
||||
import { getMetaData } from "@/server/helpers";
|
||||
|
||||
export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
|
||||
|
|
@ -39,29 +39,29 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
placeholder="Enter the URL of the page"
|
||||
type="url"
|
||||
data-modal-autofocus
|
||||
className="disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 mt-2 w-full"
|
||||
className="bg-rgray-4 mt-2 w-full disabled:cursor-not-allowed disabled:opacity-70"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
disabled={loading}
|
||||
disabled={loading}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<FilterSpaces
|
||||
selectedSpaces={selectedSpacesId}
|
||||
setSelectedSpaces={setSelectedSpacesId}
|
||||
className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-5 mr-auto bg-white/5"
|
||||
className="hover:bg-rgray-5 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
name={"Spaces"}
|
||||
disabled={loading}
|
||||
disabled={loading}
|
||||
/>
|
||||
<button
|
||||
type={"submit"}
|
||||
disabled={loading}
|
||||
disabled={loading}
|
||||
onClick={async () => {
|
||||
setLoading(true)
|
||||
const metadata = await getMetaData(url)
|
||||
setLoading(true);
|
||||
const metadata = await getMetaData(url);
|
||||
await addMemory(
|
||||
{
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
description: metadata.description,
|
||||
content: "",
|
||||
type: "page",
|
||||
url: url,
|
||||
|
|
@ -70,28 +70,28 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
},
|
||||
selectedSpacesId,
|
||||
);
|
||||
closeDialog()
|
||||
closeDialog();
|
||||
}}
|
||||
className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
|
||||
className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ x: '-50%', y: '-100%' }}
|
||||
animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
|
||||
className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
|
||||
>
|
||||
<Loader className="w-5 h-5 animate-spin text-rgray-11" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: '0%' }}
|
||||
animate={loading && { opacity: 0, y: '30%' }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ x: "-50%", y: "-100%" }}
|
||||
animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
|
||||
>
|
||||
<Loader className="text-rgray-11 h-5 w-5 animate-spin" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: "0%" }}
|
||||
animate={loading && { opacity: 0, y: "30%" }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
</button>
|
||||
<DialogClose
|
||||
disabled={loading}
|
||||
className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
|
||||
>
|
||||
disabled={loading}
|
||||
className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
|
@ -100,8 +100,7 @@ export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
}
|
||||
|
||||
export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
||||
|
||||
const { addMemory } = useMemory()
|
||||
const { addMemory } = useMemory();
|
||||
|
||||
const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
|
||||
|
||||
|
|
@ -142,7 +141,7 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
placeholder="Title of the note"
|
||||
data-modal-autofocus
|
||||
value={name}
|
||||
disabled={loading}
|
||||
disabled={loading}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<Editor
|
||||
|
|
@ -165,39 +164,41 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
<button
|
||||
onClick={() => {
|
||||
if (check()) {
|
||||
setLoading(true)
|
||||
addMemory({
|
||||
content,
|
||||
title: name,
|
||||
type: "note",
|
||||
url: "https://notes.supermemory.dhr.wtf/",
|
||||
image: '',
|
||||
savedAt: new Date()
|
||||
}, selectedSpacesId).then(closeDialog)
|
||||
setLoading(true);
|
||||
addMemory(
|
||||
{
|
||||
content,
|
||||
title: name,
|
||||
type: "note",
|
||||
url: "https://notes.supermemory.dhr.wtf/",
|
||||
image: "",
|
||||
savedAt: new Date(),
|
||||
},
|
||||
selectedSpacesId,
|
||||
).then(closeDialog);
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
|
||||
disabled={loading}
|
||||
className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
|
||||
<motion.div
|
||||
initial={{ x: '-50%', y: '-100%' }}
|
||||
animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
|
||||
className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
|
||||
>
|
||||
<Loader className="w-5 h-5 animate-spin text-rgray-11" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: '0%' }}
|
||||
animate={loading && { opacity: 0, y: '30%' }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ x: "-50%", y: "-100%" }}
|
||||
animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
|
||||
>
|
||||
<Loader className="text-rgray-11 h-5 w-5 animate-spin" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: "0%" }}
|
||||
animate={loading && { opacity: 0, y: "30%" }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
</button>
|
||||
<DialogClose
|
||||
type={undefined}
|
||||
disabled={loading}
|
||||
className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
|
||||
disabled={loading}
|
||||
className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
Cancel
|
||||
</DialogClose>
|
||||
|
|
@ -207,16 +208,14 @@ export function NoteAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
}
|
||||
|
||||
export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
|
||||
|
||||
const { addSpace } = useMemory()
|
||||
const { addSpace } = useMemory();
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [selected, setSelected] = useState<StoredContent[]>([]);
|
||||
|
||||
const [selected, setSelected] = useState<StoredContent[]>([]);
|
||||
|
||||
function check(): boolean {
|
||||
const data = {
|
||||
|
|
@ -247,65 +246,73 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
</DialogHeader>
|
||||
<Label className="mt-5 block">Name</Label>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
ref={inputRef}
|
||||
placeholder="Enter the name of the space"
|
||||
type="url"
|
||||
data-modal-autofocus
|
||||
value={name}
|
||||
disabled={loading}
|
||||
onChange={e => setName(e.target.value)}
|
||||
className="bg-rgray-4 mt-2 w-full focus-visible:data-[error=true]:ring-red-500/10 data-[error=true]:placeholder:text-red-400 placeholder:transition placeholder:duration-500"
|
||||
value={name}
|
||||
disabled={loading}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="bg-rgray-4 mt-2 w-full placeholder:transition placeholder:duration-500 data-[error=true]:placeholder:text-red-400 focus-visible:data-[error=true]:ring-red-500/10"
|
||||
/>
|
||||
{selected.length > 0 && (
|
||||
<>
|
||||
<Label className="mt-5 block">Add Memories</Label>
|
||||
<div className="flex min-h-5 py-2 flex-col justify-center items-center">
|
||||
{selected.map(i => (
|
||||
<MemorySelectedItem
|
||||
key={i.id}
|
||||
onRemove={() => setSelected(prev => prev.filter(p => p.id !== i.id))}
|
||||
{...i}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<Label className="mt-5 block">Add Memories</Label>
|
||||
<div className="flex min-h-5 flex-col items-center justify-center py-2">
|
||||
{selected.map((i) => (
|
||||
<MemorySelectedItem
|
||||
key={i.id}
|
||||
onRemove={() =>
|
||||
setSelected((prev) => prev.filter((p) => p.id !== i.id))
|
||||
}
|
||||
{...i}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<FilterMemories
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
disabled={loading}
|
||||
className="mr-auto bg-white/5 hover:bg-rgray-4 focus-visible:bg-rgray-4 disabled:opacity-70 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
Memory
|
||||
</FilterMemories>
|
||||
<FilterMemories
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
disabled={loading}
|
||||
className="hover:bg-rgray-4 focus-visible:bg-rgray-4 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
Memory
|
||||
</FilterMemories>
|
||||
<button
|
||||
type={undefined}
|
||||
onClick={() => {
|
||||
if (check()) {
|
||||
setLoading(true)
|
||||
addSpace(name, selected.map(s => s.id)).then(() => closeDialog())
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
className="relative disabled:opacity-70 disabled:cursor-not-allowed bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
|
||||
onClick={() => {
|
||||
if (check()) {
|
||||
setLoading(true);
|
||||
addSpace(
|
||||
name,
|
||||
selected.map((s) => s.id),
|
||||
).then(() => closeDialog());
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ x: '-50%', y: '-100%' }}
|
||||
animate={loading && { y: '-50%', x: '-50%', opacity: 1 }}
|
||||
className="opacity-0 absolute top-1/2 left-1/2 translate-y-[-100%] -translate-x-1/2"
|
||||
>
|
||||
<Loader className="w-5 h-5 animate-spin text-rgray-11" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: '0%' }}
|
||||
animate={loading && { opacity: 0, y: '30%' }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ x: "-50%", y: "-100%" }}
|
||||
animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
|
||||
>
|
||||
<Loader className="text-rgray-11 h-5 w-5 animate-spin" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: "0%" }}
|
||||
animate={loading && { opacity: 0, y: "30%" }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
</button>
|
||||
<DialogClose disabled={loading} className="disabled:opacity-70 disabled:cursor-not-allowed hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
|
||||
<DialogClose
|
||||
disabled={loading}
|
||||
className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
|
@ -313,15 +320,33 @@ export function SpaceAddPage({ closeDialog }: { closeDialog: () => void }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function MemorySelectedItem({ id, title, url, type, image, onRemove }: StoredContent & { onRemove: () => void; }) {
|
||||
return (
|
||||
<div className="flex justify-start gap-2 p-1 px-2 w-full items-center text-sm rounded-md hover:bg-rgray-4 focus-within-bg-rgray-4 [&:hover>[data-icon]]:block [&:hover>img]:hidden">
|
||||
<img src={type === 'note'? '/note.svg' : image ?? "/icons/logo_without_bg.png"} className="h-5 w-5" />
|
||||
<button onClick={onRemove} data-icon className="w-5 h-5 p-0 m-0 hidden focus-visible:outline-none">
|
||||
<X className="w-5 h-5 scale-90" />
|
||||
</button>
|
||||
<span>{title}</span>
|
||||
<span className="ml-auto block opacity-50">{type ==='note' ? 'Note' : cleanUrl(url)}</span>
|
||||
</div>
|
||||
)
|
||||
export function MemorySelectedItem({
|
||||
id,
|
||||
title,
|
||||
url,
|
||||
type,
|
||||
image,
|
||||
onRemove,
|
||||
}: StoredContent & { onRemove: () => void }) {
|
||||
return (
|
||||
<div className="hover:bg-rgray-4 focus-within-bg-rgray-4 flex w-full items-center justify-start gap-2 rounded-md p-1 px-2 text-sm [&:hover>[data-icon]]:block [&:hover>img]:hidden">
|
||||
<img
|
||||
src={
|
||||
type === "note" ? "/note.svg" : image ?? "/icons/logo_without_bg.png"
|
||||
}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
<button
|
||||
onClick={onRemove}
|
||||
data-icon
|
||||
className="m-0 hidden h-5 w-5 p-0 focus-visible:outline-none"
|
||||
>
|
||||
<X className="h-5 w-5 scale-90" />
|
||||
</button>
|
||||
<span>{title}</span>
|
||||
<span className="ml-auto block opacity-50">
|
||||
{type === "note" ? "Note" : cleanUrl(url)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ import { SearchResult, useMemory } from "@/contexts/MemoryContext";
|
|||
import { useDebounce } from "@/hooks/useDebounce";
|
||||
import { StoredContent } from "@/server/db/schema";
|
||||
|
||||
export interface FilterSpacesProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
export interface FilterSpacesProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
side?: "top" | "bottom";
|
||||
align?: "end" | "start" | "center";
|
||||
onClose?: () => void;
|
||||
|
|
@ -153,7 +154,7 @@ export type FilterMemoriesProps = {
|
|||
onClose?: () => void;
|
||||
selected: StoredContent[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<StoredContent[]>>;
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export function FilterMemories({
|
||||
className,
|
||||
|
|
@ -164,40 +165,39 @@ export function FilterMemories({
|
|||
setSelected,
|
||||
...props
|
||||
}: FilterMemoriesProps) {
|
||||
|
||||
const { search } = useMemory();
|
||||
const { search } = useMemory();
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const query = useDebounce(searchQuery, 500)
|
||||
const [searchQuery, setSearchQuery] = React.useState("");
|
||||
const query = useDebounce(searchQuery, 500);
|
||||
|
||||
const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]);
|
||||
const [isSearching, setIsSearching] = React.useState(false)
|
||||
const [searchResults, setSearchResults] = React.useState<SearchResult[]>([]);
|
||||
const [isSearching, setIsSearching] = React.useState(false);
|
||||
|
||||
const results = React.useMemo(() => {
|
||||
return searchResults.map(r => r.memory)
|
||||
}, [searchResults])
|
||||
const results = React.useMemo(() => {
|
||||
return searchResults.map((r) => r.memory);
|
||||
}, [searchResults]);
|
||||
|
||||
console.log('memoized', results)
|
||||
console.log("memoized", results);
|
||||
|
||||
React.useEffect(() => {
|
||||
const q = query.trim()
|
||||
if (q.length > 0) {
|
||||
setIsSearching(true);
|
||||
(async () => {
|
||||
const results = await search(q, {
|
||||
filter: {
|
||||
memories: true,
|
||||
spaces: false
|
||||
}
|
||||
})
|
||||
setSearchResults(results)
|
||||
setIsSearching(false)
|
||||
})();
|
||||
} else {
|
||||
setSearchResults([])
|
||||
}
|
||||
}, [query])
|
||||
React.useEffect(() => {
|
||||
const q = query.trim();
|
||||
if (q.length > 0) {
|
||||
setIsSearching(true);
|
||||
(async () => {
|
||||
const results = await search(q, {
|
||||
filter: {
|
||||
memories: true,
|
||||
spaces: false,
|
||||
},
|
||||
});
|
||||
setSearchResults(results);
|
||||
setIsSearching(false);
|
||||
})();
|
||||
} else {
|
||||
setSearchResults([]);
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open) {
|
||||
|
|
@ -205,7 +205,7 @@ export function FilterMemories({
|
|||
}
|
||||
}, [open]);
|
||||
|
||||
console.log(searchResults);
|
||||
console.log(searchResults);
|
||||
return (
|
||||
<AnimatePresence mode="popLayout">
|
||||
<LayoutGroup>
|
||||
|
|
@ -220,7 +220,7 @@ export function FilterMemories({
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
{props.children}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
|
@ -229,43 +229,58 @@ export function FilterMemories({
|
|||
side={side}
|
||||
className="w-[200px] p-0"
|
||||
>
|
||||
<Command
|
||||
shouldFilter={false}
|
||||
>
|
||||
<CommandInput isSearching={isSearching} value={searchQuery} onValueChange={setSearchQuery} placeholder="Filter memories..." />
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
isSearching={isSearching}
|
||||
value={searchQuery}
|
||||
onValueChange={setSearchQuery}
|
||||
placeholder="Filter memories..."
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
<CommandEmpty className="text-rgray-11 text-sm text-center py-5">{isSearching ? "Searching..." : query.trim().length > 0 ? "Nothing Found" : "Search something"}</CommandEmpty>
|
||||
{results.map((m) => (
|
||||
<CommandItem
|
||||
key={m.id}
|
||||
value={m.id.toString()}
|
||||
onSelect={(val) => {
|
||||
setSelected((prev) =>
|
||||
prev.find(p => p.id === parseInt(val))
|
||||
? prev.filter((v) => v.id !== parseInt(val))
|
||||
: [...prev, m],
|
||||
);
|
||||
}}
|
||||
asChild
|
||||
>
|
||||
<div
|
||||
className="text-rgray-11"
|
||||
>
|
||||
<img src={m.type === 'note' ? '/note.svg' : m.image ?? "/icons/logo_without_bg.png"} className="mr-2 h-4 w-4" />
|
||||
{m.title}
|
||||
<Check
|
||||
data-state-on={selected.find(i => i.id === m.id) !== undefined}
|
||||
className={cn(
|
||||
"on:opacity-100 ml-auto h-4 w-4 opacity-0",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
<CommandGroup>
|
||||
<CommandEmpty className="text-rgray-11 py-5 text-center text-sm">
|
||||
{isSearching
|
||||
? "Searching..."
|
||||
: query.trim().length > 0
|
||||
? "Nothing Found"
|
||||
: "Search something"}
|
||||
</CommandEmpty>
|
||||
{results.map((m) => (
|
||||
<CommandItem
|
||||
key={m.id}
|
||||
value={m.id.toString()}
|
||||
onSelect={(val) => {
|
||||
setSelected((prev) =>
|
||||
prev.find((p) => p.id === parseInt(val))
|
||||
? prev.filter((v) => v.id !== parseInt(val))
|
||||
: [...prev, m],
|
||||
);
|
||||
}}
|
||||
asChild
|
||||
>
|
||||
<div className="text-rgray-11">
|
||||
<img
|
||||
src={
|
||||
m.type === "note"
|
||||
? "/note.svg"
|
||||
: m.image ?? "/icons/logo_without_bg.png"
|
||||
}
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
{m.title}
|
||||
<Check
|
||||
data-state-on={
|
||||
selected.find((i) => i.id === m.id) !== undefined
|
||||
}
|
||||
className={cn(
|
||||
"on:opacity-100 ml-auto h-4 w-4 opacity-0",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ import { DialogTrigger } from "@radix-ui/react-dialog";
|
|||
import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
|
||||
import { ExpandedSpace } from "./ExpandedSpace";
|
||||
import { StoredContent, StoredSpace } from "@/server/db/schema";
|
||||
import Image from "next/image"
|
||||
import Image from "next/image";
|
||||
import { useDebounce } from "@/hooks/useDebounce";
|
||||
|
||||
export function MemoriesBar() {
|
||||
|
|
@ -57,11 +57,11 @@ export function MemoriesBar() {
|
|||
>(null);
|
||||
|
||||
const [expandedSpace, setExpandedSpace] = useState<number | null>(null);
|
||||
const [searchQuery, setSearcyQuery] = useState("");
|
||||
const [searchLoading, setSearchLoading] = useState(false)
|
||||
const query = useDebounce(searchQuery, 500)
|
||||
const [searchQuery, setSearcyQuery] = useState("");
|
||||
const [searchLoading, setSearchLoading] = useState(false);
|
||||
const query = useDebounce(searchQuery, 500);
|
||||
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
||||
|
||||
if (expandedSpace) {
|
||||
return (
|
||||
|
|
@ -72,20 +72,20 @@ export function MemoriesBar() {
|
|||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const q = query.trim()
|
||||
if (q.length < 1) {
|
||||
setSearchResults([])
|
||||
return
|
||||
}
|
||||
useEffect(() => {
|
||||
const q = query.trim();
|
||||
if (q.length < 1) {
|
||||
setSearchResults([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchLoading(true);
|
||||
setSearchLoading(true);
|
||||
|
||||
(async () => {
|
||||
setSearchResults(await search(q))
|
||||
setSearchLoading(false)
|
||||
})();
|
||||
}, [query])
|
||||
(async () => {
|
||||
setSearchResults(await search(q));
|
||||
setSearchLoading(false);
|
||||
})();
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
|
||||
|
|
@ -93,10 +93,16 @@ export function MemoriesBar() {
|
|||
<h1 className="w-full text-2xl">Your Memories</h1>
|
||||
<InputWithIcon
|
||||
placeholder="Search"
|
||||
icon={searchLoading ? <Loader className="text-rgray-11 h-5 w-5 opacity-50 animate-spin" /> : <Search className="text-rgray-11 h-5 w-5 opacity-50" />}
|
||||
icon={
|
||||
searchLoading ? (
|
||||
<Loader className="text-rgray-11 h-5 w-5 animate-spin opacity-50" />
|
||||
) : (
|
||||
<Search className="text-rgray-11 h-5 w-5 opacity-50" />
|
||||
)
|
||||
}
|
||||
className="bg-rgray-4 mt-2 w-full"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearcyQuery(e.target.value)}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearcyQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full px-8">
|
||||
|
|
@ -147,34 +153,32 @@ export function MemoriesBar() {
|
|||
ref={parent}
|
||||
className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
|
||||
>
|
||||
{query.trim().length > 0 ? (
|
||||
<>
|
||||
{searchResults.map(({ type, space, memory }, i) => (
|
||||
<>
|
||||
{type === "memory" && (
|
||||
<MemoryItem {...memory!} key={i} />
|
||||
)}
|
||||
{type === "space" && (
|
||||
<SpaceItem {...space!} key={i} onDelete={() => {}} />
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
): (
|
||||
<>
|
||||
{spaces.map((space) => (
|
||||
<SpaceItem
|
||||
onDelete={() => deleteSpace(space.id)}
|
||||
key={space.id}
|
||||
//onClick={() => setExpandedSpace(space.id)}
|
||||
{...space}
|
||||
/>
|
||||
))}
|
||||
{freeMemories.map(m => (
|
||||
<MemoryItem {...m} key={m.id} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{query.trim().length > 0 ? (
|
||||
<>
|
||||
{searchResults.map(({ type, space, memory }, i) => (
|
||||
<>
|
||||
{type === "memory" && <MemoryItem {...memory!} key={i} />}
|
||||
{type === "space" && (
|
||||
<SpaceItem {...space!} key={i} onDelete={() => {}} />
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{spaces.map((space) => (
|
||||
<SpaceItem
|
||||
onDelete={() => deleteSpace(space.id)}
|
||||
key={space.id}
|
||||
//onClick={() => setExpandedSpace(space.id)}
|
||||
{...space}
|
||||
/>
|
||||
))}
|
||||
{freeMemories.map((m) => (
|
||||
<MemoryItem {...m} key={m.id} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -190,44 +194,40 @@ const SpaceExitVariant: Variant = {
|
|||
},
|
||||
};
|
||||
|
||||
export function MemoryItem({
|
||||
id,
|
||||
title,
|
||||
image,
|
||||
type
|
||||
}: StoredContent) {
|
||||
export function MemoryItem({ id, title, image, type }: StoredContent) {
|
||||
const name = title
|
||||
? title.length > 10
|
||||
? title.slice(0, 10) + "..."
|
||||
: title
|
||||
: "<no title>";
|
||||
|
||||
const name = title ? title.length > 10 ? title.slice(0, 10) + "..." : title : '<no title>';
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100"
|
||||
>
|
||||
<button data-space-text className="focus-visible:outline-none">
|
||||
return (
|
||||
<div className="hover:bg-rgray-2 has-[[data-state='true']]:bg-rgray-2 has-[[data-space-text]:focus-visible]:bg-rgray-2 has-[[data-space-text]:focus-visible]:ring-rgray-7 [&:has-[[data-space-text]:focus-visible]>[data-more-button]]:opacity-100 relative flex select-none flex-col-reverse items-center justify-center rounded-md p-2 pb-4 text-center font-normal ring-transparent transition has-[[data-space-text]:focus-visible]:outline-none has-[[data-space-text]:focus-visible]:ring-2 md:has-[[data-state='true']]:bg-transparent [&:hover>[data-more-button]]:opacity-100">
|
||||
<button data-space-text className="focus-visible:outline-none">
|
||||
{name}
|
||||
</button>
|
||||
|
||||
<div className="w-24 h-24 flex justify-center items-center">
|
||||
{type === "page" ? (
|
||||
<img
|
||||
className="h-16 w-16"
|
||||
id={id.toString()}
|
||||
src={image!}
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src = "/icons/white_without_bg.png"
|
||||
}}
|
||||
/>
|
||||
): type === "note" ? (
|
||||
<div className="shadow-md rounded-md bg-rgray-4 p-2 flex justify-center items-center">
|
||||
<Text className="w-10 h-10" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="flex h-24 w-24 items-center justify-center">
|
||||
{type === "page" ? (
|
||||
<img
|
||||
className="h-16 w-16"
|
||||
id={id.toString()}
|
||||
src={image!}
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/icons/white_without_bg.png";
|
||||
}}
|
||||
/>
|
||||
) : type === "note" ? (
|
||||
<div className="bg-rgray-4 flex items-center justify-center rounded-md p-2 shadow-md">
|
||||
<Text className="h-10 w-10" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SpaceItem({
|
||||
|
|
@ -236,8 +236,7 @@ export function SpaceItem({
|
|||
onDelete,
|
||||
onClick,
|
||||
}: StoredSpace & { onDelete: () => void; onClick?: () => void }) {
|
||||
|
||||
const { cachedMemories } = useMemory();
|
||||
const { cachedMemories } = useMemory();
|
||||
|
||||
const [itemRef, animateItem] = useAnimate();
|
||||
const { width } = useViewport();
|
||||
|
|
@ -250,11 +249,11 @@ export function SpaceItem({
|
|||
},
|
||||
});
|
||||
|
||||
const spaceMemories = useMemo(() => {
|
||||
return cachedMemories.filter(m => m.space === id)
|
||||
}, [cachedMemories])
|
||||
const spaceMemories = useMemo(() => {
|
||||
return cachedMemories.filter((m) => m.space === id);
|
||||
}, [cachedMemories]);
|
||||
|
||||
const _name = name.length > 10 ? name.slice(0, 10) + "..." : name
|
||||
const _name = name.length > 10 ? name.slice(0, 10) + "..." : name;
|
||||
return (
|
||||
<motion.div
|
||||
ref={itemRef}
|
||||
|
|
@ -269,110 +268,120 @@ export function SpaceItem({
|
|||
isOpen={moreDropdownOpen}
|
||||
setIsOpen={setMoreDropdownOpen}
|
||||
onDelete={() => {
|
||||
onDelete()
|
||||
return;
|
||||
onDelete();
|
||||
return;
|
||||
if (!itemRef.current || width < 768) {
|
||||
onDelete();
|
||||
return;
|
||||
}
|
||||
// const trash = document.querySelector("#trash")! as HTMLDivElement;
|
||||
// const trashBin = document.querySelector("#trash-button")!;
|
||||
// const trashRect = trashBin.getBoundingClientRect();
|
||||
// const scopeRect = itemRef.current.getBoundingClientRect();
|
||||
// const el = document.createElement("div");
|
||||
// el.style.position = "fixed";
|
||||
// el.style.top = "0";
|
||||
// el.style.left = "0";
|
||||
// el.style.width = "15px";
|
||||
// el.style.height = "15px";
|
||||
// el.style.backgroundColor = "var(--gray-7)";
|
||||
// el.style.zIndex = "60";
|
||||
// el.style.borderRadius = "50%";
|
||||
// el.style.transform = "scale(5)";
|
||||
// el.style.opacity = "0";
|
||||
// trash.dataset["open"] = "true";
|
||||
// const initial = {
|
||||
// x: scopeRect.left + scopeRect.width / 2,
|
||||
// y: scopeRect.top + scopeRect.height / 2,
|
||||
// };
|
||||
// const delta = {
|
||||
// x:
|
||||
// trashRect.left +
|
||||
// trashRect.width / 2 -
|
||||
// scopeRect.left +
|
||||
// scopeRect.width / 2,
|
||||
// y:
|
||||
// trashRect.top +
|
||||
// trashRect.height / 4 -
|
||||
// scopeRect.top +
|
||||
// scopeRect.height / 2,
|
||||
// };
|
||||
// const end = {
|
||||
// x: trashRect.left + trashRect.width / 2,
|
||||
// y: trashRect.top + trashRect.height / 4,
|
||||
// };
|
||||
// el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`;
|
||||
// animateItem(itemRef.current, SpaceExitVariant, {
|
||||
// duration: 0.2,
|
||||
// }).then(() => {
|
||||
// itemRef.current.style.scale = "0";
|
||||
// onDelete();
|
||||
// });
|
||||
// document.body.appendChild(el);
|
||||
// el.animate(
|
||||
// {
|
||||
// transform: ["scale(5)", "scale(1)"],
|
||||
// opacity: [0, 0.3, 1],
|
||||
// },
|
||||
// {
|
||||
// duration: 200,
|
||||
// easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
// fill: "forwards",
|
||||
// },
|
||||
// );
|
||||
// el.animate(
|
||||
// {
|
||||
// offsetDistance: ["0%", "100%"],
|
||||
// },
|
||||
// {
|
||||
// duration: 2000,
|
||||
// easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
// fill: "forwards",
|
||||
// delay: 200,
|
||||
// },
|
||||
// ).onfinish = () => {
|
||||
// el.animate(
|
||||
// { transform: "scale(0)", opacity: 0 },
|
||||
// { duration: 200, fill: "forwards" },
|
||||
// ).onfinish = () => {
|
||||
// el.remove();
|
||||
// };
|
||||
// };
|
||||
// const trash = document.querySelector("#trash")! as HTMLDivElement;
|
||||
// const trashBin = document.querySelector("#trash-button")!;
|
||||
// const trashRect = trashBin.getBoundingClientRect();
|
||||
// const scopeRect = itemRef.current.getBoundingClientRect();
|
||||
// const el = document.createElement("div");
|
||||
// el.style.position = "fixed";
|
||||
// el.style.top = "0";
|
||||
// el.style.left = "0";
|
||||
// el.style.width = "15px";
|
||||
// el.style.height = "15px";
|
||||
// el.style.backgroundColor = "var(--gray-7)";
|
||||
// el.style.zIndex = "60";
|
||||
// el.style.borderRadius = "50%";
|
||||
// el.style.transform = "scale(5)";
|
||||
// el.style.opacity = "0";
|
||||
// trash.dataset["open"] = "true";
|
||||
// const initial = {
|
||||
// x: scopeRect.left + scopeRect.width / 2,
|
||||
// y: scopeRect.top + scopeRect.height / 2,
|
||||
// };
|
||||
// const delta = {
|
||||
// x:
|
||||
// trashRect.left +
|
||||
// trashRect.width / 2 -
|
||||
// scopeRect.left +
|
||||
// scopeRect.width / 2,
|
||||
// y:
|
||||
// trashRect.top +
|
||||
// trashRect.height / 4 -
|
||||
// scopeRect.top +
|
||||
// scopeRect.height / 2,
|
||||
// };
|
||||
// const end = {
|
||||
// x: trashRect.left + trashRect.width / 2,
|
||||
// y: trashRect.top + trashRect.height / 4,
|
||||
// };
|
||||
// el.style.offsetPath = `path('M ${initial.x} ${initial.y} Q ${delta.x * 0.01} ${delta.y * 0.01} ${end.x} ${end.y}`;
|
||||
// animateItem(itemRef.current, SpaceExitVariant, {
|
||||
// duration: 0.2,
|
||||
// }).then(() => {
|
||||
// itemRef.current.style.scale = "0";
|
||||
// onDelete();
|
||||
// });
|
||||
// document.body.appendChild(el);
|
||||
// el.animate(
|
||||
// {
|
||||
// transform: ["scale(5)", "scale(1)"],
|
||||
// opacity: [0, 0.3, 1],
|
||||
// },
|
||||
// {
|
||||
// duration: 200,
|
||||
// easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
// fill: "forwards",
|
||||
// },
|
||||
// );
|
||||
// el.animate(
|
||||
// {
|
||||
// offsetDistance: ["0%", "100%"],
|
||||
// },
|
||||
// {
|
||||
// duration: 2000,
|
||||
// easing: "cubic-bezier(0.64, 0.57, 0.67, 1.53)",
|
||||
// fill: "forwards",
|
||||
// delay: 200,
|
||||
// },
|
||||
// ).onfinish = () => {
|
||||
// el.animate(
|
||||
// { transform: "scale(0)", opacity: 0 },
|
||||
// { duration: 200, fill: "forwards" },
|
||||
// ).onfinish = () => {
|
||||
// el.remove();
|
||||
// };
|
||||
// };
|
||||
}}
|
||||
/>
|
||||
{spaceMemories.length > 2 ? (
|
||||
<MemoryWithImages3
|
||||
className="h-24 w-24"
|
||||
id={id.toString()}
|
||||
images={spaceMemories.map((c) => c.type === 'note' ? '/note.svg' : c.image).reverse() as string[]}
|
||||
images={
|
||||
spaceMemories
|
||||
.map((c) => (c.type === "note" ? "/note.svg" : c.image))
|
||||
.reverse() as string[]
|
||||
}
|
||||
/>
|
||||
) : spaceMemories.length > 1 ? (
|
||||
<MemoryWithImages2
|
||||
<MemoryWithImages2
|
||||
className="h-24 w-24"
|
||||
id={id.toString()}
|
||||
images={spaceMemories.map((c) => c.type === 'note' ? '/note.svg' : c.image).reverse() as string[]}
|
||||
images={
|
||||
spaceMemories
|
||||
.map((c) => (c.type === "note" ? "/note.svg" : c.image))
|
||||
.reverse() as string[]
|
||||
}
|
||||
/>
|
||||
) : spaceMemories.length === 1 ? (
|
||||
) : spaceMemories.length === 1 ? (
|
||||
<MemoryWithImage
|
||||
className="h-24 w-24"
|
||||
id={id.toString()}
|
||||
image={spaceMemories[0].type === 'note' ? '/note.svg' : spaceMemories[0].image!}
|
||||
image={
|
||||
spaceMemories[0].type === "note"
|
||||
? "/note.svg"
|
||||
: spaceMemories[0].image!
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-rgray-4 opacity-30 rounded-full w-24 h-24 scale-50 shadow-">
|
||||
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-rgray-4 shadow- h-24 w-24 scale-50 rounded-full opacity-30"></div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -409,34 +418,32 @@ export function SpaceMoreButton({
|
|||
<Edit3 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10">
|
||||
<Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DialogContent>
|
||||
<DialogTitle className='text-xl'>Are you sure?</DialogTitle>
|
||||
<DialogDescription className='text-md'>You will not be able to recover this space</DialogDescription>
|
||||
<DialogFooter>
|
||||
<DialogClose
|
||||
type={undefined}
|
||||
onClick={onDelete}
|
||||
className="bg-red-500/40 focus-visible:bg-red-500/60 focus-visible:ring-red-500 hover:bg-red-500/60 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2"
|
||||
>
|
||||
Delete
|
||||
</DialogClose>
|
||||
<DialogClose
|
||||
className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2"
|
||||
>
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
<DialogContent>
|
||||
<DialogTitle className="text-xl">Are you sure?</DialogTitle>
|
||||
<DialogDescription className="text-md">
|
||||
You will not be able to recover this space
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<DialogClose
|
||||
type={undefined}
|
||||
onClick={onDelete}
|
||||
className="ml-auto flex items-center justify-center rounded-md bg-red-500/40 px-3 py-2 transition hover:bg-red-500/60 focus-visible:bg-red-500/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500"
|
||||
>
|
||||
Delete
|
||||
</DialogClose>
|
||||
<DialogClose className="focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 hover:bg-rgray-4 ml-auto flex items-center justify-center rounded-md px-3 py-2 transition focus-visible:outline-none focus-visible:ring-2">
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { Loader, Search } from "lucide-react";
|
|||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import { isSea } from "node:sea";
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
|
|
@ -40,13 +39,19 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|||
|
||||
const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & { isSearching?: boolean }
|
||||
>(({ className, isSearching = false ,...props }, ref) => (
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & {
|
||||
isSearching?: boolean;
|
||||
}
|
||||
>(({ className, isSearching = false, ...props }, ref) => (
|
||||
<div
|
||||
className="border-rgray-6 flex items-center border-b px-3"
|
||||
cmdk-input-wrapper=""
|
||||
>
|
||||
{isSearching ? <Loader className="mr-2 h-4 w-4 shrink-9 opacity-50 animate-spin" /> : <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />}
|
||||
{isSearching ? (
|
||||
<Loader className="shrink-9 mr-2 h-4 w-4 animate-spin opacity-50" />
|
||||
) : (
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
)}
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
"use client";
|
||||
import React, { useCallback } from "react";
|
||||
import { ChachedSpaceContent, StoredContent, storedContent, StoredSpace } from "@/server/db/schema";
|
||||
import { addMemory, searchMemoriesAndSpaces, addSpace, fetchContentForSpace, deleteSpace, deleteMemory, fetchFreeMemories } from "@/actions/db";
|
||||
import {
|
||||
ChachedSpaceContent,
|
||||
StoredContent,
|
||||
storedContent,
|
||||
StoredSpace,
|
||||
} from "@/server/db/schema";
|
||||
import {
|
||||
addMemory,
|
||||
searchMemoriesAndSpaces,
|
||||
addSpace,
|
||||
fetchContentForSpace,
|
||||
deleteSpace,
|
||||
deleteMemory,
|
||||
fetchFreeMemories,
|
||||
} from "@/actions/db";
|
||||
import { User } from "next-auth";
|
||||
|
||||
export type SearchResult = {
|
||||
type: "memory" | "space",
|
||||
space: StoredSpace,
|
||||
memory: StoredContent
|
||||
}
|
||||
type: "memory" | "space";
|
||||
space: StoredSpace;
|
||||
memory: StoredContent;
|
||||
};
|
||||
|
||||
// temperory (will change)
|
||||
export const MemoryContext = React.createContext<{
|
||||
|
|
@ -17,107 +30,118 @@ export const MemoryContext = React.createContext<{
|
|||
addSpace: typeof addSpace;
|
||||
addMemory: typeof addMemory;
|
||||
cachedMemories: ChachedSpaceContent[];
|
||||
search: typeof searchMemoriesAndSpaces;
|
||||
deleteSpace: typeof deleteSpace;
|
||||
deleteMemory: typeof deleteMemory;
|
||||
search: typeof searchMemoriesAndSpaces;
|
||||
deleteSpace: typeof deleteSpace;
|
||||
deleteMemory: typeof deleteMemory;
|
||||
}>({
|
||||
spaces: [],
|
||||
freeMemories: [],
|
||||
addMemory: (() => {}) as unknown as (typeof addMemory),
|
||||
addSpace: (async () => {}) as unknown as (typeof addSpace),
|
||||
addMemory: (() => {}) as unknown as typeof addMemory,
|
||||
addSpace: (async () => {}) as unknown as typeof addSpace,
|
||||
cachedMemories: [],
|
||||
search: async () => [],
|
||||
deleteMemory: (() => {}) as unknown as (typeof deleteMemory),
|
||||
deleteSpace: (() => {}) as unknown as (typeof deleteSpace)
|
||||
search: async () => [],
|
||||
deleteMemory: (() => {}) as unknown as typeof deleteMemory,
|
||||
deleteSpace: (() => {}) as unknown as typeof deleteSpace,
|
||||
});
|
||||
|
||||
export const MemoryProvider: React.FC<
|
||||
{
|
||||
spaces: StoredSpace[];
|
||||
freeMemories: StoredContent[];
|
||||
cachedMemories: ChachedSpaceContent[];
|
||||
user: User;
|
||||
cachedMemories: ChachedSpaceContent[];
|
||||
user: User;
|
||||
} & React.PropsWithChildren
|
||||
> = ({ children, user, spaces: initalSpaces, freeMemories: initialFreeMemories, cachedMemories: initialCachedMemories }) => {
|
||||
|
||||
> = ({
|
||||
children,
|
||||
user,
|
||||
spaces: initalSpaces,
|
||||
freeMemories: initialFreeMemories,
|
||||
cachedMemories: initialCachedMemories,
|
||||
}) => {
|
||||
const [spaces, setSpaces] = React.useState<StoredSpace[]>(initalSpaces);
|
||||
const [freeMemories, setFreeMemories] =
|
||||
React.useState<StoredContent[]>(initialFreeMemories);
|
||||
|
||||
const [cachedMemories, setCachedMemories] = React.useState<ChachedSpaceContent[]>(
|
||||
initialCachedMemories
|
||||
);
|
||||
|
||||
const _deleteSpace: typeof deleteSpace = async (...params) => {
|
||||
const deleted = (await deleteSpace(...params))!
|
||||
const [cachedMemories, setCachedMemories] = React.useState<
|
||||
ChachedSpaceContent[]
|
||||
>(initialCachedMemories);
|
||||
|
||||
setSpaces(prev => prev.filter(i => i.id !== deleted.id))
|
||||
setCachedMemories(prev => prev.filter(i => i.space !== deleted.id))
|
||||
const _deleteSpace: typeof deleteSpace = async (...params) => {
|
||||
const deleted = (await deleteSpace(...params))!;
|
||||
|
||||
setFreeMemories(await fetchFreeMemories())
|
||||
setSpaces((prev) => prev.filter((i) => i.id !== deleted.id));
|
||||
setCachedMemories((prev) => prev.filter((i) => i.space !== deleted.id));
|
||||
|
||||
return deleted
|
||||
}
|
||||
setFreeMemories(await fetchFreeMemories());
|
||||
|
||||
const _deleteMemory: typeof deleteMemory = async (...params) => {
|
||||
const deleted = (await deleteMemory(...params))!
|
||||
return deleted;
|
||||
};
|
||||
|
||||
setCachedMemories(prev => prev.filter(i => i.id !== deleted.id))
|
||||
setFreeMemories(await fetchFreeMemories())
|
||||
const _deleteMemory: typeof deleteMemory = async (...params) => {
|
||||
const deleted = (await deleteMemory(...params))!;
|
||||
|
||||
setCachedMemories((prev) => prev.filter((i) => i.id !== deleted.id));
|
||||
setFreeMemories(await fetchFreeMemories());
|
||||
|
||||
return deleted;
|
||||
};
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
// const fetchMemories = useCallback(async (query: string) => {
|
||||
// const response = await fetch(`/api/memories?${query}`);
|
||||
// }, []);
|
||||
|
||||
|
||||
const _addSpace: typeof addSpace = async (...params) => {
|
||||
const { space: addedSpace, addedMemories } = (await addSpace(...params))!;
|
||||
const _addSpace: typeof addSpace = async (...params) => {
|
||||
const { space: addedSpace, addedMemories } = (await addSpace(...params))!;
|
||||
|
||||
setSpaces(prev => [...prev, addedSpace])
|
||||
const cachedMemories = (await fetchContentForSpace(addedSpace.id, {
|
||||
offset: 0,
|
||||
limit: 3
|
||||
})).map(m => ({ ...m, space: addedSpace.id }))
|
||||
|
||||
setCachedMemories(prev => [...prev, ...cachedMemories])
|
||||
setSpaces((prev) => [...prev, addedSpace]);
|
||||
const cachedMemories = (
|
||||
await fetchContentForSpace(addedSpace.id, {
|
||||
offset: 0,
|
||||
limit: 3,
|
||||
})
|
||||
).map((m) => ({ ...m, space: addedSpace.id }));
|
||||
|
||||
setFreeMemories(await fetchFreeMemories())
|
||||
|
||||
return {
|
||||
space: addedSpace, addedMemories
|
||||
}
|
||||
}
|
||||
setCachedMemories((prev) => [...prev, ...cachedMemories]);
|
||||
|
||||
const _addMemory: typeof addMemory = async (...params) => {
|
||||
const { memory: addedMemory, addedToSpaces } = (await addMemory(...params))!;
|
||||
setFreeMemories(await fetchFreeMemories());
|
||||
|
||||
addedToSpaces.length > 0 ? setCachedMemories(prev => [
|
||||
...prev,
|
||||
...addedToSpaces.map(s => ({
|
||||
...addedMemory,
|
||||
space: s.spaceId
|
||||
}))
|
||||
]) : setFreeMemories(prev => [...prev, addedMemory])
|
||||
return {
|
||||
space: addedSpace,
|
||||
addedMemories,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
memory: addedMemory,
|
||||
addedToSpaces
|
||||
}
|
||||
}
|
||||
const _addMemory: typeof addMemory = async (...params) => {
|
||||
const { memory: addedMemory, addedToSpaces } = (await addMemory(
|
||||
...params,
|
||||
))!;
|
||||
|
||||
addedToSpaces.length > 0
|
||||
? setCachedMemories((prev) => [
|
||||
...prev,
|
||||
...addedToSpaces.map((s) => ({
|
||||
...addedMemory,
|
||||
space: s.spaceId,
|
||||
})),
|
||||
])
|
||||
: setFreeMemories((prev) => [...prev, addedMemory]);
|
||||
|
||||
return {
|
||||
memory: addedMemory,
|
||||
addedToSpaces,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<MemoryContext.Provider
|
||||
value={{
|
||||
search: searchMemoriesAndSpaces,
|
||||
search: searchMemoriesAndSpaces,
|
||||
spaces,
|
||||
addSpace: _addSpace,
|
||||
deleteSpace: _deleteSpace,
|
||||
freeMemories,
|
||||
cachedMemories,
|
||||
deleteMemory: _deleteMemory,
|
||||
deleteMemory: _deleteMemory,
|
||||
addMemory: _addMemory,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* Use this hook when you need to debounce a value.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const users = createTable("user", {
|
|||
image: text("image", { length: 255 }),
|
||||
});
|
||||
|
||||
export type User = typeof users.$inferSelect
|
||||
export type User = typeof users.$inferSelect;
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
accounts: many(accounts),
|
||||
|
|
@ -34,7 +34,7 @@ export const accounts = createTable(
|
|||
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
|
||||
userId: text("userId", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type", { length: 255 }).notNull(),
|
||||
provider: text("provider", { length: 255 }).notNull(),
|
||||
providerAccountId: text("providerAccountId", { length: 255 }).notNull(),
|
||||
|
|
@ -60,7 +60,7 @@ export const sessions = createTable(
|
|||
sessionToken: text("sessionToken", { length: 255 }).notNull(),
|
||||
userId: text("userId", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: int("expires", { mode: "timestamp" }).notNull(),
|
||||
},
|
||||
(session) => ({
|
||||
|
|
@ -94,7 +94,9 @@ export const storedContent = createTable(
|
|||
"page",
|
||||
),
|
||||
image: text("image", { length: 255 }),
|
||||
user: text("user", { length: 255 }).references(() => users.id, { onDelete: 'cascade' }),
|
||||
user: text("user", { length: 255 }).references(() => users.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
},
|
||||
(sc) => ({
|
||||
urlIdx: index("storedContent_url_idx").on(sc.url),
|
||||
|
|
@ -109,10 +111,10 @@ export const contentToSpace = createTable(
|
|||
{
|
||||
contentId: integer("contentId")
|
||||
.notNull()
|
||||
.references(() => storedContent.id, { onDelete: 'cascade' }),
|
||||
.references(() => storedContent.id, { onDelete: "cascade" }),
|
||||
spaceId: integer("spaceId")
|
||||
.notNull()
|
||||
.references(() => space.id, { onDelete: 'cascade' }),
|
||||
.references(() => space.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(cts) => ({
|
||||
compoundKey: primaryKey({ columns: [cts.contentId, cts.spaceId] }),
|
||||
|
|
@ -124,7 +126,9 @@ export const space = createTable(
|
|||
{
|
||||
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
|
||||
name: text("name").notNull().unique().default("none"),
|
||||
user: text("user", { length: 255 }).references(() => users.id, { onDelete: 'cascade' }),
|
||||
user: text("user", { length: 255 }).references(() => users.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
},
|
||||
(space) => ({
|
||||
nameIdx: index("spaces_name_idx").on(space.name),
|
||||
|
|
@ -135,5 +139,5 @@ export const space = createTable(
|
|||
export type StoredContent = Omit<typeof storedContent.$inferSelect, "user">;
|
||||
export type StoredSpace = typeof space.$inferSelect;
|
||||
export type ChachedSpaceContent = StoredContent & {
|
||||
space: number;
|
||||
}
|
||||
space: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import { db } from "."
|
||||
import { space, user } from "./schema"
|
||||
import { db } from ".";
|
||||
import { space, user } from "./schema";
|
||||
|
||||
const user = await db.select(user).all()
|
||||
const user = await db.select(user).all();
|
||||
|
||||
await db.insert(space).values([
|
||||
{
|
||||
|
||||
}
|
||||
])
|
||||
await db.insert(space).values([{}]);
|
||||
|
|
|
|||
|
|
@ -1,28 +1,32 @@
|
|||
'use server';
|
||||
import * as cheerio from "cheerio"
|
||||
"use server";
|
||||
import * as cheerio from "cheerio";
|
||||
|
||||
export async function getMetaData(url: string) {
|
||||
const response = await fetch(url);
|
||||
const html = await response.text();
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// Extract the base URL
|
||||
const baseUrl = new URL(url).origin;
|
||||
|
||||
// Extract title
|
||||
const title = $('title').text().trim()
|
||||
const title = $("title").text().trim();
|
||||
|
||||
const description = $('meta[name=description]').attr('content') ?? ''
|
||||
const description = $("meta[name=description]").attr("content") ?? "";
|
||||
|
||||
const _favicon = $('link[rel=icon]').attr('href') ?? 'https://supermemory.dhr.wtf/web.svg';
|
||||
|
||||
let favicon = _favicon.trim().length > 0 ? _favicon.trim() : 'https://supermemory.dhr.wtf/web.svg'
|
||||
if (favicon.startsWith("/")) {
|
||||
favicon = baseUrl + favicon
|
||||
} else if (favicon.startsWith("./")) {
|
||||
favicon = baseUrl + favicon.slice(1)
|
||||
}
|
||||
const _favicon =
|
||||
$("link[rel=icon]").attr("href") ?? "https://supermemory.dhr.wtf/web.svg";
|
||||
|
||||
let favicon =
|
||||
_favicon.trim().length > 0
|
||||
? _favicon.trim()
|
||||
: "https://supermemory.dhr.wtf/web.svg";
|
||||
if (favicon.startsWith("/")) {
|
||||
favicon = baseUrl + favicon;
|
||||
} else if (favicon.startsWith("./")) {
|
||||
favicon = baseUrl + favicon.slice(1);
|
||||
}
|
||||
|
||||
// Prepare the metadata object
|
||||
const metadata = {
|
||||
|
|
|
|||
|
|
@ -14,40 +14,55 @@ export async function fetchContentForSpace(
|
|||
limit: number;
|
||||
},
|
||||
) {
|
||||
|
||||
const query = db
|
||||
.select()
|
||||
.from(storedContent)
|
||||
.where(
|
||||
exists(
|
||||
db.select().from(contentToSpace).where(and(eq(contentToSpace.spaceId, spaceId), eq(contentToSpace.contentId, storedContent.id))),
|
||||
db
|
||||
.select()
|
||||
.from(contentToSpace)
|
||||
.where(
|
||||
and(
|
||||
eq(contentToSpace.spaceId, spaceId),
|
||||
eq(contentToSpace.contentId, storedContent.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
).orderBy(asc(storedContent.title))
|
||||
)
|
||||
.orderBy(asc(storedContent.title));
|
||||
|
||||
return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
|
||||
return range
|
||||
? await query.limit(range.limit).offset(range.offset)
|
||||
: await query.all();
|
||||
}
|
||||
|
||||
export async function fetchFreeMemories(
|
||||
userId: string,
|
||||
range?: {
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
userId: string,
|
||||
range?: {
|
||||
offset: number;
|
||||
limit: number;
|
||||
},
|
||||
) {
|
||||
const query = db
|
||||
const query = db
|
||||
.select()
|
||||
.from(storedContent)
|
||||
.where(
|
||||
and(
|
||||
notExists(
|
||||
db.select().from(contentToSpace).where(eq(contentToSpace.contentId, storedContent.id)),
|
||||
),
|
||||
eq(storedContent.user, userId),
|
||||
)
|
||||
|
||||
).orderBy(asc(storedContent.title))
|
||||
and(
|
||||
notExists(
|
||||
db
|
||||
.select()
|
||||
.from(contentToSpace)
|
||||
.where(eq(contentToSpace.contentId, storedContent.id)),
|
||||
),
|
||||
eq(storedContent.user, userId),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(storedContent.title));
|
||||
|
||||
return range ? await query.limit(range.limit).offset(range.offset) : await query.all()
|
||||
return range
|
||||
? await query.limit(range.limit).offset(range.offset)
|
||||
: await query.all();
|
||||
}
|
||||
|
||||
export const transformContent = async (
|
||||
|
|
|
|||
|
|
@ -17,4 +17,7 @@ type = "ratelimit"
|
|||
namespace_id = "1001"
|
||||
|
||||
# 25 requests per 10 seconds
|
||||
simple = { limit = 25, period = 10 }
|
||||
simple = { limit = 25, period = 10 }
|
||||
|
||||
[placement]
|
||||
mode = "smart"
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
"@auth/drizzle-adapter": "^0.7.0",
|
||||
"@cloudflare/ai": "^1.0.52",
|
||||
"@cloudflare/next-on-pages-next-dev": "^0.0.1",
|
||||
"@cloudflare/puppeteer": "^0.0.6",
|
||||
"@crxjs/vite-plugin": "^1.0.14",
|
||||
"@google/generative-ai": "^0.3.1",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"ai": "^3.0.0",
|
||||
"better-sqlite3": "^9.4.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"cloudflare": "^3.0.0",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"drizzle-orm": "^0.29.4",
|
||||
"eslint-plugin-next-on-pages": "^1.9.0",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue