2025-12-06 12:46:40 +09:00
|
|
|
"use client"
|
2025-03-25 02:58:11 +00:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
import type { UIMessage } from "ai"
|
2025-12-06 16:18:26 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
import {
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
BookmarkPlus,
|
2025-12-06 12:46:40 +09:00
|
|
|
Check,
|
|
|
|
|
ChevronDown,
|
|
|
|
|
ChevronUp,
|
|
|
|
|
Copy,
|
2025-12-10 21:32:35 +09:00
|
|
|
FileCode,
|
|
|
|
|
FileText,
|
2026-01-24 07:14:11 +05:30
|
|
|
Link,
|
2025-12-06 12:46:40 +09:00
|
|
|
Pencil,
|
|
|
|
|
RotateCcw,
|
|
|
|
|
ThumbsDown,
|
|
|
|
|
ThumbsUp,
|
|
|
|
|
X,
|
|
|
|
|
} from "lucide-react"
|
2025-12-11 17:18:48 +05:30
|
|
|
import type { MutableRefObject } from "react"
|
2025-12-06 12:46:40 +09:00
|
|
|
import { useCallback, useEffect, useRef, useState } from "react"
|
|
|
|
|
import ReactMarkdown from "react-markdown"
|
2025-12-12 14:06:53 +09:00
|
|
|
import { toast } from "sonner"
|
2025-12-10 20:54:43 +05:30
|
|
|
import {
|
|
|
|
|
Reasoning,
|
|
|
|
|
ReasoningContent,
|
|
|
|
|
ReasoningTrigger,
|
|
|
|
|
} from "@/components/ai-elements/reasoning"
|
2026-01-04 12:04:06 +09:00
|
|
|
import { ChatLobby } from "@/components/chat/ChatLobby"
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
import { TemplateCreateDialog } from "@/components/chat/TemplateCreateDialog"
|
2026-01-04 12:04:06 +09:00
|
|
|
import { ToolCallCard } from "@/components/chat/ToolCallCard"
|
|
|
|
|
import type { DiagramOperation, ToolPartLike } from "@/components/chat/types"
|
2026-01-20 20:52:04 +09:00
|
|
|
import type { ValidationState } from "@/components/chat/ValidationCard"
|
|
|
|
|
import { ValidationCard } from "@/components/chat/ValidationCard"
|
2026-01-28 17:57:07 +08:00
|
|
|
import Image from "@/components/image-with-basepath"
|
2025-12-06 12:46:40 +09:00
|
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
2025-12-30 19:52:57 +08:00
|
|
|
import { useDictionary } from "@/hooks/use-dictionary"
|
2025-12-22 19:58:55 +05:30
|
|
|
import { getApiEndpoint } from "@/lib/base-path"
|
2025-12-14 14:04:44 +09:00
|
|
|
import {
|
2025-12-15 21:28:31 +09:00
|
|
|
applyDiagramOperations,
|
2025-12-14 14:04:44 +09:00
|
|
|
convertToLegalXml,
|
2025-12-23 18:54:03 +09:00
|
|
|
extractCompleteMxCells,
|
2025-12-14 14:04:44 +09:00
|
|
|
replaceNodes,
|
|
|
|
|
validateAndFixXml,
|
|
|
|
|
} from "@/lib/utils"
|
2025-12-03 21:49:34 +09:00
|
|
|
|
2025-12-15 21:28:31 +09:00
|
|
|
// Helper to extract complete operations from streaming input
|
|
|
|
|
function getCompleteOperations(
|
|
|
|
|
operations: DiagramOperation[] | undefined,
|
|
|
|
|
): DiagramOperation[] {
|
|
|
|
|
if (!operations || !Array.isArray(operations)) return []
|
|
|
|
|
return operations.filter(
|
|
|
|
|
(op) =>
|
|
|
|
|
op &&
|
2025-12-25 13:19:04 +09:00
|
|
|
typeof op.operation === "string" &&
|
|
|
|
|
["update", "add", "delete"].includes(op.operation) &&
|
2025-12-15 21:28:31 +09:00
|
|
|
typeof op.cell_id === "string" &&
|
|
|
|
|
op.cell_id.length > 0 &&
|
2025-12-25 13:19:04 +09:00
|
|
|
(op.operation === "delete" || typeof op.new_xml === "string"),
|
2025-12-15 21:28:31 +09:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
import { useDiagram } from "@/contexts/diagram-context"
|
2025-03-26 00:30:00 +00:00
|
|
|
|
2026-01-24 07:14:11 +05:30
|
|
|
// Helper to split text content into regular text and file/URL sections (PDF, text files, or URLs)
|
2025-12-10 21:32:35 +09:00
|
|
|
interface TextSection {
|
2026-01-24 07:14:11 +05:30
|
|
|
type: "text" | "file" | "url"
|
2025-12-10 21:32:35 +09:00
|
|
|
content: string
|
|
|
|
|
filename?: string
|
|
|
|
|
charCount?: number
|
2026-01-24 07:14:11 +05:30
|
|
|
fileType?: "pdf" | "text" | "url"
|
2025-12-10 21:32:35 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function splitTextIntoFileSections(text: string): TextSection[] {
|
|
|
|
|
const sections: TextSection[] = []
|
2026-01-24 07:14:11 +05:30
|
|
|
// Match [PDF: filename], [File: filename], or [URL: url] patterns
|
2025-12-10 21:32:35 +09:00
|
|
|
const filePattern =
|
2026-01-24 07:14:11 +05:30
|
|
|
/\[(PDF|File|URL):\s*([^\]]+)\]\n([\s\S]*?)(?=\n\n\[(PDF|File|URL):|$)/g
|
2025-12-10 21:32:35 +09:00
|
|
|
let lastIndex = 0
|
|
|
|
|
let match
|
|
|
|
|
|
|
|
|
|
while ((match = filePattern.exec(text)) !== null) {
|
|
|
|
|
// Add text before this file section
|
|
|
|
|
const beforeText = text.slice(lastIndex, match.index).trim()
|
|
|
|
|
if (beforeText) {
|
|
|
|
|
sections.push({ type: "text", content: beforeText })
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 07:14:11 +05:30
|
|
|
// Add file/url section
|
|
|
|
|
const sectionType = match[1].toLowerCase()
|
|
|
|
|
const fileType =
|
|
|
|
|
sectionType === "pdf"
|
|
|
|
|
? "pdf"
|
|
|
|
|
: sectionType === "url"
|
|
|
|
|
? "url"
|
|
|
|
|
: "text"
|
2025-12-10 21:32:35 +09:00
|
|
|
const filename = match[2].trim()
|
2026-01-24 07:14:11 +05:30
|
|
|
const content = match[3].trim()
|
2025-12-10 21:32:35 +09:00
|
|
|
sections.push({
|
2026-01-24 07:14:11 +05:30
|
|
|
type: sectionType === "url" ? "url" : "file",
|
|
|
|
|
content: content,
|
2025-12-10 21:32:35 +09:00
|
|
|
filename,
|
2026-01-24 07:14:11 +05:30
|
|
|
charCount: content.length,
|
2025-12-10 21:32:35 +09:00
|
|
|
fileType,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
lastIndex = match.index + match[0].length
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 07:14:11 +05:30
|
|
|
// Add remaining text after last section
|
2025-12-10 21:32:35 +09:00
|
|
|
const remainingText = text.slice(lastIndex).trim()
|
|
|
|
|
if (remainingText) {
|
|
|
|
|
sections.push({ type: "text", content: remainingText })
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 07:14:11 +05:30
|
|
|
// If no file/url sections found, return original text
|
2025-12-10 21:32:35 +09:00
|
|
|
if (sections.length === 0) {
|
|
|
|
|
sections.push({ type: "text", content: text })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sections
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 12:39:35 +08:00
|
|
|
const getMessageTextContent = (message: UIMessage): string => {
|
2025-12-06 12:46:40 +09:00
|
|
|
if (!message.parts) return ""
|
2025-11-29 12:39:35 +08:00
|
|
|
return message.parts
|
2025-12-06 16:18:26 +09:00
|
|
|
.filter((part) => part.type === "text")
|
|
|
|
|
.map((part) => (part as { text: string }).text)
|
2025-12-06 12:46:40 +09:00
|
|
|
.join("\n")
|
|
|
|
|
}
|
2025-11-29 12:39:35 +08:00
|
|
|
|
2025-12-10 21:32:35 +09:00
|
|
|
// Get only the user's original text, excluding appended file content
|
|
|
|
|
const getUserOriginalText = (message: UIMessage): string => {
|
|
|
|
|
const fullText = getMessageTextContent(message)
|
2026-01-24 07:14:11 +05:30
|
|
|
// Strip out [PDF: ...], [File: ...], and [URL: ...] sections that were appended
|
|
|
|
|
const filePattern = /\n\n\[(PDF|File|URL):\s*[^\]]+\]\n[\s\S]*$/
|
2025-12-10 21:32:35 +09:00
|
|
|
return fullText.replace(filePattern, "").trim()
|
|
|
|
|
}
|
|
|
|
|
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
interface SessionMetadata {
|
|
|
|
|
id: string
|
|
|
|
|
title: string
|
|
|
|
|
updatedAt: number
|
|
|
|
|
thumbnailDataUrl?: string
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-25 02:58:11 +00:00
|
|
|
interface ChatMessageDisplayProps {
|
2025-12-06 12:46:40 +09:00
|
|
|
messages: UIMessage[]
|
|
|
|
|
setInput: (input: string) => void
|
|
|
|
|
setFiles: (files: File[]) => void
|
2025-12-11 17:18:48 +05:30
|
|
|
processedToolCallsRef: MutableRefObject<Set<string>>
|
2025-12-15 21:28:31 +09:00
|
|
|
editDiagramOriginalXmlRef: MutableRefObject<Map<string, string>>
|
2025-12-06 12:46:40 +09:00
|
|
|
sessionId?: string
|
|
|
|
|
onRegenerate?: (messageIndex: number) => void
|
|
|
|
|
onEditMessage?: (messageIndex: number, newText: string) => void
|
2025-12-10 20:54:43 +05:30
|
|
|
status?: "streaming" | "submitted" | "idle" | "error" | "ready"
|
2026-01-01 15:42:48 +09:00
|
|
|
isRestored?: boolean
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
sessions?: SessionMetadata[]
|
|
|
|
|
onSelectSession?: (id: string) => void
|
|
|
|
|
onDeleteSession?: (id: string) => void
|
|
|
|
|
loadedMessageIdsRef?: MutableRefObject<Set<string>>
|
2026-01-20 20:52:04 +09:00
|
|
|
validationStates?: Record<string, ValidationState>
|
|
|
|
|
onImproveWithSuggestions?: (feedback: string) => void
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
onSendTemplate?: (
|
|
|
|
|
template: import("@/lib/template-storage").Template,
|
|
|
|
|
) => void
|
|
|
|
|
currentInput?: string
|
2025-03-25 02:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ChatMessageDisplay({
|
|
|
|
|
messages,
|
|
|
|
|
setInput,
|
|
|
|
|
setFiles,
|
2025-12-11 17:18:48 +05:30
|
|
|
processedToolCallsRef,
|
2025-12-15 21:28:31 +09:00
|
|
|
editDiagramOriginalXmlRef,
|
2025-12-05 21:15:02 +09:00
|
|
|
sessionId,
|
2025-12-04 22:56:59 +09:00
|
|
|
onRegenerate,
|
|
|
|
|
onEditMessage,
|
2025-12-10 20:54:43 +05:30
|
|
|
status = "idle",
|
2026-01-01 15:42:48 +09:00
|
|
|
isRestored = false,
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
sessions = [],
|
|
|
|
|
onSelectSession,
|
|
|
|
|
onDeleteSession,
|
|
|
|
|
loadedMessageIdsRef,
|
2026-01-20 20:52:04 +09:00
|
|
|
validationStates = {},
|
|
|
|
|
onImproveWithSuggestions,
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
onSendTemplate,
|
|
|
|
|
currentInput = "",
|
2025-03-25 02:58:11 +00:00
|
|
|
}: ChatMessageDisplayProps) {
|
2025-12-30 19:52:57 +08:00
|
|
|
const dict = useDictionary()
|
2025-12-06 12:46:40 +09:00
|
|
|
const { chartXML, loadDiagram: onDisplayChart } = useDiagram()
|
|
|
|
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
const scrollTopRef = useRef<HTMLDivElement>(null)
|
2025-12-06 12:46:40 +09:00
|
|
|
const previousXML = useRef<string>("")
|
2025-12-11 17:18:48 +05:30
|
|
|
const processedToolCalls = processedToolCallsRef
|
2025-12-14 21:23:14 +09:00
|
|
|
// Track the last processed XML per toolCallId to skip redundant processing during streaming
|
|
|
|
|
const lastProcessedXmlRef = useRef<Map<string, string>>(new Map())
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
|
|
|
|
|
// Reset refs when messages become empty (new chat or session switch)
|
|
|
|
|
// This ensures cached examples work correctly after starting a new session
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (messages.length === 0) {
|
|
|
|
|
previousXML.current = ""
|
|
|
|
|
lastProcessedXmlRef.current.clear()
|
|
|
|
|
// Note: processedToolCalls is passed from parent, so we clear it too
|
|
|
|
|
processedToolCalls.current.clear()
|
|
|
|
|
// Scroll to top to show newest history items
|
|
|
|
|
scrollTopRef.current?.scrollIntoView({ behavior: "instant" })
|
|
|
|
|
}
|
|
|
|
|
}, [messages.length, processedToolCalls])
|
2025-12-14 21:23:14 +09:00
|
|
|
// Debounce streaming diagram updates - store pending XML and timeout
|
|
|
|
|
const pendingXmlRef = useRef<string | null>(null)
|
|
|
|
|
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
const STREAMING_DEBOUNCE_MS = 150 // Only update diagram every 150ms during streaming
|
2025-12-15 21:28:31 +09:00
|
|
|
// Refs for edit_diagram streaming
|
|
|
|
|
const pendingEditRef = useRef<{
|
|
|
|
|
operations: DiagramOperation[]
|
|
|
|
|
toolCallId: string
|
|
|
|
|
} | null>(null)
|
|
|
|
|
const editDebounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
|
|
|
null,
|
|
|
|
|
)
|
2025-04-04 02:29:33 +00:00
|
|
|
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>(
|
2025-12-06 12:46:40 +09:00
|
|
|
{},
|
|
|
|
|
)
|
2025-12-30 22:45:50 +08:00
|
|
|
const [copiedToolCallId, setCopiedToolCallId] = useState<string | null>(
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
const [copyFailedToolCallId, setCopyFailedToolCallId] = useState<
|
|
|
|
|
string | null
|
|
|
|
|
>(null)
|
2025-12-06 12:46:40 +09:00
|
|
|
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null)
|
|
|
|
|
const [copyFailedMessageId, setCopyFailedMessageId] = useState<
|
|
|
|
|
string | null
|
|
|
|
|
>(null)
|
|
|
|
|
const [feedback, setFeedback] = useState<Record<string, "good" | "bad">>({})
|
|
|
|
|
const [editingMessageId, setEditingMessageId] = useState<string | null>(
|
|
|
|
|
null,
|
|
|
|
|
)
|
2025-12-06 16:18:26 +09:00
|
|
|
const editTextareaRef = useRef<HTMLTextAreaElement>(null)
|
2025-12-06 12:46:40 +09:00
|
|
|
const [editText, setEditText] = useState<string>("")
|
2025-12-10 21:32:35 +09:00
|
|
|
// Track which PDF sections are expanded (key: messageId-sectionIndex)
|
|
|
|
|
const [expandedPdfSections, setExpandedPdfSections] = useState<
|
|
|
|
|
Record<string, boolean>
|
|
|
|
|
>({})
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
// Track "Save as Template" dialog
|
|
|
|
|
const [saveAsTemplateMessageId, setSaveAsTemplateMessageId] = useState<
|
|
|
|
|
string | null
|
|
|
|
|
>(null)
|
2025-11-29 12:39:35 +08:00
|
|
|
|
2025-12-30 22:45:50 +08:00
|
|
|
const setCopyState = (
|
|
|
|
|
messageId: string,
|
|
|
|
|
isToolCall: boolean,
|
|
|
|
|
isSuccess: boolean,
|
|
|
|
|
) => {
|
|
|
|
|
if (isSuccess) {
|
|
|
|
|
if (isToolCall) {
|
|
|
|
|
setCopiedToolCallId(messageId)
|
|
|
|
|
setTimeout(() => setCopiedToolCallId(null), 2000)
|
|
|
|
|
} else {
|
|
|
|
|
setCopiedMessageId(messageId)
|
|
|
|
|
setTimeout(() => setCopiedMessageId(null), 2000)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (isToolCall) {
|
|
|
|
|
setCopyFailedToolCallId(messageId)
|
|
|
|
|
setTimeout(() => setCopyFailedToolCallId(null), 2000)
|
|
|
|
|
} else {
|
|
|
|
|
setCopyFailedMessageId(messageId)
|
|
|
|
|
setTimeout(() => setCopyFailedMessageId(null), 2000)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const copyMessageToClipboard = async (
|
|
|
|
|
messageId: string,
|
|
|
|
|
text: string,
|
|
|
|
|
isToolCall = false,
|
|
|
|
|
) => {
|
2025-11-29 12:39:35 +08:00
|
|
|
try {
|
2025-12-06 12:46:40 +09:00
|
|
|
await navigator.clipboard.writeText(text)
|
2025-12-30 22:45:50 +08:00
|
|
|
setCopyState(messageId, isToolCall, true)
|
2026-01-01 14:45:46 +09:00
|
|
|
} catch (_err) {
|
2025-12-11 20:09:42 +08:00
|
|
|
// Fallback for non-secure contexts (HTTP) or permission denied
|
|
|
|
|
const textarea = document.createElement("textarea")
|
|
|
|
|
textarea.value = text
|
|
|
|
|
textarea.style.position = "fixed"
|
|
|
|
|
textarea.style.left = "-9999px"
|
|
|
|
|
textarea.style.opacity = "0"
|
|
|
|
|
document.body.appendChild(textarea)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
textarea.select()
|
|
|
|
|
const success = document.execCommand("copy")
|
|
|
|
|
if (!success) {
|
|
|
|
|
throw new Error("Copy command failed")
|
|
|
|
|
}
|
2025-12-30 22:45:50 +08:00
|
|
|
setCopyState(messageId, isToolCall, true)
|
2025-12-11 20:09:42 +08:00
|
|
|
} catch (fallbackErr) {
|
|
|
|
|
console.error("Failed to copy message:", fallbackErr)
|
2025-12-30 19:52:57 +08:00
|
|
|
toast.error(dict.chat.failedToCopyDetail)
|
2025-12-30 22:45:50 +08:00
|
|
|
setCopyState(messageId, isToolCall, false)
|
2025-12-11 20:09:42 +08:00
|
|
|
} finally {
|
|
|
|
|
document.body.removeChild(textarea)
|
|
|
|
|
}
|
2025-11-29 12:39:35 +08:00
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-11-29 12:39:35 +08:00
|
|
|
|
2025-12-05 21:15:02 +09:00
|
|
|
const submitFeedback = async (messageId: string, value: "good" | "bad") => {
|
|
|
|
|
// Toggle off if already selected
|
|
|
|
|
if (feedback[messageId] === value) {
|
|
|
|
|
setFeedback((prev) => {
|
2025-12-06 12:46:40 +09:00
|
|
|
const next = { ...prev }
|
|
|
|
|
delete next[messageId]
|
|
|
|
|
return next
|
|
|
|
|
})
|
|
|
|
|
return
|
2025-12-05 21:15:02 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
setFeedback((prev) => ({ ...prev, [messageId]: value }))
|
2025-12-05 21:15:02 +09:00
|
|
|
|
|
|
|
|
try {
|
2025-12-22 19:58:55 +05:30
|
|
|
await fetch(getApiEndpoint("/api/log-feedback"), {
|
2025-12-05 21:15:02 +09:00
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
messageId,
|
|
|
|
|
feedback: value,
|
|
|
|
|
sessionId,
|
|
|
|
|
}),
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
2025-12-05 21:15:02 +09:00
|
|
|
} catch (error) {
|
2025-12-12 14:08:20 +09:00
|
|
|
console.error("Failed to log feedback:", error)
|
2025-12-30 19:52:57 +08:00
|
|
|
toast.error(dict.errors.failedToRecordFeedback)
|
2025-12-12 14:08:20 +09:00
|
|
|
// Revert optimistic UI update
|
|
|
|
|
setFeedback((prev) => {
|
|
|
|
|
const next = { ...prev }
|
|
|
|
|
delete next[messageId]
|
|
|
|
|
return next
|
|
|
|
|
})
|
2025-12-05 21:15:02 +09:00
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-12-05 21:15:02 +09:00
|
|
|
|
2025-08-31 12:54:14 +09:00
|
|
|
const handleDisplayChart = useCallback(
|
2025-12-12 14:52:25 +09:00
|
|
|
(xml: string, showToast = false) => {
|
2025-12-23 18:54:03 +09:00
|
|
|
let currentXml = xml || ""
|
|
|
|
|
|
|
|
|
|
// During streaming (showToast=false), extract only complete mxCell elements
|
|
|
|
|
// This allows progressive rendering even with partial/incomplete trailing XML
|
|
|
|
|
if (!showToast) {
|
|
|
|
|
const completeCells = extractCompleteMxCells(currentXml)
|
|
|
|
|
if (!completeCells) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
currentXml = completeCells
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
const convertedXml = convertToLegalXml(currentXml)
|
2025-08-31 12:54:14 +09:00
|
|
|
if (convertedXml !== previousXML.current) {
|
2025-12-12 14:52:25 +09:00
|
|
|
// Parse and validate XML BEFORE calling replaceNodes
|
|
|
|
|
const parser = new DOMParser()
|
2025-12-23 18:54:03 +09:00
|
|
|
// Wrap in root element for parsing multiple mxCell elements
|
|
|
|
|
const testDoc = parser.parseFromString(
|
|
|
|
|
`<root>${convertedXml}</root>`,
|
|
|
|
|
"text/xml",
|
|
|
|
|
)
|
2025-12-12 14:52:25 +09:00
|
|
|
const parseError = testDoc.querySelector("parsererror")
|
|
|
|
|
|
|
|
|
|
if (parseError) {
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
// Only show toast if this is the final XML (not during streaming)
|
2025-12-12 14:52:25 +09:00
|
|
|
if (showToast) {
|
2025-12-30 19:52:57 +08:00
|
|
|
toast.error(dict.errors.malformedXml)
|
2025-12-12 14:52:25 +09:00
|
|
|
}
|
|
|
|
|
return // Skip this update
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// If chartXML is empty, create a default mxfile structure to use with replaceNodes
|
|
|
|
|
// This ensures the XML is properly wrapped in mxfile/diagram/mxGraphModel format
|
|
|
|
|
const baseXML =
|
|
|
|
|
chartXML ||
|
|
|
|
|
`<mxfile><diagram name="Page-1" id="page-1"><mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel></diagram></mxfile>`
|
|
|
|
|
const replacedXML = replaceNodes(baseXML, convertedXml)
|
|
|
|
|
|
2025-12-23 18:54:03 +09:00
|
|
|
// During streaming (showToast=false), skip heavy validation for lower latency
|
|
|
|
|
// The quick DOM parse check above catches malformed XML
|
|
|
|
|
// Full validation runs on final output (showToast=true)
|
|
|
|
|
if (!showToast) {
|
|
|
|
|
previousXML.current = convertedXml
|
|
|
|
|
onDisplayChart(replacedXML, true)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Final output: run full validation and auto-fix
|
2025-12-13 15:00:28 +09:00
|
|
|
const validation = validateAndFixXml(replacedXML)
|
|
|
|
|
if (validation.valid) {
|
2025-12-12 14:52:25 +09:00
|
|
|
previousXML.current = convertedXml
|
2025-12-13 15:00:28 +09:00
|
|
|
// Use fixed XML if available, otherwise use original
|
|
|
|
|
const xmlToLoad = validation.fixed || replacedXML
|
|
|
|
|
onDisplayChart(xmlToLoad, true)
|
2025-12-12 14:52:25 +09:00
|
|
|
} else {
|
2025-12-30 19:52:57 +08:00
|
|
|
toast.error(dict.errors.validationFailed)
|
2025-12-12 14:52:25 +09:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
console.error("Error processing XML:", error)
|
2025-12-12 14:52:25 +09:00
|
|
|
// Only show toast if this is the final XML (not during streaming)
|
|
|
|
|
if (showToast) {
|
2025-12-30 19:52:57 +08:00
|
|
|
toast.error(dict.errors.failedToProcess)
|
2025-12-12 14:52:25 +09:00
|
|
|
}
|
2025-12-03 20:11:50 +09:00
|
|
|
}
|
2025-08-31 12:54:14 +09:00
|
|
|
}
|
|
|
|
|
},
|
2025-12-06 12:46:40 +09:00
|
|
|
[chartXML, onDisplayChart],
|
|
|
|
|
)
|
2025-07-31 09:32:44 +00:00
|
|
|
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
// Track previous message count to detect bulk loads vs streaming
|
|
|
|
|
const prevMessageCountRef = useRef(0)
|
|
|
|
|
|
2025-03-25 02:58:11 +00:00
|
|
|
useEffect(() => {
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
if (messagesEndRef.current && messages.length > 0) {
|
|
|
|
|
const prevCount = prevMessageCountRef.current
|
|
|
|
|
const currentCount = messages.length
|
|
|
|
|
prevMessageCountRef.current = currentCount
|
|
|
|
|
|
|
|
|
|
// Bulk load (session restore) - instant scroll, no animation
|
|
|
|
|
if (prevCount === 0 || currentCount - prevCount > 1) {
|
|
|
|
|
messagesEndRef.current.scrollIntoView({ behavior: "instant" })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Single message added - smooth scroll
|
2025-12-06 12:46:40 +09:00
|
|
|
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
|
2025-03-25 02:58:11 +00:00
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
}, [messages])
|
2025-03-25 02:58:11 +00:00
|
|
|
|
2025-12-06 16:18:26 +09:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (editingMessageId && editTextareaRef.current) {
|
|
|
|
|
editTextareaRef.current.focus()
|
|
|
|
|
}
|
|
|
|
|
}, [editingMessageId])
|
|
|
|
|
|
2025-04-04 02:29:33 +00:00
|
|
|
useEffect(() => {
|
2025-12-14 21:23:14 +09:00
|
|
|
// Only process the last message for streaming performance
|
|
|
|
|
// Previous messages are already processed and won't change
|
|
|
|
|
const messagesToProcess =
|
|
|
|
|
messages.length > 0 ? [messages[messages.length - 1]] : []
|
|
|
|
|
|
|
|
|
|
messagesToProcess.forEach((message) => {
|
2025-04-04 02:29:33 +00:00
|
|
|
if (message.parts) {
|
2025-12-06 16:18:26 +09:00
|
|
|
message.parts.forEach((part) => {
|
2025-08-31 12:54:14 +09:00
|
|
|
if (part.type?.startsWith("tool-")) {
|
2025-12-06 16:18:26 +09:00
|
|
|
const toolPart = part as ToolPartLike
|
|
|
|
|
const { toolCallId, state, input } = toolPart
|
2025-08-31 12:54:14 +09:00
|
|
|
|
2026-01-20 20:52:04 +09:00
|
|
|
// Auto-collapse on completion, but only if user hasn't manually toggled
|
2025-08-31 12:54:14 +09:00
|
|
|
if (state === "output-available") {
|
2026-01-20 20:52:04 +09:00
|
|
|
setExpandedTools((prev) => {
|
|
|
|
|
// Only auto-collapse if not already set (user hasn't interacted)
|
|
|
|
|
if (prev[toolCallId] === undefined) {
|
|
|
|
|
return { ...prev, [toolCallId]: false }
|
|
|
|
|
}
|
|
|
|
|
return prev
|
|
|
|
|
})
|
2025-07-31 09:32:44 +00:00
|
|
|
}
|
2025-08-31 12:54:14 +09:00
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
part.type === "tool-display_diagram" &&
|
2025-12-06 16:18:26 +09:00
|
|
|
input?.xml
|
2025-08-31 12:54:14 +09:00
|
|
|
) {
|
2025-12-06 16:18:26 +09:00
|
|
|
const xml = input.xml as string
|
2025-12-14 21:23:14 +09:00
|
|
|
|
|
|
|
|
// Skip if XML hasn't changed since last processing
|
|
|
|
|
const lastXml =
|
|
|
|
|
lastProcessedXmlRef.current.get(toolCallId)
|
|
|
|
|
if (lastXml === xml) {
|
|
|
|
|
return // Skip redundant processing
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-31 12:54:14 +09:00
|
|
|
if (
|
|
|
|
|
state === "input-streaming" ||
|
|
|
|
|
state === "input-available"
|
|
|
|
|
) {
|
2025-12-14 21:23:14 +09:00
|
|
|
// Debounce streaming updates - queue the XML and process after delay
|
|
|
|
|
pendingXmlRef.current = xml
|
|
|
|
|
|
|
|
|
|
if (!debounceTimeoutRef.current) {
|
|
|
|
|
// No pending timeout - set one up
|
|
|
|
|
debounceTimeoutRef.current = setTimeout(
|
|
|
|
|
() => {
|
|
|
|
|
const pendingXml =
|
|
|
|
|
pendingXmlRef.current
|
|
|
|
|
debounceTimeoutRef.current = null
|
|
|
|
|
pendingXmlRef.current = null
|
|
|
|
|
if (pendingXml) {
|
|
|
|
|
handleDisplayChart(
|
|
|
|
|
pendingXml,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
lastProcessedXmlRef.current.set(
|
|
|
|
|
toolCallId,
|
|
|
|
|
pendingXml,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
STREAMING_DEBOUNCE_MS,
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-03 21:49:34 +09:00
|
|
|
} else if (
|
2025-08-31 12:54:14 +09:00
|
|
|
state === "output-available" &&
|
|
|
|
|
!processedToolCalls.current.has(toolCallId)
|
|
|
|
|
) {
|
2025-12-14 21:23:14 +09:00
|
|
|
// Final output - process immediately (clear any pending debounce)
|
|
|
|
|
if (debounceTimeoutRef.current) {
|
|
|
|
|
clearTimeout(debounceTimeoutRef.current)
|
|
|
|
|
debounceTimeoutRef.current = null
|
|
|
|
|
pendingXmlRef.current = null
|
|
|
|
|
}
|
2025-12-12 14:52:25 +09:00
|
|
|
// Show toast only if final XML is malformed
|
|
|
|
|
handleDisplayChart(xml, true)
|
2025-12-06 12:46:40 +09:00
|
|
|
processedToolCalls.current.add(toolCallId)
|
2025-12-14 21:23:14 +09:00
|
|
|
// Clean up the ref entry - tool is complete, no longer needed
|
|
|
|
|
lastProcessedXmlRef.current.delete(toolCallId)
|
2025-12-15 21:28:31 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle edit_diagram streaming - apply operations incrementally for preview
|
|
|
|
|
// Uses shared editDiagramOriginalXmlRef to coordinate with tool handler
|
|
|
|
|
if (
|
|
|
|
|
part.type === "tool-edit_diagram" &&
|
|
|
|
|
input?.operations
|
|
|
|
|
) {
|
|
|
|
|
const completeOps = getCompleteOperations(
|
|
|
|
|
input.operations as DiagramOperation[],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (completeOps.length === 0) return
|
|
|
|
|
|
|
|
|
|
// Capture original XML when streaming starts (store in shared ref)
|
|
|
|
|
if (
|
|
|
|
|
!editDiagramOriginalXmlRef.current.has(
|
|
|
|
|
toolCallId,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
if (!chartXML) {
|
|
|
|
|
console.warn(
|
|
|
|
|
"[edit_diagram streaming] No chart XML available",
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
editDiagramOriginalXmlRef.current.set(
|
|
|
|
|
toolCallId,
|
|
|
|
|
chartXML,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const originalXml =
|
|
|
|
|
editDiagramOriginalXmlRef.current.get(
|
|
|
|
|
toolCallId,
|
|
|
|
|
)
|
|
|
|
|
if (!originalXml) return
|
|
|
|
|
|
|
|
|
|
// Skip if no change from last processed state
|
|
|
|
|
const lastCount = lastProcessedXmlRef.current.get(
|
|
|
|
|
toolCallId + "-opCount",
|
|
|
|
|
)
|
|
|
|
|
if (lastCount === String(completeOps.length)) return
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
state === "input-streaming" ||
|
|
|
|
|
state === "input-available"
|
|
|
|
|
) {
|
|
|
|
|
// Queue the operations for debounced processing
|
|
|
|
|
pendingEditRef.current = {
|
|
|
|
|
operations: completeOps,
|
|
|
|
|
toolCallId,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!editDebounceTimeoutRef.current) {
|
|
|
|
|
editDebounceTimeoutRef.current = setTimeout(
|
|
|
|
|
() => {
|
|
|
|
|
const pending =
|
|
|
|
|
pendingEditRef.current
|
|
|
|
|
editDebounceTimeoutRef.current =
|
|
|
|
|
null
|
|
|
|
|
pendingEditRef.current = null
|
|
|
|
|
|
|
|
|
|
if (pending) {
|
|
|
|
|
const origXml =
|
|
|
|
|
editDiagramOriginalXmlRef.current.get(
|
|
|
|
|
pending.toolCallId,
|
|
|
|
|
)
|
|
|
|
|
if (!origXml) return
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const {
|
|
|
|
|
result: editedXml,
|
|
|
|
|
} = applyDiagramOperations(
|
|
|
|
|
origXml,
|
|
|
|
|
pending.operations,
|
|
|
|
|
)
|
|
|
|
|
handleDisplayChart(
|
|
|
|
|
editedXml,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
lastProcessedXmlRef.current.set(
|
|
|
|
|
pending.toolCallId +
|
|
|
|
|
"-opCount",
|
|
|
|
|
String(
|
|
|
|
|
pending.operations
|
|
|
|
|
.length,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`[edit_diagram streaming] Operation failed:`,
|
|
|
|
|
e instanceof Error
|
|
|
|
|
? e.message
|
|
|
|
|
: e,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
STREAMING_DEBOUNCE_MS,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else if (
|
|
|
|
|
state === "output-available" &&
|
|
|
|
|
!processedToolCalls.current.has(toolCallId)
|
|
|
|
|
) {
|
|
|
|
|
// Final state - cleanup streaming refs (tool handler does final application)
|
|
|
|
|
if (editDebounceTimeoutRef.current) {
|
|
|
|
|
clearTimeout(editDebounceTimeoutRef.current)
|
|
|
|
|
editDebounceTimeoutRef.current = null
|
|
|
|
|
}
|
|
|
|
|
lastProcessedXmlRef.current.delete(
|
|
|
|
|
toolCallId + "-opCount",
|
|
|
|
|
)
|
|
|
|
|
processedToolCalls.current.add(toolCallId)
|
|
|
|
|
// Note: Don't delete editDiagramOriginalXmlRef here - tool handler needs it
|
2025-07-31 09:32:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-04 02:29:33 +00:00
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
2025-04-04 02:29:33 +00:00
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
2025-12-14 21:23:14 +09:00
|
|
|
|
2025-12-23 18:54:03 +09:00
|
|
|
// NOTE: Don't cleanup debounce timeouts here!
|
|
|
|
|
// The cleanup runs on every re-render (when messages changes),
|
|
|
|
|
// which would cancel the timeout before it fires.
|
|
|
|
|
// Let the timeouts complete naturally - they're harmless if component unmounts.
|
2025-12-15 21:28:31 +09:00
|
|
|
}, [messages, handleDisplayChart, chartXML])
|
2025-04-04 01:26:16 +00:00
|
|
|
|
2025-03-25 02:58:11 +00:00
|
|
|
return (
|
2025-12-05 22:42:39 +09:00
|
|
|
<ScrollArea className="h-full w-full scrollbar-thin">
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
<div ref={scrollTopRef} />
|
2026-01-01 15:42:48 +09:00
|
|
|
{messages.length === 0 && isRestored ? (
|
2026-01-04 12:04:06 +09:00
|
|
|
<ChatLobby
|
|
|
|
|
sessions={sessions}
|
|
|
|
|
onSelectSession={onSelectSession || (() => {})}
|
|
|
|
|
onDeleteSession={onDeleteSession}
|
|
|
|
|
setInput={setInput}
|
|
|
|
|
setFiles={setFiles}
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
onSendTemplate={onSendTemplate}
|
|
|
|
|
currentInput={currentInput}
|
2026-01-04 12:04:06 +09:00
|
|
|
dict={dict}
|
|
|
|
|
/>
|
2026-01-01 15:42:48 +09:00
|
|
|
) : messages.length === 0 ? null : (
|
2025-12-05 22:42:39 +09:00
|
|
|
<div className="py-4 px-4 space-y-4">
|
2025-12-03 21:49:34 +09:00
|
|
|
{messages.map((message, messageIndex) => {
|
2025-12-06 12:46:40 +09:00
|
|
|
const userMessageText =
|
|
|
|
|
message.role === "user"
|
|
|
|
|
? getMessageTextContent(message)
|
|
|
|
|
: ""
|
|
|
|
|
const isLastAssistantMessage =
|
|
|
|
|
message.role === "assistant" &&
|
|
|
|
|
(messageIndex === messages.length - 1 ||
|
|
|
|
|
messages
|
|
|
|
|
.slice(messageIndex + 1)
|
|
|
|
|
.every((m) => m.role !== "assistant"))
|
|
|
|
|
const isLastUserMessage =
|
|
|
|
|
message.role === "user" &&
|
|
|
|
|
(messageIndex === messages.length - 1 ||
|
|
|
|
|
messages
|
|
|
|
|
.slice(messageIndex + 1)
|
|
|
|
|
.every((m) => m.role !== "user"))
|
|
|
|
|
const isEditing = editingMessageId === message.id
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
// Skip animation for loaded messages (from session restore)
|
2026-01-01 15:42:48 +09:00
|
|
|
const isRestoredMessage =
|
feat: add chat session history with IndexedDB persistence (#500)
* feat(session): add chat session history with IndexedDB storage
- Add session-storage.ts with IndexedDB wrapper using idb library
- Add use-session-manager.ts hook for session state management
- Add session-history-dropdown.tsx for session selection UI
- Integrate session system into chat-panel.tsx
- Auto-generate session titles from first user message
- Auto-save sessions on message completion
- Support session switching, deletion, and creation
- Migrate existing localStorage data to IndexedDB
- Add i18n translations for session history UI
* feat(session): improve history dropdown and persist diagram history
- Add time-based grouping (Today, Yesterday, This Week, Earlier)
- Add thumbnail previews using Next.js Image component
- Add staggered entrance animations with fade-in effects
- Improve active session indicator with left border accent
- Fix scrolling by using native overflow instead of ScrollArea
- Persist diagram version history to IndexedDB sessions
- Remove redundant diagram XML from localStorage
- Add i18n strings for time group labels (en, ja, zh)
* fix(session): prevent data loss on theme change and tab close
- Add isDrawioReady effect to restore diagram after DrawIO remount
- Add visibilitychange handler to save session when page becomes hidden
- Fix missing currentSessionId in saveCurrentSession dependency array
- Remove unused sanitizeMessages import from use-session-manager
* fix(session): fix diagram save and migration data loss bugs
- Add diagramHistory to save effect dependency array so diagram-only
edits trigger saves (previously only message changes did)
- Destructure stable sessionManager values to prevent unnecessary
effect re-runs on every render
- Add try-catch wrapper around debounced async save operation
- Make saveSession() return boolean to indicate success/failure
- Verify IndexedDB write succeeded before deleting localStorage data
during migration (prevents data loss if write silently fails)
- Keep localStorage data for retry if migration fails instead of
marking as complete anyway
* refactor(session): extract helpers to reduce code duplication
- Add syncUIWithSession helper to consolidate 4 duplicate UI sync blocks
- Add buildSessionData helper to consolidate 4 duplicate save logic blocks
- Remove unused saveTimeoutRef and its cleanup effect
- Net reduction of ~80 lines of duplicate code
* style(ui): improve history dropdown and delete dialog styling
- Change destructive color from coral to muted rose for refined look
- Make session history panel taller (400px fixed height)
- Fix popover alignment to prevent truncation
- Style delete button with soft red outline instead of solid fill
- Make delete dialog more compact (max-w-sm)
* fix(session): reset refs on new chat and show recent sessions
- Fix cached example diagrams not displaying after creating new session
- Reset previousXML, lastProcessedXmlRef and processedToolCalls when
messages become empty (new chat or session switch)
- Add recent chats section in empty chat state with collapsible examples
- Pass sessions and onSelectSession to ChatMessageDisplay
- Add loadedMessageIdsRef to skip animations on session restore
- Add debug console.log for diagram processing flow
* feat(session): add search bar and improve history UI
- Remove session history dropdown, use main panel instead
- Add search bar to filter history chats by title
- Show minutes (Xm ago) instead of "Just now" for recent sessions
- Scroll to top when switching to new/empty chat
- Remove title truncation limit for better searchability
- Remove debug console.log statements
* refactor: remove redundant code and fix nested button hydration error
- Remove unused 'sessions' from deleteSession dependency array
- Remove unused 'switchedTo' variable and simplify return type
- Remove unused 'restoredMessageIdsRef' (always empty)
- Fix nested button hydration error by using div with role=button
- Simplify handleDeleteSession callback
* fix(session): fix migration bug, improve metadata perf, truncate titles
- Fix migration retry loop when localStorage has empty array
- Use cursor-based iteration for getAllSessionMetadata
- Truncate session titles to 100 chars with ellipsis
* refactor: remove dead code and extract diagram length constant
- Remove unused exports: getAllSessions, createNewSession, updateSessionTitle
- Remove write-only CURRENT_SESSION_KEY and all localStorage calls
- Remove dead messagesEndRef and unused scroll effect
- Extract magic number 300 to MIN_REAL_DIAGRAM_LENGTH constant
- Add isRealDiagram() helper function for semantic clarity
2026-01-04 10:25:19 +09:00
|
|
|
loadedMessageIdsRef?.current.has(message.id) ??
|
|
|
|
|
false
|
2025-12-03 21:49:34 +09:00
|
|
|
return (
|
2025-11-29 12:39:35 +08:00
|
|
|
<div
|
2025-12-03 21:49:34 +09:00
|
|
|
key={message.id}
|
2026-01-01 15:42:48 +09:00
|
|
|
className={`flex w-full ${message.role === "user" ? "justify-end" : "justify-start"} ${isRestoredMessage ? "" : "animate-message-in"}`}
|
|
|
|
|
style={
|
|
|
|
|
isRestoredMessage
|
|
|
|
|
? undefined
|
|
|
|
|
: {
|
|
|
|
|
animationDelay: `${messageIndex * 50}ms`,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-29 12:39:35 +08:00
|
|
|
>
|
2025-12-06 12:46:40 +09:00
|
|
|
{message.role === "user" &&
|
|
|
|
|
userMessageText &&
|
|
|
|
|
!isEditing && (
|
|
|
|
|
<div className="flex items-center gap-1 self-center mr-2">
|
|
|
|
|
{/* Edit button - only on last user message */}
|
|
|
|
|
{onEditMessage &&
|
|
|
|
|
isLastUserMessage && (
|
|
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-06 12:46:40 +09:00
|
|
|
onClick={() => {
|
|
|
|
|
setEditingMessageId(
|
|
|
|
|
message.id,
|
|
|
|
|
)
|
|
|
|
|
setEditText(
|
2025-12-10 21:32:35 +09:00
|
|
|
getUserOriginalText(
|
|
|
|
|
message,
|
|
|
|
|
),
|
2025-12-06 12:46:40 +09:00
|
|
|
)
|
|
|
|
|
}}
|
|
|
|
|
className="p-1.5 rounded-lg text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted transition-colors"
|
2025-12-30 19:52:57 +08:00
|
|
|
title={
|
|
|
|
|
dict.chat
|
|
|
|
|
.editMessage
|
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
>
|
|
|
|
|
<Pencil className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2025-12-04 22:56:59 +09:00
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-06 12:46:40 +09:00
|
|
|
onClick={() =>
|
|
|
|
|
copyMessageToClipboard(
|
|
|
|
|
message.id,
|
|
|
|
|
userMessageText,
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-04 22:56:59 +09:00
|
|
|
className="p-1.5 rounded-lg text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted transition-colors"
|
2025-12-06 12:46:40 +09:00
|
|
|
title={
|
|
|
|
|
copiedMessageId ===
|
|
|
|
|
message.id
|
2025-12-30 19:52:57 +08:00
|
|
|
? dict.chat.copied
|
2025-12-06 12:46:40 +09:00
|
|
|
: copyFailedMessageId ===
|
|
|
|
|
message.id
|
2025-12-30 19:52:57 +08:00
|
|
|
? dict.chat
|
|
|
|
|
.failedToCopy
|
|
|
|
|
: dict.chat
|
|
|
|
|
.copyResponse
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-12-04 22:56:59 +09:00
|
|
|
>
|
2025-12-06 12:46:40 +09:00
|
|
|
{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" />
|
|
|
|
|
)}
|
2025-12-04 22:56:59 +09:00
|
|
|
</button>
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
{/* Save as Template button - only for user messages */}
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() =>
|
|
|
|
|
setSaveAsTemplateMessageId(
|
|
|
|
|
message.id,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
className="p-1.5 rounded-lg text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted transition-colors"
|
|
|
|
|
title={
|
|
|
|
|
dict.templates
|
|
|
|
|
?.saveAsTemplate ||
|
|
|
|
|
"Save as Template"
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<BookmarkPlus className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
2025-12-06 12:46:40 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
feat: add personal My Templates library alongside Quick Examples (#773)
* 增加了ralph自动化编程梳理
* feat: US-001 - 为模板库建立独立的 IndexedDB 存储层
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-002 - 在空聊天状态用我的模板库替换官方示例
- 将 ChatLobby 中的 Quick Examples 替换为 TemplatePanel
- 当没有历史会话时,展示完整的模板库面板
- 当有历史会话时,展示可折叠的 "My Templates" 区域
- 使用 TemplatePanel 组件展示用户的个人模板库
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-004 - Provide template creation flow
- Create TemplateCreateDialog component with form fields for prompt, title, description, tags, and pinned
- Add i18n translations for template creation UI in en, zh, zh-Hant, ja
- Update TemplatePanel to integrate the create dialog
- Support initialPrompt prop for pre-filling from current input
- Validate required prompt field (empty prompt not allowed)
- Auto-generate default title from first 20 chars of Pin templates appear at top of list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-005 - 为模板卡片提供编辑、删除和复制操作
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-006 - Send template directly on click and record usage statistics
- Implement click-to-send template functionality with confirmation dialog
- Add clickCount and runCount increment logic
- Display runCount and lastUsedAt on template card
- Add i18n translations for confirmation dialog (en, zh, zh-Hant, ja)
- Pass onSendTemplate and currentInput props through component hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-007 - Support template search, pin and default sorting
- Add search bar to TemplatePanel with real-time filtering by title, description, and tags
- Add pin/unpin toggle button on template cards (uses Bookmark icon with fill indicator)
- Search uses existing searchTemplates function from template-storage
- Sort uses existing sortTemplates function (pinned desc, runCount desc, lastUsedAt desc, updatedAt desc)
- Show empty state with Search icon when search returns no results
- List re-sorts immediately after pin/unpin toggle
- Add i18n keys: searchPlaceholder, searchNoResults, pin, unpin for all 4 languages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-008 - Support saving current input as template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-009 - Support saving historical user message as template
- Add "Save as Template" button to user messages
- Pre-fill prompt with original user message text
- Only show on user messages,- Dialog opens TemplateCreateDialog on click
- Template appears in list immediately after saving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: US-010 - Support template import and export
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove local-only dirs from git tracking (.agents, .cursor, scripts, screenshots)
These directories contain local IDE configs, agent scripts, and
dev tooling that should not be part of the upstream repository.
Added them to .gitignore to prevent future accidental commits.
* chore: remove AGENTS.md from git tracking
* fix: add missing i18n keys for template export/import (en/zh/zh-Hant/ja)
* fix: review fixes for my-templates PR
- Fix fragile querySelector("form") with id-based lookup
- Add objectStoreNames.contains guards for IndexedDB upgrades
- Remove duplicate TemplateSchema, import from template-storage
- Revert contributor-specific .gitignore additions
- Fix broken i18n placeholders and missing translations (zh/ja/zh-Hant)
- Remove unused setFiles prop from ChatLobby
- Remove tags feature (unnecessary complexity)
- Improve template card layout: overlay icons on hover, align stats
- Add break-all and overflow-hidden for long prompt text in dialogs
- Move incrementClickCount into sendTemplate for accurate tracking
- Use Intl.RelativeTimeFormat for locale-aware relative time
* feat: restore Quick Examples panel and add lobby panel visibility settings
Bring back the ExamplePanel as a third collapsible section in ChatLobby
alongside Recent Chats and My Templates. Add toggle switches in Settings
to show/hide each lobby panel, persisted via localStorage.
* fix: template send race condition, import defaults, and empty title bug
- Use flushSync instead of setTimeout(0) in handleSendTemplate to
ensure React state is flushed before form submission
- Explicitly validate and default all fields in importTemplates to
prevent undefined counters from malformed import JSON
- Fall back to existing title in edit dialog instead of writing undefined
* fix: address Copilot review comments and remove PRD file
- Fix fallback formatLastUsed returning "Not used yet" for recent usage
- Remove dead mounted flag in TemplatePanel useEffect
- Respect panel visibility settings in no-history lobby state
- Reject empty/whitespace titles in import validation
- Trim title/prompt in importTemplates with default title fallback
- Remove tasks/prd-template-library-replaces-examples.md from repo
* fix: remove double sort, dead code, redundant stats, and break-all CSS
- Remove redundant sortTemplates call in loadTemplates (already sorted by getAllTemplates)
- Remove unused createEmptyTemplateInput and sortTemplates import
- Show "Not used yet" only once for unused templates instead of twice
- Use break-words instead of break-all on prompt textareas
---------
Co-authored-by: 杜雷 <dreamfly@126.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2026-04-03 11:16:01 +08:00
|
|
|
|
|
|
|
|
{/* Save as Template Dialog */}
|
|
|
|
|
{saveAsTemplateMessageId === message.id && (
|
|
|
|
|
<TemplateCreateDialog
|
|
|
|
|
open={true}
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
if (!open)
|
|
|
|
|
setSaveAsTemplateMessageId(null)
|
|
|
|
|
}}
|
|
|
|
|
onSuccess={() => {
|
|
|
|
|
setSaveAsTemplateMessageId(null)
|
|
|
|
|
}}
|
|
|
|
|
initialPrompt={getUserOriginalText(
|
|
|
|
|
message,
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2025-12-05 22:42:39 +09:00
|
|
|
<div className="max-w-[85%] min-w-0">
|
2025-12-10 20:54:43 +05:30
|
|
|
{/* Reasoning blocks - displayed first for assistant messages */}
|
|
|
|
|
{message.role === "assistant" &&
|
|
|
|
|
message.parts?.map(
|
|
|
|
|
(part, partIndex) => {
|
|
|
|
|
if (part.type === "reasoning") {
|
|
|
|
|
const reasoningPart =
|
|
|
|
|
part as {
|
|
|
|
|
type: "reasoning"
|
|
|
|
|
text: string
|
|
|
|
|
}
|
|
|
|
|
const isLastPart =
|
|
|
|
|
partIndex ===
|
|
|
|
|
(message.parts
|
|
|
|
|
?.length ?? 0) -
|
|
|
|
|
1
|
|
|
|
|
const isLastMessage =
|
|
|
|
|
message.id ===
|
|
|
|
|
messages[
|
|
|
|
|
messages.length - 1
|
|
|
|
|
]?.id
|
|
|
|
|
const isStreamingReasoning =
|
|
|
|
|
status ===
|
|
|
|
|
"streaming" &&
|
|
|
|
|
isLastPart &&
|
|
|
|
|
isLastMessage
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Reasoning
|
|
|
|
|
key={`${message.id}-reasoning-${partIndex}`}
|
|
|
|
|
className="w-full"
|
|
|
|
|
isStreaming={
|
|
|
|
|
isStreamingReasoning
|
|
|
|
|
}
|
2026-01-01 15:42:48 +09:00
|
|
|
defaultOpen={
|
|
|
|
|
!isRestoredMessage
|
|
|
|
|
}
|
2025-12-10 20:54:43 +05:30
|
|
|
>
|
|
|
|
|
<ReasoningTrigger />
|
|
|
|
|
<ReasoningContent>
|
|
|
|
|
{
|
|
|
|
|
reasoningPart.text
|
|
|
|
|
}
|
|
|
|
|
</ReasoningContent>
|
|
|
|
|
</Reasoning>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
},
|
|
|
|
|
)}
|
2025-12-04 22:56:59 +09:00
|
|
|
{/* Edit mode for user messages */}
|
|
|
|
|
{isEditing && message.role === "user" ? (
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<textarea
|
2025-12-06 16:18:26 +09:00
|
|
|
ref={editTextareaRef}
|
2025-12-04 22:56:59 +09:00
|
|
|
value={editText}
|
2025-12-06 12:46:40 +09:00
|
|
|
onChange={(e) =>
|
|
|
|
|
setEditText(e.target.value)
|
|
|
|
|
}
|
2025-12-04 22:56:59 +09:00
|
|
|
className="w-full min-w-[300px] px-4 py-3 text-sm rounded-2xl border border-primary bg-background text-foreground resize-none focus:outline-none focus:ring-2 focus:ring-primary"
|
2025-12-06 12:46:40 +09:00
|
|
|
rows={Math.min(
|
|
|
|
|
editText.split("\n")
|
|
|
|
|
.length + 1,
|
|
|
|
|
6,
|
|
|
|
|
)}
|
2025-12-04 22:56:59 +09:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (e.key === "Escape") {
|
2025-12-06 12:46:40 +09:00
|
|
|
setEditingMessageId(
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
setEditText("")
|
|
|
|
|
} else if (
|
|
|
|
|
e.key === "Enter" &&
|
|
|
|
|
(e.metaKey || e.ctrlKey)
|
|
|
|
|
) {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
if (
|
|
|
|
|
editText.trim() &&
|
|
|
|
|
onEditMessage
|
|
|
|
|
) {
|
|
|
|
|
onEditMessage(
|
|
|
|
|
messageIndex,
|
|
|
|
|
editText.trim(),
|
|
|
|
|
)
|
|
|
|
|
setEditingMessageId(
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
setEditText("")
|
2025-12-04 22:56:59 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div className="flex justify-end gap-2">
|
|
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-04 22:56:59 +09:00
|
|
|
onClick={() => {
|
2025-12-06 12:46:40 +09:00
|
|
|
setEditingMessageId(
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
setEditText("")
|
2025-12-04 22:56:59 +09:00
|
|
|
}}
|
|
|
|
|
className="px-3 py-1.5 text-xs rounded-lg bg-muted hover:bg-muted/80 transition-colors"
|
|
|
|
|
>
|
2025-12-30 19:52:57 +08:00
|
|
|
{dict.common.cancel}
|
2025-12-04 22:56:59 +09:00
|
|
|
</button>
|
|
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-04 22:56:59 +09:00
|
|
|
onClick={() => {
|
2025-12-06 12:46:40 +09:00
|
|
|
if (
|
|
|
|
|
editText.trim() &&
|
|
|
|
|
onEditMessage
|
|
|
|
|
) {
|
|
|
|
|
onEditMessage(
|
|
|
|
|
messageIndex,
|
|
|
|
|
editText.trim(),
|
|
|
|
|
)
|
|
|
|
|
setEditingMessageId(
|
|
|
|
|
null,
|
|
|
|
|
)
|
|
|
|
|
setEditText("")
|
2025-12-04 22:56:59 +09:00
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
disabled={!editText.trim()}
|
|
|
|
|
className="px-3 py-1.5 text-xs rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
|
|
|
|
|
>
|
2025-12-30 19:52:57 +08:00
|
|
|
{dict.chat.saveAndSubmit}
|
2025-12-04 22:56:59 +09:00
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-12-03 21:49:34 +09:00
|
|
|
</div>
|
2025-12-04 22:56:59 +09:00
|
|
|
) : (
|
2025-12-07 19:56:31 +09:00
|
|
|
/* Render parts in order, grouping consecutive text/file parts into bubbles */
|
|
|
|
|
(() => {
|
|
|
|
|
const parts = message.parts || []
|
|
|
|
|
const groups: {
|
|
|
|
|
type: "content" | "tool"
|
|
|
|
|
parts: typeof parts
|
|
|
|
|
startIndex: number
|
|
|
|
|
}[] = []
|
|
|
|
|
|
|
|
|
|
parts.forEach((part, index) => {
|
|
|
|
|
const isToolPart =
|
|
|
|
|
part.type?.startsWith(
|
|
|
|
|
"tool-",
|
|
|
|
|
)
|
|
|
|
|
const isContentPart =
|
|
|
|
|
part.type === "text" ||
|
|
|
|
|
part.type === "file"
|
|
|
|
|
|
|
|
|
|
if (isToolPart) {
|
|
|
|
|
groups.push({
|
|
|
|
|
type: "tool",
|
|
|
|
|
parts: [part],
|
|
|
|
|
startIndex: index,
|
|
|
|
|
})
|
|
|
|
|
} else if (isContentPart) {
|
|
|
|
|
const lastGroup =
|
|
|
|
|
groups[
|
|
|
|
|
groups.length - 1
|
|
|
|
|
]
|
2025-12-06 12:46:40 +09:00
|
|
|
if (
|
2025-12-07 19:56:31 +09:00
|
|
|
lastGroup?.type ===
|
|
|
|
|
"content"
|
2025-12-06 12:46:40 +09:00
|
|
|
) {
|
2025-12-07 19:56:31 +09:00
|
|
|
lastGroup.parts.push(
|
|
|
|
|
part,
|
2025-12-06 12:46:40 +09:00
|
|
|
)
|
2025-12-07 19:56:31 +09:00
|
|
|
} else {
|
|
|
|
|
groups.push({
|
|
|
|
|
type: "content",
|
|
|
|
|
parts: [part],
|
|
|
|
|
startIndex: index,
|
|
|
|
|
})
|
2025-12-04 22:56:59 +09:00
|
|
|
}
|
2025-12-07 19:56:31 +09:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return groups.map(
|
|
|
|
|
(group, groupIndex) => {
|
|
|
|
|
if (group.type === "tool") {
|
2026-01-20 20:52:04 +09:00
|
|
|
const toolPart = group
|
|
|
|
|
.parts[0] as ToolPartLike
|
|
|
|
|
const toolCallId =
|
|
|
|
|
toolPart.toolCallId
|
|
|
|
|
const isDisplayDiagram =
|
|
|
|
|
toolPart.type ===
|
|
|
|
|
"tool-display_diagram"
|
|
|
|
|
const validationState =
|
|
|
|
|
validationStates[
|
|
|
|
|
toolCallId
|
|
|
|
|
]
|
|
|
|
|
|
2026-01-04 12:04:06 +09:00
|
|
|
return (
|
2026-01-20 20:52:04 +09:00
|
|
|
<div
|
2026-01-04 12:04:06 +09:00
|
|
|
key={`${message.id}-tool-${group.startIndex}`}
|
2026-01-20 20:52:04 +09:00
|
|
|
>
|
|
|
|
|
<ToolCallCard
|
|
|
|
|
part={
|
|
|
|
|
toolPart
|
|
|
|
|
}
|
|
|
|
|
expandedTools={
|
|
|
|
|
expandedTools
|
|
|
|
|
}
|
|
|
|
|
setExpandedTools={
|
|
|
|
|
setExpandedTools
|
|
|
|
|
}
|
|
|
|
|
onCopy={
|
|
|
|
|
copyMessageToClipboard
|
|
|
|
|
}
|
|
|
|
|
copiedToolCallId={
|
|
|
|
|
copiedToolCallId
|
|
|
|
|
}
|
|
|
|
|
copyFailedToolCallId={
|
|
|
|
|
copyFailedToolCallId
|
|
|
|
|
}
|
|
|
|
|
dict={dict}
|
|
|
|
|
/>
|
|
|
|
|
{/* Show validation card for display_diagram tools */}
|
|
|
|
|
{isDisplayDiagram &&
|
|
|
|
|
validationState && (
|
|
|
|
|
<ValidationCard
|
|
|
|
|
state={
|
|
|
|
|
validationState
|
|
|
|
|
}
|
|
|
|
|
onImproveWithSuggestions={
|
|
|
|
|
onImproveWithSuggestions
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2025-12-06 16:18:26 +09:00
|
|
|
)
|
|
|
|
|
}
|
2025-12-07 19:56:31 +09:00
|
|
|
|
|
|
|
|
// Content bubble
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={`${message.id}-content-${group.startIndex}`}
|
|
|
|
|
className={`px-4 py-3 text-sm leading-relaxed ${
|
|
|
|
|
message.role ===
|
|
|
|
|
"user"
|
|
|
|
|
? "bg-primary text-primary-foreground rounded-2xl rounded-br-md shadow-sm"
|
|
|
|
|
: message.role ===
|
|
|
|
|
"system"
|
|
|
|
|
? "bg-destructive/10 text-destructive border border-destructive/20 rounded-2xl rounded-bl-md"
|
|
|
|
|
: "bg-muted/60 text-foreground rounded-2xl rounded-bl-md"
|
|
|
|
|
} ${message.role === "user" && isLastUserMessage && onEditMessage ? "cursor-pointer hover:opacity-90 transition-opacity" : ""} ${groupIndex > 0 ? "mt-3" : ""}`}
|
|
|
|
|
role={
|
|
|
|
|
message.role ===
|
|
|
|
|
"user" &&
|
|
|
|
|
isLastUserMessage &&
|
|
|
|
|
onEditMessage
|
|
|
|
|
? "button"
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
tabIndex={
|
|
|
|
|
message.role ===
|
|
|
|
|
"user" &&
|
|
|
|
|
isLastUserMessage &&
|
|
|
|
|
onEditMessage
|
|
|
|
|
? 0
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (
|
|
|
|
|
message.role ===
|
|
|
|
|
"user" &&
|
|
|
|
|
isLastUserMessage &&
|
|
|
|
|
onEditMessage
|
|
|
|
|
) {
|
|
|
|
|
setEditingMessageId(
|
|
|
|
|
message.id,
|
|
|
|
|
)
|
|
|
|
|
setEditText(
|
2025-12-10 21:32:35 +09:00
|
|
|
getUserOriginalText(
|
|
|
|
|
message,
|
|
|
|
|
),
|
2025-12-07 19:56:31 +09:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (
|
|
|
|
|
(e.key ===
|
|
|
|
|
"Enter" ||
|
|
|
|
|
e.key ===
|
|
|
|
|
" ") &&
|
|
|
|
|
message.role ===
|
|
|
|
|
"user" &&
|
|
|
|
|
isLastUserMessage &&
|
|
|
|
|
onEditMessage
|
|
|
|
|
) {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
setEditingMessageId(
|
|
|
|
|
message.id,
|
|
|
|
|
)
|
|
|
|
|
setEditText(
|
2025-12-10 21:32:35 +09:00
|
|
|
getUserOriginalText(
|
|
|
|
|
message,
|
|
|
|
|
),
|
2025-12-07 19:56:31 +09:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
title={
|
|
|
|
|
message.role ===
|
|
|
|
|
"user" &&
|
|
|
|
|
isLastUserMessage &&
|
|
|
|
|
onEditMessage
|
2025-12-30 19:52:57 +08:00
|
|
|
? dict.chat
|
|
|
|
|
.clickToEdit
|
2025-12-07 19:56:31 +09:00
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{group.parts.map(
|
|
|
|
|
(
|
|
|
|
|
part,
|
|
|
|
|
partIndex,
|
|
|
|
|
) => {
|
|
|
|
|
if (
|
|
|
|
|
part.type ===
|
|
|
|
|
"text"
|
|
|
|
|
) {
|
2025-12-10 21:32:35 +09:00
|
|
|
const textContent =
|
|
|
|
|
(
|
|
|
|
|
part as {
|
|
|
|
|
text: string
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
.text
|
|
|
|
|
const sections =
|
|
|
|
|
splitTextIntoFileSections(
|
|
|
|
|
textContent,
|
|
|
|
|
)
|
2025-12-07 19:56:31 +09:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={`${message.id}-text-${group.startIndex}-${partIndex}`}
|
2025-12-10 21:32:35 +09:00
|
|
|
className="space-y-2"
|
2025-12-07 19:56:31 +09:00
|
|
|
>
|
2025-12-10 21:32:35 +09:00
|
|
|
{sections.map(
|
|
|
|
|
(
|
|
|
|
|
section,
|
|
|
|
|
sectionIndex,
|
|
|
|
|
) => {
|
|
|
|
|
if (
|
|
|
|
|
section.type ===
|
2026-01-24 07:14:11 +05:30
|
|
|
"file" ||
|
|
|
|
|
section.type ===
|
|
|
|
|
"url"
|
2025-12-10 21:32:35 +09:00
|
|
|
) {
|
2026-01-24 07:14:11 +05:30
|
|
|
const sectionKey = `${message.id}-${section.type}-${partIndex}-${sectionIndex}`
|
2025-12-10 21:32:35 +09:00
|
|
|
const isExpanded =
|
|
|
|
|
expandedPdfSections[
|
2026-01-24 07:14:11 +05:30
|
|
|
sectionKey
|
2025-12-10 21:32:35 +09:00
|
|
|
] ??
|
|
|
|
|
false
|
|
|
|
|
const charDisplay =
|
|
|
|
|
section.charCount &&
|
|
|
|
|
section.charCount >=
|
|
|
|
|
1000
|
|
|
|
|
? `${(section.charCount / 1000).toFixed(1)}k`
|
|
|
|
|
: section.charCount
|
2026-01-24 07:14:11 +05:30
|
|
|
|
|
|
|
|
// Icon selector
|
|
|
|
|
const Icon =
|
|
|
|
|
section.fileType ===
|
|
|
|
|
"pdf"
|
|
|
|
|
? FileText
|
|
|
|
|
: section.fileType ===
|
|
|
|
|
"url"
|
|
|
|
|
? Link
|
|
|
|
|
: FileCode
|
|
|
|
|
|
|
|
|
|
const iconColor =
|
|
|
|
|
section.fileType ===
|
|
|
|
|
"pdf"
|
|
|
|
|
? "text-red-500"
|
|
|
|
|
: "text-blue-700"
|
|
|
|
|
|
2025-12-10 21:32:35 +09:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={
|
2026-01-24 07:14:11 +05:30
|
|
|
sectionKey
|
2025-12-10 21:32:35 +09:00
|
|
|
}
|
|
|
|
|
className="rounded-lg border border-border/60 bg-muted/30 overflow-hidden"
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={(
|
|
|
|
|
e,
|
|
|
|
|
) => {
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
setExpandedPdfSections(
|
|
|
|
|
(
|
|
|
|
|
prev,
|
|
|
|
|
) => ({
|
|
|
|
|
...prev,
|
2026-01-24 07:14:11 +05:30
|
|
|
[sectionKey]:
|
2025-12-10 21:32:35 +09:00
|
|
|
!isExpanded,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
}}
|
|
|
|
|
className="w-full flex items-center justify-between px-3 py-2 hover:bg-muted/50 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-01-24 07:14:11 +05:30
|
|
|
<Icon
|
|
|
|
|
className={`h-4 w-4 ${iconColor}`}
|
|
|
|
|
/>
|
|
|
|
|
<span className="text-xs font-medium truncate max-w-[200px]">
|
2025-12-10 21:32:35 +09:00
|
|
|
{
|
|
|
|
|
section.filename
|
|
|
|
|
}
|
|
|
|
|
</span>
|
|
|
|
|
<span className="text-[10px] text-muted-foreground">
|
|
|
|
|
(
|
|
|
|
|
{
|
|
|
|
|
charDisplay
|
|
|
|
|
}{" "}
|
|
|
|
|
chars)
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
{isExpanded ? (
|
|
|
|
|
<ChevronUp className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
{isExpanded && (
|
2026-01-09 17:20:02 +05:30
|
|
|
<div className="px-3 py-2 border-t border-border/40 max-h-48 overflow-y-auto bg-muted/30 scrollbar-thin">
|
2025-12-10 21:32:35 +09:00
|
|
|
<pre className="text-xs whitespace-pre-wrap text-foreground/80">
|
|
|
|
|
{
|
|
|
|
|
section.content
|
|
|
|
|
}
|
|
|
|
|
</pre>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
// Regular text section
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={`${message.id}-textsection-${partIndex}-${sectionIndex}`}
|
|
|
|
|
className={`prose prose-sm max-w-none break-words [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 ${
|
|
|
|
|
message.role ===
|
|
|
|
|
"user"
|
|
|
|
|
? "[&_*]:!text-primary-foreground prose-code:bg-white/20"
|
|
|
|
|
: "dark:prose-invert"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<ReactMarkdown>
|
|
|
|
|
{
|
|
|
|
|
section.content
|
|
|
|
|
}
|
|
|
|
|
</ReactMarkdown>
|
|
|
|
|
</div>
|
2025-12-07 19:56:31 +09:00
|
|
|
)
|
2025-12-10 21:32:35 +09:00
|
|
|
},
|
|
|
|
|
)}
|
2025-12-07 19:56:31 +09:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
part.type ===
|
|
|
|
|
"file"
|
|
|
|
|
) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={`${message.id}-file-${group.startIndex}-${partIndex}`}
|
|
|
|
|
className="mt-2"
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src={
|
|
|
|
|
(
|
|
|
|
|
part as {
|
|
|
|
|
url: string
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
.url
|
|
|
|
|
}
|
|
|
|
|
width={
|
|
|
|
|
200
|
|
|
|
|
}
|
|
|
|
|
height={
|
|
|
|
|
200
|
|
|
|
|
}
|
|
|
|
|
alt={`Uploaded diagram or image for AI analysis`}
|
|
|
|
|
className="rounded-lg border border-white/20"
|
|
|
|
|
style={{
|
|
|
|
|
objectFit:
|
|
|
|
|
"contain",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
},
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
},
|
2025-12-06 16:18:26 +09:00
|
|
|
)
|
2025-12-07 19:56:31 +09:00
|
|
|
})()
|
|
|
|
|
)}
|
2025-12-04 22:56:59 +09:00
|
|
|
{/* Action buttons for assistant messages */}
|
|
|
|
|
{message.role === "assistant" && (
|
|
|
|
|
<div className="flex items-center gap-1 mt-2">
|
|
|
|
|
{/* Copy button */}
|
|
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-06 12:46:40 +09:00
|
|
|
onClick={() =>
|
|
|
|
|
copyMessageToClipboard(
|
|
|
|
|
message.id,
|
|
|
|
|
getMessageTextContent(
|
|
|
|
|
message,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-04 22:56:59 +09:00
|
|
|
className={`p-1.5 rounded-lg transition-colors ${
|
2025-12-06 12:46:40 +09:00
|
|
|
copiedMessageId ===
|
|
|
|
|
message.id
|
2025-12-04 22:56:59 +09:00
|
|
|
? "text-green-600 bg-green-100"
|
|
|
|
|
: "text-muted-foreground/60 hover:text-foreground hover:bg-muted"
|
|
|
|
|
}`}
|
2025-12-06 12:46:40 +09:00
|
|
|
title={
|
|
|
|
|
copiedMessageId ===
|
|
|
|
|
message.id
|
2025-12-30 19:52:57 +08:00
|
|
|
? dict.chat.copied
|
|
|
|
|
: dict.chat.copyResponse
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-12-04 22:56:59 +09:00
|
|
|
>
|
2025-12-06 12:46:40 +09:00
|
|
|
{copiedMessageId ===
|
|
|
|
|
message.id ? (
|
2025-12-04 22:56:59 +09:00
|
|
|
<Check className="h-3.5 w-3.5" />
|
|
|
|
|
) : (
|
|
|
|
|
<Copy className="h-3.5 w-3.5" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
2025-12-07 19:36:09 +09:00
|
|
|
{/* Regenerate button - only on last assistant message, not for cached examples */}
|
2025-12-06 12:46:40 +09:00
|
|
|
{onRegenerate &&
|
2025-12-07 19:36:09 +09:00
|
|
|
isLastAssistantMessage &&
|
|
|
|
|
!message.parts?.some((p: any) =>
|
|
|
|
|
p.toolCallId?.startsWith(
|
|
|
|
|
"cached-",
|
|
|
|
|
),
|
|
|
|
|
) && (
|
2025-12-06 12:46:40 +09:00
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-06 12:46:40 +09:00
|
|
|
onClick={() =>
|
|
|
|
|
onRegenerate(
|
|
|
|
|
messageIndex,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
className="p-1.5 rounded-lg text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors"
|
2025-12-30 19:52:57 +08:00
|
|
|
title={
|
|
|
|
|
dict.chat.regenerate
|
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
>
|
|
|
|
|
<RotateCcw className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2025-12-05 21:15:02 +09:00
|
|
|
{/* Divider */}
|
|
|
|
|
<div className="w-px h-4 bg-border mx-1" />
|
|
|
|
|
{/* Thumbs up */}
|
|
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-06 12:46:40 +09:00
|
|
|
onClick={() =>
|
|
|
|
|
submitFeedback(
|
|
|
|
|
message.id,
|
|
|
|
|
"good",
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-05 21:15:02 +09:00
|
|
|
className={`p-1.5 rounded-lg transition-colors ${
|
2025-12-06 12:46:40 +09:00
|
|
|
feedback[message.id] ===
|
|
|
|
|
"good"
|
2025-12-05 21:15:02 +09:00
|
|
|
? "text-green-600 bg-green-100"
|
|
|
|
|
: "text-muted-foreground/60 hover:text-green-600 hover:bg-green-50"
|
|
|
|
|
}`}
|
2025-12-30 19:52:57 +08:00
|
|
|
title={dict.chat.goodResponse}
|
2025-12-05 21:15:02 +09:00
|
|
|
>
|
|
|
|
|
<ThumbsUp className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
|
|
|
|
{/* Thumbs down */}
|
|
|
|
|
<button
|
2025-12-06 16:18:26 +09:00
|
|
|
type="button"
|
2025-12-06 12:46:40 +09:00
|
|
|
onClick={() =>
|
|
|
|
|
submitFeedback(
|
|
|
|
|
message.id,
|
|
|
|
|
"bad",
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-05 21:15:02 +09:00
|
|
|
className={`p-1.5 rounded-lg transition-colors ${
|
2025-12-06 12:46:40 +09:00
|
|
|
feedback[message.id] ===
|
|
|
|
|
"bad"
|
2025-12-05 21:15:02 +09:00
|
|
|
? "text-red-600 bg-red-100"
|
|
|
|
|
: "text-muted-foreground/60 hover:text-red-600 hover:bg-red-50"
|
|
|
|
|
}`}
|
2025-12-30 19:52:57 +08:00
|
|
|
title={dict.chat.badResponse}
|
2025-12-05 21:15:02 +09:00
|
|
|
>
|
|
|
|
|
<ThumbsDown className="h-3.5 w-3.5" />
|
|
|
|
|
</button>
|
2025-12-04 22:56:59 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-12-03 21:49:34 +09:00
|
|
|
</div>
|
2025-11-29 12:39:35 +08:00
|
|
|
</div>
|
2025-12-06 12:46:40 +09:00
|
|
|
)
|
2025-12-03 21:49:34 +09:00
|
|
|
})}
|
|
|
|
|
</div>
|
2025-03-25 02:58:11 +00:00
|
|
|
)}
|
|
|
|
|
<div ref={messagesEndRef} />
|
|
|
|
|
</ScrollArea>
|
2025-12-06 12:46:40 +09:00
|
|
|
)
|
2025-03-25 02:58:11 +00:00
|
|
|
}
|