diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 000000000..5e90d4b9f --- /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 000000000..4b2e6d2f5 --- /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/biome.json b/apps/web/biome.json index ea994ee62..48649190e 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/views/chat/chat-messages.tsx b/apps/web/components/views/chat/chat-messages.tsx index ab228ce34..cff7e1b78 100644 --- a/apps/web/components/views/chat/chat-messages.tsx +++ b/apps/web/components/views/chat/chat-messages.tsx @@ -7,9 +7,8 @@ import { Input } from "@ui/components/input"; import { DefaultChatTransport } from "ai"; import { ArrowUp, Check, Copy, RotateCcw, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; import { toast } from "sonner"; +import { Streamdown } from "streamdown"; import { TextShimmer } from "@/components/text-shimmer"; import { usePersistentChat, useProject } from "@/stores"; import { useGraphHighlights } from "@/stores/highlights"; @@ -21,10 +20,10 @@ function useStickyAutoScroll(triggerKeys: ReadonlyArray) { 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} /> -