diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 00000000..5e90d4b9 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,54 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + Please review this pull request and provide feedback on: + - Code quality and best practices + - Potential bugs or issues + - Performance considerations + - Security concerns + - Test coverage + + Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. + + Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. + + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options + claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..4b2e6d2f --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options + # claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)' + diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index eafa4dcd..2118b63e 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -227,7 +227,7 @@ const MemoryGraphPage = () => { // Progressive loading via useInfiniteQuery const IS_DEV = process.env.NODE_ENV === "development"; - const PAGE_SIZE = IS_DEV ? 3 : 100; + const PAGE_SIZE = IS_DEV ? 100 : 100; const MAX_TOTAL = 1000; const { @@ -244,7 +244,7 @@ const MemoryGraphPage = () => { const response = await $fetch("@post/memories/documents", { body: { page: pageParam as number, - limit: (pageParam as number) === 1 ? (IS_DEV ? 3 : 500) : PAGE_SIZE, + limit: (pageParam as number) === 1 ? (IS_DEV ? 500 : 500) : PAGE_SIZE, sort: "createdAt", order: "desc", containerTags: selectedProject ? [selectedProject] : undefined, @@ -388,284 +388,284 @@ const MemoryGraphPage = () => { }, []); return ( -
- {/* Main content area */} - - - -
- handleViewModeChange('graph')} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === 'graph' && ( - - )} - - - Graph - - +
+ {/* Main content area */} + + + +
+ handleViewModeChange("graph")} + transition={{ duration: 0.2 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {viewMode === "graph" && ( + + )} + + + Graph + + - handleViewModeChange('list')} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === 'list' && ( - - )} - - - List - - -
-
+ handleViewModeChange("list")} + transition={{ duration: 0.2 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {viewMode === "list" && ( + + )} + + + List + + +
+ - {/* Animated content switching */} - - {viewMode === 'graph' ? ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- ) : ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- )} -
+ {/* Animated content switching */} + + {viewMode === "graph" ? ( + + +
+
+
+

+ No Memories to Visualize +

+ +
+
+
+
+
+ ) : ( + + +
+
+
+

+ No Memories to Visualize +

+ +
+
+
+
+
+ )} +
- {/* Top Bar */} -
-
- - - - + {/* Top Bar */} +
+
+ + + + -
- -
+
+ +
- - - -
+ + + +
-
- -
-
+
+ +
+
- {/* Floating Open Chat Button */} - {!isOpen && !isMobile && ( - - - - )} - + {/* Floating Open Chat Button */} + {!isOpen && !isMobile && ( + + + + )} + - {/* Chat panel - positioned absolutely */} - - - - - + {/* Chat panel - positioned absolutely */} + + + + + - {showAddMemoryView && ( - setShowAddMemoryView(false)} - /> - )} + {showAddMemoryView && ( + setShowAddMemoryView(false)} + /> + )} - {/* Tour Alert Dialog */} - + {/* Tour Alert Dialog */} + - {/* Referral/Upgrade Modal */} - setShowReferralModal(false)} - /> -
- ); + {/* Referral/Upgrade Modal */} + setShowReferralModal(false)} + /> +
+ ); }; // Wrapper component to handle auth and waitlist checks @@ -673,11 +673,7 @@ export default function Page() { const router = useRouter(); const { user } = useAuth(); - // Check waitlist status - const { - data: waitlistStatus, - isLoading: isCheckingWaitlist, - } = useQuery({ + const { data: waitlistStatus, isLoading: isCheckingWaitlist } = useQuery({ queryKey: ["waitlist-status", user?.id], queryFn: async () => { try { diff --git a/apps/web/biome.json b/apps/web/biome.json index ea994ee6..48649190 100644 --- a/apps/web/biome.json +++ b/apps/web/biome.json @@ -1,6 +1,7 @@ { "root": false, - "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", + "extends": "//", + "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", "linter": { "rules": { "nursery": { @@ -8,4 +9,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/components/menu.tsx b/apps/web/components/menu.tsx index 926b2a7a..f59617c0 100644 --- a/apps/web/components/menu.tsx +++ b/apps/web/components/menu.tsx @@ -159,7 +159,7 @@ function Menu({ id }: { id?: string }) { }, [isMobile, isMobileMenuOpen, isHovered, expandedView, setMenuExpanded]); // Calculate width based on state - const menuWidth = expandedView || isCollapsing ? 600 : isHovered ? 220 : 56; + const menuWidth = expandedView || isCollapsing ? 600 : isHovered ? 160 : 56; // Dynamic z-index for mobile based on active panel const mobileZIndex = @@ -283,7 +283,7 @@ function Menu({ id }: { id?: string }) { opacity: isHovered ? 1 : 0, x: isHovered ? 0 : -10, }} - className="drop-shadow-lg absolute left-10 right-16 whitespace-nowrap" + className="drop-shadow-lg pl-3 whitespace-nowrap" initial={{ opacity: 0, x: -10 }} style={{ transform: "translateZ(0)", @@ -296,20 +296,6 @@ function Menu({ id }: { id?: string }) { > {item.text} - {shouldShowLimitWarning && item.key === "addUrl" && ( - - {memoriesLimit - memoriesUsed} left - - )} {index === 0 && ( ) { const [isAutoScroll, setIsAutoScroll] = useState(true); const [isFarFromBottom, setIsFarFromBottom] = useState(false); - function scrollToBottom(behavior: ScrollBehavior = "auto") { + const scrollToBottom = (behavior: ScrollBehavior = "auto") => { const node = bottomRef.current; if (node) node.scrollIntoView({ behavior, block: "end" }); - } + }; useEffect(function observeBottomVisibility() { const container = scrollContainerRef.current; @@ -67,20 +66,20 @@ function useStickyAutoScroll(triggerKeys: ReadonlyArray) { function autoScrollOnNewContent() { if (isAutoScroll) scrollToBottom("auto"); }, - [isAutoScroll, ...triggerKeys], + [isAutoScroll, scrollToBottom, ...triggerKeys], ); - function recomputeDistanceFromBottom() { + const recomputeDistanceFromBottom = () => { const container = scrollContainerRef.current; if (!container) return; const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; setIsFarFromBottom(distanceFromBottom > 100); - } + }; useEffect(() => { recomputeDistanceFromBottom(); - }, [...triggerKeys]); + }, [recomputeDistanceFromBottom, ...triggerKeys]); function onScroll() { recomputeDistanceFromBottom(); @@ -154,7 +153,6 @@ export function ChatMessages() { const msgs = getCurrentConversation(); setMessages(msgs ?? []); setInput(""); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentChatId]); useEffect(() => { @@ -208,7 +206,7 @@ export function ChatMessages() { currentSummary?.title && currentSummary.title.trim().length > 0, ); shouldGenerateTitleRef.current = !hasTitle; - }, [currentChatId, id, getCurrentChat]); + }, [getCurrentChat]); const { scrollContainerRef, bottomRef, @@ -222,17 +220,17 @@ export function ChatMessages() { <>
{messages.map((message) => (
{message.parts @@ -241,27 +239,22 @@ export function ChatMessages() { part.type, ), ) - .map((part, index) => { + .map((part) => { switch (part.type) { case "text": return ( -
- - {(part as any).text} - +
+ {part.text}
); - case "tool-searchMemories": + case "tool-searchMemories": { switch (part.state) { case "input-available": case "input-streaming": return (
Searching memories... @@ -270,44 +263,42 @@ export function ChatMessages() { case "output-error": return (
Error recalling memories
); case "output-available": { - const output = (part as any).output; + const output = part.output; const foundCount = typeof output === "object" && output !== null && "count" in output ? Number(output.count) || 0 : 0; - const ids = Array.isArray(output?.results) - ? ((output.results as any[]) - .map((r) => r?.documentId) - .filter(Boolean) as string[]) - : []; return (
Found {foundCount}{" "} memories
); } + default: + return null; } - case "tool-addMemory": + } + case "tool-addMemory": { switch (part.state) { case "input-available": return (
Adding memory...
@@ -315,8 +306,8 @@ export function ChatMessages() { case "output-error": return (
Error adding memory
@@ -324,8 +315,8 @@ export function ChatMessages() { case "output-available": return (
Memory added
@@ -333,23 +324,24 @@ export function ChatMessages() { case "input-streaming": return (
Adding memory...
); + default: + return null; } + } + default: + return null; } - - return null; })}
{message.role === "assistant" && (
@@ -387,11 +381,6 @@ export function ChatMessages() {
@@ -426,12 +420,12 @@ export function ChatMessages() {
setInput(e.target.value)} disabled={status === "submitted"} + onChange={(e) => setInput(e.target.value)} placeholder="Say something..." + value={input} /> -
); diff --git a/packages/ui/memory-graph/navigation-controls.tsx b/packages/ui/memory-graph/navigation-controls.tsx new file mode 100644 index 00000000..b2abd67f --- /dev/null +++ b/packages/ui/memory-graph/navigation-controls.tsx @@ -0,0 +1,67 @@ +"use client" + +import { memo } from "react" +import type { GraphNode } from "./types" + +interface NavigationControlsProps { + onCenter: () => void + onZoomIn: () => void + onZoomOut: () => void + onAutoFit: () => void + nodes: GraphNode[] + className?: string +} + +export const NavigationControls = memo(({ + onCenter, + onZoomIn, + onZoomOut, + onAutoFit, + nodes, + className = "", +}) => { + if (nodes.length === 0) { + return null + } + + return ( +
+ + +
+ + +
+
+ ) +}) + +NavigationControls.displayName = "NavigationControls" \ No newline at end of file diff --git a/packages/ui/memory-graph/types.ts b/packages/ui/memory-graph/types.ts index f1af3ac2..4692d2c0 100644 --- a/packages/ui/memory-graph/types.ts +++ b/packages/ui/memory-graph/types.ts @@ -68,6 +68,9 @@ export interface GraphCanvasProps { onPanEnd: () => void; onWheel: (e: React.WheelEvent) => void; onDoubleClick: (e: React.MouseEvent) => void; + onTouchStart?: (e: React.TouchEvent) => void; + onTouchMove?: (e: React.TouchEvent) => void; + onTouchEnd?: (e: React.TouchEvent) => void; draggingNodeId: string | null; // Optional list of document IDs (customId or internal id) to highlight highlightDocumentIds?: string[];