From 9f1bd3798b0de052ba7b8db399c3e33133155329 Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Wed, 5 Nov 2025 14:57:52 -0800 Subject: [PATCH 1/2] chore: update new contributions for raycast extension --- apps/raycast-extension/CHANGELOG.md | 13 +- apps/raycast-extension/package-lock.json | 116 ++++------------- apps/raycast-extension/package.json | 19 ++- apps/raycast-extension/src/add-memory.tsx | 51 +++----- apps/raycast-extension/src/api.ts | 57 ++++----- .../raycast-extension/src/search-memories.tsx | 119 +++++------------- .../raycast-extension/src/search-projects.tsx | 106 ++++++++++++++++ .../raycast-extension/src/withSupermemory.tsx | 48 +++++++ 8 files changed, 276 insertions(+), 253 deletions(-) create mode 100644 apps/raycast-extension/src/search-projects.tsx create mode 100644 apps/raycast-extension/src/withSupermemory.tsx diff --git a/apps/raycast-extension/CHANGELOG.md b/apps/raycast-extension/CHANGELOG.md index 06b6fe0e..0a774ad7 100644 --- a/apps/raycast-extension/CHANGELOG.md +++ b/apps/raycast-extension/CHANGELOG.md @@ -1,6 +1,13 @@ -# supermemory-raycast Changelog +# Supermemory Changelog -## [Initial Version] - {2025-09-27} +## [Search Projects + Enhancements] - 2025-10-13 + +- Added command to search and add projects +- Removed `useEffect` +- Simplified `getApiKey` since `Preferences` will be enforced for presence and trim automatically +- Moved API Key check into its own HoC + +## [Initial Version] - 2025-10-02 - Added Supermemory integration with Add Memory and Search Memories commands -- Added project organization support for memories \ No newline at end of file +- Added project organization support for memories diff --git a/apps/raycast-extension/package-lock.json b/apps/raycast-extension/package-lock.json index 703ef839..323af290 100644 --- a/apps/raycast-extension/package-lock.json +++ b/apps/raycast-extension/package-lock.json @@ -7,8 +7,8 @@ "name": "supermemory", "license": "MIT", "dependencies": { - "@raycast/api": "^1.103.2", - "@raycast/utils": "^1.17.0" + "@raycast/api": "^1.103.3", + "@raycast/utils": "^2.2.1" }, "devDependencies": { "@raycast/eslint-config": "^2.0.4", @@ -1134,18 +1134,18 @@ } }, "node_modules/@raycast/api": { - "version": "1.103.2", - "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.103.2.tgz", - "integrity": "sha512-5QTKMRv86t0cUFH9a07ne8a+ry8bZ2xtOd7trx3yiMmJREr5edYXsqcGDsOyeJLjrjH7WhoRlESIW/CHgNwg8w==", + "version": "1.103.3", + "resolved": "https://registry.npmjs.org/@raycast/api/-/api-1.103.3.tgz", + "integrity": "sha512-+lS8yOrqSP7++ZyomZnMkN0pzEZto/XOqag8MXt9SmTuJBZvfMV74f9wzXmJkRmynYxQHnpLqvs9otWrDd3Bvw==", "license": "MIT", "dependencies": { - "@oclif/core": "^4.4.1", - "@oclif/plugin-autocomplete": "^3.2.31", - "@oclif/plugin-help": "^6.2.29", - "@oclif/plugin-not-found": "^3.2.57", + "@oclif/core": "^4.5.4", + "@oclif/plugin-autocomplete": "^3.2.35", + "@oclif/plugin-help": "^6.2.33", + "@oclif/plugin-not-found": "^3.2.68", "@types/node": "22.13.10", "@types/react": "19.0.10", - "esbuild": "^0.25.5", + "esbuild": "^0.25.10", "react": "19.0.0" }, "bin": { @@ -1204,20 +1204,21 @@ } }, "node_modules/@raycast/utils": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-1.19.1.tgz", - "integrity": "sha512-/udUGcTZCgZZwzesmjBkqG5naQZTD/ZLHbqRwkWcF+W97vf9tr9raxKyQjKsdZ17OVllw2T3sHBQsVUdEmCm2g==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-2.2.1.tgz", + "integrity": "sha512-MBOD3eccHTu1HVQstoqoJJsA5PubWv18EUq8Dxwg1PEJE+xVjEuslXpsNgR/2RtpTGeKgGiXePxUiVp5mILN5w==", "license": "MIT", "dependencies": { - "cross-fetch": "^3.1.6", - "dequal": "^2.0.3", - "object-hash": "^3.0.0", - "signal-exit": "^4.0.2", - "stream-chain": "^2.2.5", - "stream-json": "^1.8.0" + "dequal": "^2.0.3" }, "peerDependencies": { - "@raycast/api": ">=1.69.0" + "@raycast/api": ">=1.99.4", + "react": ">=19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } } }, "node_modules/@types/estree": { @@ -1733,15 +1734,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2608,35 +2600,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2910,21 +2873,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/stream-chain": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", - "license": "BSD-3-Clause" - }, - "node_modules/stream-json": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", - "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", - "license": "BSD-3-Clause", - "dependencies": { - "stream-chain": "^2.2.5" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3037,12 +2985,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -3127,22 +3069,6 @@ "punycode": "^2.1.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json index 9481601b..2d32ca52 100644 --- a/apps/raycast-extension/package.json +++ b/apps/raycast-extension/package.json @@ -5,6 +5,9 @@ "description": "Add and search memories with your personal AI-powered knowledge base", "icon": "extension-icon.png", "author": "supermemory", + "contributors": [ + "xmok" + ], "platforms": [ "macOS", "Windows" @@ -18,15 +21,23 @@ { "name": "add-memory", "title": "Add Memory", - "subtitle": "add memory to your supermemory app", + "subtitle": "Supermemory", "description": "Add text, URLs, or documents to your supermemory knowledge base", "mode": "view" }, { "name": "search-memories", "title": "Search Memories", + "subtitle": "Supermemory", "description": "Search through your saved memories and find relevant information", "mode": "view" + }, + { + "name": "search-projects", + "title": "Search Projects", + "subtitle": "Supermemory", + "description": "Search through your saved projects and find relevant information", + "mode": "view" } ], "preferences": [ @@ -40,8 +51,8 @@ } ], "dependencies": { - "@raycast/api": "^1.103.2", - "@raycast/utils": "^1.17.0" + "@raycast/api": "^1.103.3", + "@raycast/utils": "^2.2.1" }, "devDependencies": { "@raycast/eslint-config": "^2.0.4", @@ -59,4 +70,4 @@ "prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1", "publish": "npx @raycast/api@latest publish" } -} \ No newline at end of file +} diff --git a/apps/raycast-extension/src/add-memory.tsx b/apps/raycast-extension/src/add-memory.tsx index 87fb853f..9b6d2a6e 100644 --- a/apps/raycast-extension/src/add-memory.tsx +++ b/apps/raycast-extension/src/add-memory.tsx @@ -5,47 +5,24 @@ import { showToast, Toast, useNavigation, + Icon, } from "@raycast/api"; -import { useEffect, useState } from "react"; -import { - addMemory, - fetchProjects, - checkApiConnection, - type Project, -} from "./api"; +import { useState } from "react"; +import { addMemory, fetchProjects } from "./api"; +import { usePromise } from "@raycast/utils"; +import { withSupermemory } from "./withSupermemory"; interface FormValues { content: string; project: string; } -export default function Command() { - const [projects, setProjects] = useState([]); - const [isLoading, setIsLoading] = useState(true); +export default withSupermemory(Command); +function Command() { const [isSubmitting, setIsSubmitting] = useState(false); const { pop } = useNavigation(); - useEffect(() => { - async function loadProjects() { - try { - setIsLoading(true); - const isConnected = await checkApiConnection(); - if (!isConnected) { - return; - } - - const fetchedProjects = await fetchProjects(); - setProjects(fetchedProjects); - } catch (error) { - console.error("Failed to load projects:", error); - } finally { - setIsLoading(false); - } - } - - loadProjects(); - }, []); - + const { isLoading, data: projects = [] } = usePromise(fetchProjects); async function handleSubmit(values: FormValues) { if (!values.content.trim()) { await showToast({ @@ -78,9 +55,15 @@ export default function Command() {
- - + !isLoading && ( + + + + ) } > ; } +interface AddProjectRequest { + name: string; +} + export interface SearchRequest { q: string; containerTags?: string[]; @@ -66,28 +70,16 @@ class AuthenticationError extends Error { } } -async function getApiKey(): Promise { - try { - const preferences = getPreferenceValues<{ apiKey: string }>(); - const apiKey = preferences.apiKey?.trim(); - - if (!apiKey) { - throw new AuthenticationError( - "API key is required. Please add your Supermemory API key in preferences.", - ); - } - - return apiKey; - } catch { - throw new AuthenticationError("Failed to get API key from preferences."); - } +function getApiKey(): string { + const { apiKey } = getPreferenceValues(); + return apiKey; } async function makeAuthenticatedRequest( endpoint: string, options: RequestInit = {}, ): Promise { - const apiKey = await getApiKey(); + const apiKey = getApiKey(); const url = `${API_BASE_URL}${endpoint}`; @@ -159,6 +151,21 @@ export async function fetchProjects(): Promise { } } +export async function addProject(request: AddProjectRequest): Promise { + const response = await makeAuthenticatedRequest("/v3/projects", { + method: "POST", + body: JSON.stringify(request), + }); + + await showToast({ + style: Toast.Style.Success, + title: "Project Added", + message: "Successfully added project to Supermemory", + }); + + return response; +} + export async function addMemory(request: AddMemoryRequest): Promise { try { const response = await makeAuthenticatedRequest("/v3/documents", { @@ -209,19 +216,7 @@ export async function searchMemories( } // Helper function to check if API key is configured and valid -export async function checkApiConnection(): Promise { - try { - await fetchProjects(); - return true; - } catch (error) { - if (error instanceof AuthenticationError) { - await showToast({ - style: Toast.Style.Failure, - title: "API Key Required", - message: - "Please configure your Supermemory API key in preferences. Get it from https://supermemory.link/raycast", - }); - } - return false; - } +export async function fetchSettings(): Promise { + const response = await makeAuthenticatedRequest("/v3/settings"); + return response; } diff --git a/apps/raycast-extension/src/search-memories.tsx b/apps/raycast-extension/src/search-memories.tsx index 850fbe3c..846f1fd9 100644 --- a/apps/raycast-extension/src/search-memories.tsx +++ b/apps/raycast-extension/src/search-memories.tsx @@ -6,11 +6,11 @@ import { Icon, showToast, Toast, - Clipboard, - openExtensionPreferences, } from "@raycast/api"; -import { useState, useEffect, useCallback } from "react"; -import { searchMemories, checkApiConnection, type SearchResult } from "./api"; +import { useState } from "react"; +import { searchMemories, type SearchResult } from "./api"; +import { usePromise } from "@raycast/utils"; +import { withSupermemory } from "./withSupermemory"; const extractContent = (memory: SearchResult) => { if (memory.chunks && memory.chunks.length > 0) { @@ -46,67 +46,31 @@ const extractUrl = (memory: SearchResult) => { return null; }; -export default function Command() { - const [searchResults, setSearchResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); +export default withSupermemory(Command); +function Command() { const [searchText, setSearchText] = useState(""); - const [hasSearched, setHasSearched] = useState(false); - const [isConnected, setIsConnected] = useState(null); - useEffect(() => { - async function checkConnection() { - const connected = await checkApiConnection(); - setIsConnected(connected); - } - checkConnection(); - }, []); - - const performSearch = useCallback( + const { isLoading, data: searchResults = [] } = usePromise( async (query: string) => { - if (!query.trim() || !isConnected) return; - - try { - setIsLoading(true); - setHasSearched(true); + const q = query.trim(); + if (!q) return []; - const results = await searchMemories({ - q: query.trim(), - limit: 50, + const results = await searchMemories({ + q, + limit: 50, + }); + if (!results.length) { + await showToast({ + style: Toast.Style.Success, + title: "Search Complete", + message: "No memories found for your query", }); - - setSearchResults(results); - - if (results.length === 0) { - await showToast({ - style: Toast.Style.Success, - title: "Search Complete", - message: "No memories found for your query", - }); - } - } catch (error) { - console.error("Search failed:", error); - setSearchResults([]); - } finally { - setIsLoading(false); } + return results; }, - [isConnected], + [searchText], ); - useEffect(() => { - if (!searchText.trim()) { - setSearchResults([]); - setHasSearched(false); - return; - } - - const debounceTimer = setTimeout(() => { - performSearch(searchText); - }, 500); - - return () => clearTimeout(debounceTimer); - }, [searchText, performSearch]); - const formatDate = (dateString: string) => { try { return new Date(dateString).toLocaleDateString("en-US", { @@ -126,27 +90,7 @@ export default function Command() { return `${content.substring(0, maxLength)}...`; }; - if (isConnected === false) { - return ( - - - openExtensionPreferences()} - icon={Icon.Gear} - /> - - } - /> - - ); - } - + const hasSearched = !isLoading && !searchResults.length; return ( - {!hasSearched && !searchText.trim() ? ( + {hasSearched && !searchText.trim() ? ( - ) : hasSearched && searchResults.length === 0 ? ( + ) : hasSearched ? ( + ) : isLoading && searchText.trim() ? ( + ) : ( searchResults.map((memory) => { const content = extractContent(memory); @@ -175,7 +124,7 @@ export default function Command() { key={memory.documentId} icon={url ? Icon.Link : Icon.Document} title={memory.title || "Untitled Memory"} - subtitle={truncateContent(content)} + subtitle={{ value: truncateContent(content), tooltip: content }} accessories={[ { text: formatDate(memory.createdAt) }, ...(memory.score @@ -189,11 +138,10 @@ export default function Command() { target={} icon={Icon.Eye} /> - Clipboard.copy(content)} - icon={Icon.Clipboard} shortcut={{ modifiers: ["cmd"], key: "c" }} + content={content} /> {url && ( - Clipboard.copy(content)} - icon={Icon.Clipboard} shortcut={{ modifiers: ["cmd"], key: "c" }} + content={content} /> {url && ( + {!isLoading && !projects?.length ? ( + + } + onPop={mutate} + /> + + } + /> + ) : ( + projects?.map((project) => ( + + } + onPop={mutate} + /> + + } + /> + )) + )} + + ); +} + +function CreateProject() { + const { pop } = useNavigation(); + const [isLoading, setIsLoading] = useState(false); + const { handleSubmit, itemProps } = useForm<{ name: string }>({ + async onSubmit(values) { + setIsLoading(true); + try { + await addProject(values); + pop(); + } catch (error) { + await showFailureToast(error, { title: "Failed to add project" }); + } finally { + setIsLoading(false); + } + }, + validation: { + name: FormValidation.Required, + }, + }); + return ( + + + + } + > + + + ); +} diff --git a/apps/raycast-extension/src/withSupermemory.tsx b/apps/raycast-extension/src/withSupermemory.tsx new file mode 100644 index 00000000..13c1d7b8 --- /dev/null +++ b/apps/raycast-extension/src/withSupermemory.tsx @@ -0,0 +1,48 @@ +import { usePromise } from "@raycast/utils"; +import { fetchSettings } from "./api"; +import { + Action, + ActionPanel, + Detail, + Icon, + List, + openExtensionPreferences, +} from "@raycast/api"; +import { ComponentType } from "react"; + +export function withSupermemory

(Component: ComponentType

) { + return function SupermemoryWrappedComponent(props: P) { + const { isLoading, data } = usePromise(fetchSettings, [], { + failureToastOptions: { + title: "Invalid API Key", + message: + "Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast", + }, + }); + + if (!data) { + return isLoading ? ( + + ) : ( + + + + + } + /> + + ); + } + + return ; + }; +} From b7820f90f94f9740e2fbfb5df265051f53d2890f Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommmu Date: Wed, 5 Nov 2025 15:05:16 -0800 Subject: [PATCH 2/2] add quick add from selection with extension --- apps/raycast-extension/CHANGELOG.md | 6 + apps/raycast-extension/package.json | 3 +- apps/raycast-extension/src/add-memory.tsx | 181 +++++---- .../raycast-extension/src/search-memories.tsx | 375 +++++++++--------- 4 files changed, 305 insertions(+), 260 deletions(-) diff --git a/apps/raycast-extension/CHANGELOG.md b/apps/raycast-extension/CHANGELOG.md index 0a774ad7..f8bdf596 100644 --- a/apps/raycast-extension/CHANGELOG.md +++ b/apps/raycast-extension/CHANGELOG.md @@ -1,5 +1,11 @@ # Supermemory Changelog +## [Quick Add from Selection] - 2025-11-05 + +- Select text anywhere and quickly add it as a memory - the content field is automatically filled +- Select text and search memories instantly - the search field is automatically filled +- Save time by selecting text before opening commands + ## [Search Projects + Enhancements] - 2025-10-13 - Added command to search and add projects diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json index 2d32ca52..ed821577 100644 --- a/apps/raycast-extension/package.json +++ b/apps/raycast-extension/package.json @@ -6,7 +6,8 @@ "icon": "extension-icon.png", "author": "supermemory", "contributors": [ - "xmok" + "xmok", + "maheshthedev" ], "platforms": [ "macOS", diff --git a/apps/raycast-extension/src/add-memory.tsx b/apps/raycast-extension/src/add-memory.tsx index 9b6d2a6e..812c1c4a 100644 --- a/apps/raycast-extension/src/add-memory.tsx +++ b/apps/raycast-extension/src/add-memory.tsx @@ -1,93 +1,114 @@ import { - Form, - ActionPanel, - Action, - showToast, - Toast, - useNavigation, - Icon, -} from "@raycast/api"; -import { useState } from "react"; -import { addMemory, fetchProjects } from "./api"; -import { usePromise } from "@raycast/utils"; -import { withSupermemory } from "./withSupermemory"; + Form, + ActionPanel, + Action, + showToast, + Toast, + useNavigation, + Icon, + getSelectedText, +} from "@raycast/api" +import { useState, useEffect } from "react" +import { addMemory, fetchProjects } from "./api" +import { usePromise } from "@raycast/utils" +import { withSupermemory } from "./withSupermemory" interface FormValues { - content: string; - project: string; + content: string + project: string } -export default withSupermemory(Command); +export default withSupermemory(Command) function Command() { - const [isSubmitting, setIsSubmitting] = useState(false); - const { pop } = useNavigation(); + const [isSubmitting, setIsSubmitting] = useState(false) + const [initialContent, setInitialContent] = useState("") + const { pop } = useNavigation() - const { isLoading, data: projects = [] } = usePromise(fetchProjects); - async function handleSubmit(values: FormValues) { - if (!values.content.trim()) { - await showToast({ - style: Toast.Style.Failure, - title: "Content Required", - message: "Please enter some content for the memory", - }); - return; - } + const { isLoading, data: projects = [] } = usePromise(fetchProjects) - try { - setIsSubmitting(true); + useEffect(() => { + async function loadSelectedText() { + try { + const selectedText = await getSelectedText() + if (selectedText) { + setInitialContent(selectedText) + } + } catch { + // No text selected or error getting selected text - silently fail + // User can still manually enter content + } + } - const containerTags = values.project ? [values.project] : undefined; + loadSelectedText() + }, []) - await addMemory({ - content: values.content.trim(), - containerTags, - }); + async function handleSubmit(values: FormValues) { + if (!values.content.trim()) { + await showToast({ + style: Toast.Style.Failure, + title: "Content Required", + message: "Please enter some content for the memory", + }) + return + } - pop(); - } catch (error) { - console.error("Failed to add memory:", error); - } finally { - setIsSubmitting(false); - } - } + try { + setIsSubmitting(true) - return ( -

- - - ) - } - > - - - - - {projects.map((project) => ( - - ))} - - - ); + const containerTags = values.project ? [values.project] : undefined + + await addMemory({ + content: values.content.trim(), + containerTags, + }) + + pop() + } catch (error) { + console.error("Failed to add memory:", error) + } finally { + setIsSubmitting(false) + } + } + + return ( +
+ + + ) + } + > + setInitialContent(value)} + /> + + + + {projects.map((project) => ( + + ))} + + + ) } diff --git a/apps/raycast-extension/src/search-memories.tsx b/apps/raycast-extension/src/search-memories.tsx index 846f1fd9..dbc47ceb 100644 --- a/apps/raycast-extension/src/search-memories.tsx +++ b/apps/raycast-extension/src/search-memories.tsx @@ -1,170 +1,187 @@ import { - ActionPanel, - Detail, - List, - Action, - Icon, - showToast, - Toast, -} from "@raycast/api"; -import { useState } from "react"; -import { searchMemories, type SearchResult } from "./api"; -import { usePromise } from "@raycast/utils"; -import { withSupermemory } from "./withSupermemory"; + ActionPanel, + Detail, + List, + Action, + Icon, + showToast, + Toast, + getSelectedText, +} from "@raycast/api" +import { useState, useEffect } from "react" +import { searchMemories, type SearchResult } from "./api" +import { usePromise } from "@raycast/utils" +import { withSupermemory } from "./withSupermemory" const extractContent = (memory: SearchResult) => { - if (memory.chunks && memory.chunks.length > 0) { - return memory.chunks - .map((chunk: unknown) => { - if (typeof chunk === "string") return chunk; - if ( - chunk && - typeof chunk === "object" && - "content" in chunk && - typeof chunk.content === "string" - ) - return chunk.content; - if ( - chunk && - typeof chunk === "object" && - "text" in chunk && - typeof chunk.text === "string" - ) - return chunk.text; - return ""; - }) - .filter(Boolean) - .join(" "); - } - return "No content available"; -}; + if (memory.chunks && memory.chunks.length > 0) { + return memory.chunks + .map((chunk: unknown) => { + if (typeof chunk === "string") return chunk + if ( + chunk && + typeof chunk === "object" && + "content" in chunk && + typeof chunk.content === "string" + ) + return chunk.content + if ( + chunk && + typeof chunk === "object" && + "text" in chunk && + typeof chunk.text === "string" + ) + return chunk.text + return "" + }) + .filter(Boolean) + .join(" ") + } + return "No content available" +} const extractUrl = (memory: SearchResult) => { - if (memory.metadata?.url && typeof memory.metadata.url === "string") { - return memory.metadata.url; - } - return null; -}; + if (memory.metadata?.url && typeof memory.metadata.url === "string") { + return memory.metadata.url + } + return null +} + +const truncateContent = (content: string, maxLength = 100) => { + if (content.length <= maxLength) return content + return `${content.substring(0, maxLength)}...` +} -export default withSupermemory(Command); +export default withSupermemory(Command) function Command() { - const [searchText, setSearchText] = useState(""); - - const { isLoading, data: searchResults = [] } = usePromise( - async (query: string) => { - const q = query.trim(); - if (!q) return []; - - const results = await searchMemories({ - q, - limit: 50, - }); - if (!results.length) { - await showToast({ - style: Toast.Style.Success, - title: "Search Complete", - message: "No memories found for your query", - }); - } - return results; - }, - [searchText], - ); - - const formatDate = (dateString: string) => { - try { - return new Date(dateString).toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - hour: "2-digit", - minute: "2-digit", - }); - } catch { - return "Unknown date"; - } - }; - - const truncateContent = (content: string, maxLength = 100) => { - if (content.length <= maxLength) return content; - return `${content.substring(0, maxLength)}...`; - }; - - const hasSearched = !isLoading && !searchResults.length; - return ( - - {hasSearched && !searchText.trim() ? ( - - ) : hasSearched ? ( - - ) : isLoading && searchText.trim() ? ( - - ) : ( - searchResults.map((memory) => { - const content = extractContent(memory); - const url = extractUrl(memory); - return ( - - } - icon={Icon.Eye} - /> - - {url && ( - - )} - - } - /> - ); - }) - )} - - ); + const [searchText, setSearchText] = useState("") + + useEffect(() => { + async function loadSelectedText() { + try { + const selectedText = await getSelectedText() + if (selectedText) { + setSearchText(selectedText) + } + } catch { + // No text selected or error getting selected text - silently fail + } + } + + loadSelectedText() + }, []) + + const { isLoading, data: searchResults = [] } = usePromise( + async (query: string) => { + const q = query.trim() + if (!q) return [] + + const results = await searchMemories({ + q, + limit: 50, + }) + if (!results.length) { + await showToast({ + style: Toast.Style.Success, + title: "Search Complete", + message: "No memories found for your query", + }) + } + return results + }, + [searchText], + ) + + const formatDate = (dateString: string) => { + try { + return new Date(dateString).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }) + } catch { + return "Unknown date" + } + } + + const hasSearched = !isLoading && !searchResults.length + return ( + + {hasSearched && !searchText.trim() ? ( + + ) : hasSearched ? ( + + ) : isLoading && searchText.trim() ? ( + + ) : ( + searchResults.map((memory) => { + const content = extractContent(memory) + const url = extractUrl(memory) + return ( + + } + icon={Icon.Eye} + /> + + {url && ( + + )} + + } + /> + ) + }) + )} + + ) } function MemoryDetail({ memory }: { memory: SearchResult }) { - const content = extractContent(memory); - const url = extractUrl(memory); + const content = extractContent(memory) + const url = extractUrl(memory) - const markdown = ` + const markdown = ` # ${memory.title || "Untitled Memory"} ${content} @@ -174,27 +191,27 @@ ${content} **Created:** ${new Date(memory.createdAt).toLocaleString()} ${url ? `**URL:** ${url}` : ""} ${memory.score ? `**Relevance:** ${Math.round(memory.score * 100)}%` : ""} -`; - - return ( - - - {url && ( - - )} - - } - /> - ); +` + + return ( + + + {url && ( + + )} + + } + /> + ) }