Compare commits

..

11 Commits

Author SHA1 Message Date
dayuan.jiang
476a632d1b fix: update @ai-sdk/amazon-bedrock to 3.0.62 for tool streaming support 2025-11-30 16:32:40 +09:00
dayuan.jiang
0cba721d33 Merge branch 'main' into feat/tool-streaming 2025-11-30 16:17:40 +09:00
dayuan.jiang
de910bdf6c fix: correct anthropic beta header config for fine-grained tool streaming
- Use bedrock.anthropicBeta for Bedrock provider (not additionalModelRequestFields)
- Use top-level headers for direct Anthropic API
- Update @ai-sdk/amazon-bedrock to 3.0.62
- Add headers support to ModelConfig interface
2025-11-30 16:12:12 +09:00
Ming long Hu
6e6de1eba6 feat: add copy button to user messages in chat sidebar (#21)
* Initial plan

* Initial plan for adding copy button to user messages

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* Add copy button to user messages in chat sidebar

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* Refactor: extract userMessageText to avoid duplicate function calls

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* feat(ui): 增加消息复制功能支持非 HTTPS 环境

优化复制消息到剪贴板的逻辑,增加对非 HTTPS 环境的降级处理,提升用户体验。

* fix(package): 添加 peer 属性以支持依赖关系

* chore(.gitignore): 添加 next 和 next-ai-draw-io@0.2.0 相关条目

* fix: improve copy button implementation

- Fix copy button alignment (place left of user message)
- Remove unused React import
- Move getMessageTextContent outside component
- Add error state with X icon for copy failures
- Translate Chinese comments to English
- Simplify copy function
- Add missing semicolon
- Remove accidental .gitignore entries

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2025-11-29 13:39:35 +09:00
dayuan.jiang
00af87edbe fix: prevent duplicate PR review comments by tracking existing file:line 2025-11-29 12:55:53 +09:00
dayuan.jiang
468d6c0276 fix: require suggested fixes in PR review comments 2025-11-29 12:46:24 +09:00
dayuan.jiang
14f74b076f fix: remove gh pr comment from allowed tools to force inline comments only 2025-11-29 12:38:35 +09:00
dayuan.jiang
78d9229ca3 fix: remove gh pr comment from allowed tools to force inline comments only 2025-11-29 12:36:35 +09:00
dayuan.jiang
d8e0a1daad fix: enforce inline comments for all issues found
- Explicitly require mcp inline comment tool for each issue
- Clarify gh pr comment is only for final summary
- Forbid dismissing issues as minor/harmless
2025-11-29 12:31:46 +09:00
Dayuan Jiang
32e75ab556 feat: expand PR review scope to catch more issues (#25)
* feat: use pull_request_target to support fork PR reviews

* feat: expand PR review scope to catch more issues

- Add categories for scope creep, suspicious .gitignore additions, UI inconsistencies
- Change from 'be very selective' to 'report ALL issues found'
- Simplify DO NOT comment list to allow more actionable feedback
2025-11-29 12:22:21 +09:00
dayuan.jiang
b758f63d7f feat: use pull_request_target to support fork PR reviews 2025-11-29 11:09:51 +09:00
8 changed files with 4790 additions and 116 deletions

6
.eslintrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

View File

@@ -74,11 +74,7 @@ jobs:
This is a personal project - an AI-powered draw.io diagram generator built with:
- Next.js 15 with React 19
- Multiple AI providers: Bedrock, Anthropic, OpenAI, Google, Azure, OpenRouter, Ollama
First, check previous review comments from github-actions bot using `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments`.
For each previous comment:
- If the issue is fixed in the current code, resolve the comment thread using:
- Vercel AI SDK (streamText, useChat, tool calling)
- Multiple AI providers: Bedrock, Anthropic, OpenAI, Google, Azure, OpenRouter, Ollama
STEP 1: Check existing comments to avoid duplicates.

5
.gitignore vendored
View File

@@ -41,8 +41,3 @@ yarn-error.log*
next-env.d.ts
push-via-ec2.sh
.claude/settings.local.json
next
next-ai-draw-io@0.2.0
object
starting

View File

@@ -120,13 +120,14 @@ ${lastMessageText}
console.log("Enhanced messages:", enhancedMessages);
// Get AI model from environment configuration
const { model, providerOptions } = getAIModel();
const { model, providerOptions, headers } = getAIModel();
const result = streamText({
model,
system: systemMessage,
messages: enhancedMessages,
...(providerOptions && { providerOptions }),
...(headers && { headers }),
tools: {
// Client-side tool that will be executed on the client
display_diagram: {

View File

@@ -1,16 +1,23 @@
"use client";
import type React from "react";
import { useRef, useEffect, useState, useCallback } from "react";
import Image from "next/image";
import { ScrollArea } from "@/components/ui/scroll-area";
import ExamplePanel from "./chat-example-panel";
import { UIMessage } from "ai";
import { convertToLegalXml, replaceNodes } from "@/lib/utils";
import { Copy, Check } from "lucide-react";
import { Copy, Check, X } from "lucide-react";
import { useDiagram } from "@/contexts/diagram-context";
const getMessageTextContent = (message: UIMessage): string => {
if (!message.parts) return "";
return message.parts
.filter((part: any) => part.type === "text")
.map((part: any) => part.text)
.join("\n");
};
interface ChatMessageDisplayProps {
messages: UIMessage[];
error?: Error | null;
@@ -32,50 +39,20 @@ export function ChatMessageDisplay({
{}
);
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
const [copyFailedMessageId, setCopyFailedMessageId] = useState<string | null>(null);
// 复制消息到剪贴板,支持非 HTTPS 环境的降级处理
const copyMessageToClipboard = async (messageId: string, text: string) => {
try {
// 优先使用 Clipboard API需要 HTTPS 或 localhost
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
// 降级方案:使用传统的 execCommand 方法(兼容 HTTP 环境)
const textArea = document.createElement("textarea");
textArea.value = text;
// 设置样式避免影响页面布局
textArea.style.position = "fixed";
// 降级方案:使用传统的 execCommand 方法(兼容 HTTP 环境)
// Fallback: Use textarea selection (works in most browsers)
textArea.style.top = "-9999px";
textArea.style.opacity = "0";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand("copy");
document.body.removeChild(textArea);
if (!successful) {
throw new Error("execCommand copy failed");
}
}
await navigator.clipboard.writeText(text);
setCopiedMessageId(messageId);
setTimeout(() => {
setCopiedMessageId(null);
}, 2000);
setTimeout(() => setCopiedMessageId(null), 2000);
} catch (err) {
console.error("Failed to copy message:", err);
setCopyFailedMessageId(messageId);
setTimeout(() => setCopyFailedMessageId(null), 2000);
}
};
const getMessageTextContent = (message: UIMessage): string => {
if (!message.parts) return "";
return message.parts
.filter((part: any) => part.type === "text")
.map((part: any) => part.text)
.join("\n");
};
const handleDisplayChart = useCallback(
(xml: string) => {
const currentXml = xml || "";
@@ -183,16 +160,16 @@ export function ChatMessageDisplay({
{output || (toolName === "display_diagram"
? "Diagram generated"
: toolName === "edit_diagram"
? "Diagram edited"
: "Tool executed")}
? "Diagram edited"
: "Tool executed")}
</div>
) : state === "output-error" ? (
<div className="text-red-600">
{output || (toolName === "display_diagram"
? "Error generating diagram"
: toolName === "edit_diagram"
? "Error editing diagram"
: "Tool error")}
? "Error editing diagram"
: "Tool error")}
</div>
) : null}
</div>
@@ -211,11 +188,25 @@ export function ChatMessageDisplay({
return (
<div
key={message.id}
className={`mb-4 ${message.role === "user" ? "text-right" : "text-left"
}`}
className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
>
{message.role === "user" && userMessageText && (
<button
onClick={() => copyMessageToClipboard(message.id, userMessageText)}
className="p-1 text-gray-400 hover:text-gray-600 transition-colors self-center mr-1"
title={copiedMessageId === message.id ? "Copied!" : copyFailedMessageId === message.id ? "Failed to copy" : "Copy message"}
>
{copiedMessageId === message.id ? (
<Check className="h-3.5 w-3.5 text-green-500" />
) : copyFailedMessageId === message.id ? (
<X className="h-3.5 w-3.5 text-red-500" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
)}
<div
className={`inline-block px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${message.role === "user"
className={`px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${message.role === "user"
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground"
}`}
@@ -249,23 +240,8 @@ export function ChatMessageDisplay({
}
})}
</div>
{userMessageText && (
<div className="flex justify-start mt-1">
<button
onClick={() => copyMessageToClipboard(message.id, userMessageText)}
className="p-1 text-gray-400 hover:text-gray-600 transition-colors"
title={copiedMessageId === message.id ? "Copied!" : "Copy message"}
>
{copiedMessageId === message.id ? (
<Check className="h-3.5 w-3.5 text-green-500" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
</div>
)}
</div>
)
);
})
)}
{error && (

View File

@@ -18,15 +18,19 @@ export type ProviderName =
interface ModelConfig {
model: any;
providerOptions?: any;
headers?: Record<string, string>;
}
// Anthropic beta headers for fine-grained tool streaming
const ANTHROPIC_BETA_OPTIONS = {
anthropic: {
additionalModelRequestFields: {
anthropic_beta: ['fine-grained-tool-streaming-2025-05-14']
}
}
// Bedrock provider options for Anthropic beta features
const BEDROCK_ANTHROPIC_BETA = {
bedrock: {
anthropicBeta: ['fine-grained-tool-streaming-2025-05-14'],
},
};
// Direct Anthropic API headers for beta features
const ANTHROPIC_BETA_HEADERS = {
'anthropic-beta': 'fine-grained-tool-streaming-2025-05-14',
};
/**
@@ -87,13 +91,14 @@ export function getAIModel(): ModelConfig {
let model: any;
let providerOptions: any = undefined;
let headers: Record<string, string> | undefined = undefined;
switch (provider) {
case 'bedrock':
model = bedrock(modelId);
// Add Anthropic beta headers if using Claude models via Bedrock
// Add Anthropic beta options if using Claude models via Bedrock
if (modelId.includes('anthropic.claude')) {
providerOptions = ANTHROPIC_BETA_OPTIONS;
providerOptions = BEDROCK_ANTHROPIC_BETA;
}
break;
@@ -112,7 +117,7 @@ export function getAIModel(): ModelConfig {
case 'anthropic':
model = anthropic(modelId);
// Add beta headers for fine-grained tool streaming
providerOptions = ANTHROPIC_BETA_OPTIONS;
headers = ANTHROPIC_BETA_HEADERS;
break;
case 'google':
@@ -140,10 +145,10 @@ export function getAIModel(): ModelConfig {
);
}
// Log if provider options are being applied
if (providerOptions) {
console.log('[AI Provider] Applying provider-specific options');
// Log if provider options or headers are being applied
if (providerOptions || headers) {
console.log('[AI Provider] Applying provider-specific options/headers');
}
return { model, providerOptions };
return { model, providerOptions, headers };
}

4753
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.52",
"@ai-sdk/amazon-bedrock": "^3.0.62",
"@ai-sdk/anthropic": "^2.0.44",
"@ai-sdk/azure": "^2.0.69",
"@ai-sdk/google": "^2.0.0",
@@ -46,6 +46,8 @@
"@types/pako": "^2.0.3",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "9.39.1",
"eslint-config-next": "16.0.5",
"tailwindcss": "^4",
"typescript": "^5"
}