From 4a7a33ca4d9ee3ebce9d0be5e181e0d9bd176b91 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Mon, 10 Aug 2026 17:24:39 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(chat):=20=E4=BF=AE=E5=A4=8D=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=96=87=E4=BB=B6=E9=93=BE=E6=8E=A5=E8=B7=A8=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E8=B7=AF=E5=BE=84=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三个独立问题导致聊天里模型输出的本地文件链接"可点击但必报错": 1. URL 风格盘符路径 /D:/work/a.zip:裸路径分支缺少 file-url 分支 已有的前导斜杠剥离,Windows 端 Path::is_absolute() 为 false, 报 "The linked path is not valid on this device."。 补 URL_STYLE_DRIVE_PATH_PATTERN 剥离(parse 与 decode 两处)。 2. macOS 家目录路径 ~/release/a.zip:此前判为 relative,Rust 端 join 出 /~/... 报 "does not exist"。前端将 ~ 锚定路径 归为 absolute,Rust 端 absolute/file-url 源经 expand_tilde_path 展开;relative 源保持字面量,工作区内名为 "~" 的目录不受影响。 3. 压缩包(zip/7z/tar/gz 等)不在任何动作名单,双平台一律 "cannot be opened safely"。新增 ARCHIVE_EXTENSIONS 归入 reveal(文件管理器定位,不执行)。 chatFileLinks.ts 两端镜像逐字节同步;双端前端测试与 Rust 测试 补齐 /D:/、/d:/、~/、压缩包及字面 "~" 目录回归用例。 --- .../web/src/lib/chat/chatFileLinks.ts | 13 +++- .../web/test/chat-file-links.test.mjs | 4 + .../src/commands/workspace/chat_file_links.rs | 73 ++++++++++++++++++- .../src/lib/chat/messages/chatFileLinks.ts | 13 +++- .../test/chat/chat-file-links.test.mjs | 4 + 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/crates/agent-gateway/web/src/lib/chat/chatFileLinks.ts b/crates/agent-gateway/web/src/lib/chat/chatFileLinks.ts index 7cf63d3b9..ce46a77de 100644 --- a/crates/agent-gateway/web/src/lib/chat/chatFileLinks.ts +++ b/crates/agent-gateway/web/src/lib/chat/chatFileLinks.ts @@ -10,6 +10,12 @@ const WINDOWS_DRIVE_PATH_PATTERN = /^[a-zA-Z]:[\\/]/; const WINDOWS_UNC_PATH_PATTERN = /^(?:\\\\|\/\/)/; const FILE_URL_PATTERN = /^file:\/\//i; const ABSOLUTE_POSIX_PATH_PATTERN = /^\//; +// Home-anchored paths ("~" or "~/...") resolve on the conversation's host +// device, so they classify as absolute; the host expands the tilde. +const HOME_ANCHORED_PATH_PATTERN = /^~(?:\/|$)/; +// URL-style Windows drive paths ("/D:/work/a.ts") keep a leading slash that +// no filesystem accepts; strip it exactly like the file-url branch does. +const URL_STYLE_DRIVE_PATH_PATTERN = /^\/([a-zA-Z]:\/)/; const URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/; const LOCATION_FRAGMENT_PATTERN = /^#L([1-9]\d*)(?:-L?([1-9]\d*))?$/i; const LOCATION_SUFFIX_PATTERN = /:([1-9]\d*)(?::([1-9]\d*))?$/; @@ -40,7 +46,8 @@ function isAbsolutePath(path: string) { return ( WINDOWS_DRIVE_PATH_PATTERN.test(path) || WINDOWS_UNC_PATH_PATTERN.test(path) || - ABSOLUTE_POSIX_PATH_PATTERN.test(path) + ABSOLUTE_POSIX_PATH_PATTERN.test(path) || + HOME_ANCHORED_PATH_PATTERN.test(path) ); } @@ -137,7 +144,7 @@ export function parseChatFileLink(raw: string): ChatFileLink | null { } const { path, line, endLine, column } = parseTrailingLocation(input); - const normalized = normalizePath(path); + const normalized = normalizePath(path).replace(URL_STYLE_DRIVE_PATH_PATTERN, "$1"); if (isAbsolutePath(normalized)) { return createChatFileLink(normalized, "absolute", { line, endLine, column }); @@ -177,7 +184,7 @@ export function decodeChatFileLinkPayload(payload: string): ChatFileLink | null if (!path || (source !== "absolute" && source !== "relative" && source !== "file-url")) return null; - const normalized = normalizePath(path); + const normalized = normalizePath(path).replace(URL_STYLE_DRIVE_PATH_PATTERN, "$1"); const sourceMatches = source === "relative" ? isSafeRelativePath(normalized) && !isAbsolutePath(normalized) diff --git a/crates/agent-gateway/web/test/chat-file-links.test.mjs b/crates/agent-gateway/web/test/chat-file-links.test.mjs index e1b0b99f7..4c74babd5 100644 --- a/crates/agent-gateway/web/test/chat-file-links.test.mjs +++ b/crates/agent-gateway/web/test/chat-file-links.test.mjs @@ -41,6 +41,10 @@ const validCases = [ [String.raw`C:\work\src\a.ts`, { path: "C:/work/src/a.ts", source: "absolute" }], [String.raw`C:\\project\\file.ts`, { path: "C:/project/file.ts", source: "absolute" }], ["D:/other/a.ts", { path: "D:/other/a.ts", source: "absolute" }], + ["/D:/workspace/release/a.zip", { path: "D:/workspace/release/a.zip", source: "absolute" }], + ["/d:/workspace/release/a.zip", { path: "d:/workspace/release/a.zip", source: "absolute" }], + ["~/release/a.zip", { path: "~/release/a.zip", source: "absolute" }], + ["~/work/a.ts:12", { path: "~/work/a.ts", line: 12, source: "absolute" }], ["C:/work/src/a.ts:12", { path: "C:/work/src/a.ts", line: 12, source: "absolute" }], [ "C:/work/src/a.ts:12:4", diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs b/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs index fbea8edca..512ee2b40 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs @@ -167,6 +167,14 @@ const EXECUTABLE_EXTENSIONS: &[&str] = &[ "xpi", ]; +// Build artifacts the chat frequently links to (release archives, exports). +// They are neither editable text nor previewable, so reveal them in the host +// file manager instead of failing closed. +const ARCHIVE_EXTENSIONS: &[&str] = &[ + "7z", "bz2", "cab", "gz", "iso", "lz4", "lzma", "rar", "tar", "tbz2", "tgz", "txz", "xz", + "zip", "zst", +]; + #[derive(Debug, Clone, Copy, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] pub enum ChatFileLinkErrorCode { @@ -382,7 +390,14 @@ fn build_chat_file_link_plan( )); } - let raw_target = PathBuf::from(path.trim()); + // Home-anchored link paths ("~/release/a.zip") expand against the host + // home directory, mirroring the frontend's absolute classification. + // Relative paths keep their literal form so a workspace entry named "~" + // still joins against the conversation workdir. + let raw_target = match source { + "absolute" | "file-url" => expand_tilde_path(path.trim()), + _ => PathBuf::from(path.trim()), + }; let candidate = match source { "relative" if !raw_target.is_absolute() => conversation_workdir.join(raw_target), "absolute" | "file-url" if raw_target.is_absolute() => raw_target, @@ -475,7 +490,7 @@ fn build_chat_file_link_plan( "editor" } else if has_extension(&target, PREVIEW_EXTENSIONS) { "preview" - } else if executable { + } else if executable || has_extension(&target, ARCHIVE_EXTENSIONS) { "revealed" } else if is_probably_text(&target) { "editor" @@ -741,6 +756,60 @@ mod tests { fs::remove_dir_all(root).expect("remove temp workspace"); } + #[test] + fn archive_files_reveal_in_the_file_manager_instead_of_failing_closed() { + let root = temp_workspace(); + for name in ["release-1.0.4.zip", "backup.tar", "export.7z", "logs.tgz"] { + fs::write(root.join(name), [0x50_u8, 0x4b, 0x03, 0x04]).expect("write archive"); + let planned = plan(&root, name, "relative"); + assert_eq!(planned.response.action, "revealed", "{name}"); + assert_eq!(planned.system_mode, Some("reveal"), "{name}"); + } + fs::remove_dir_all(root).expect("remove temp workspace"); + } + + #[test] + fn home_anchored_absolute_links_expand_against_the_host_home() { + let Some(home) = dirs::home_dir() else { + return; + }; + let suffix = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("clock") + .as_nanos(); + let home_dir_name = format!("liveagent-chat-file-links-home-{suffix}"); + let home_target_dir = home.join(&home_dir_name); + if fs::create_dir_all(&home_target_dir).is_err() { + return; + } + fs::write(home_target_dir.join("notes.md"), "# home\n").expect("write home file"); + let root = temp_workspace(); + + let planned = build_chat_file_link_plan( + "conversation-test", + &root.to_string_lossy(), + &format!("~/{home_dir_name}/notes.md"), + "absolute", + None, + None, + None, + false, + ) + .expect("home-anchored link must resolve"); + assert_eq!(planned.response.action, "editor"); + assert!(planned.response.outside_workspace); + + // A workspace entry literally named "~" keeps joining relatively. + fs::create_dir(root.join("~")).expect("create literal tilde dir"); + fs::write(root.join("~/inner.md"), "# inner\n").expect("write literal tilde file"); + let relative = plan(&root, "~/inner.md", "relative"); + assert_eq!(relative.response.action, "editor"); + assert!(!relative.response.outside_workspace); + + fs::remove_dir_all(root).expect("remove temp workspace"); + fs::remove_dir_all(home_target_dir).expect("remove home dir"); + } + #[test] fn workspace_directories_use_file_tree_first_and_support_a_safe_manager_fallback() { let root = temp_workspace(); diff --git a/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts b/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts index 7cf63d3b9..ce46a77de 100644 --- a/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts +++ b/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts @@ -10,6 +10,12 @@ const WINDOWS_DRIVE_PATH_PATTERN = /^[a-zA-Z]:[\\/]/; const WINDOWS_UNC_PATH_PATTERN = /^(?:\\\\|\/\/)/; const FILE_URL_PATTERN = /^file:\/\//i; const ABSOLUTE_POSIX_PATH_PATTERN = /^\//; +// Home-anchored paths ("~" or "~/...") resolve on the conversation's host +// device, so they classify as absolute; the host expands the tilde. +const HOME_ANCHORED_PATH_PATTERN = /^~(?:\/|$)/; +// URL-style Windows drive paths ("/D:/work/a.ts") keep a leading slash that +// no filesystem accepts; strip it exactly like the file-url branch does. +const URL_STYLE_DRIVE_PATH_PATTERN = /^\/([a-zA-Z]:\/)/; const URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/; const LOCATION_FRAGMENT_PATTERN = /^#L([1-9]\d*)(?:-L?([1-9]\d*))?$/i; const LOCATION_SUFFIX_PATTERN = /:([1-9]\d*)(?::([1-9]\d*))?$/; @@ -40,7 +46,8 @@ function isAbsolutePath(path: string) { return ( WINDOWS_DRIVE_PATH_PATTERN.test(path) || WINDOWS_UNC_PATH_PATTERN.test(path) || - ABSOLUTE_POSIX_PATH_PATTERN.test(path) + ABSOLUTE_POSIX_PATH_PATTERN.test(path) || + HOME_ANCHORED_PATH_PATTERN.test(path) ); } @@ -137,7 +144,7 @@ export function parseChatFileLink(raw: string): ChatFileLink | null { } const { path, line, endLine, column } = parseTrailingLocation(input); - const normalized = normalizePath(path); + const normalized = normalizePath(path).replace(URL_STYLE_DRIVE_PATH_PATTERN, "$1"); if (isAbsolutePath(normalized)) { return createChatFileLink(normalized, "absolute", { line, endLine, column }); @@ -177,7 +184,7 @@ export function decodeChatFileLinkPayload(payload: string): ChatFileLink | null if (!path || (source !== "absolute" && source !== "relative" && source !== "file-url")) return null; - const normalized = normalizePath(path); + const normalized = normalizePath(path).replace(URL_STYLE_DRIVE_PATH_PATTERN, "$1"); const sourceMatches = source === "relative" ? isSafeRelativePath(normalized) && !isAbsolutePath(normalized) diff --git a/crates/agent-gui/test/chat/chat-file-links.test.mjs b/crates/agent-gui/test/chat/chat-file-links.test.mjs index 2ba3a2a57..68e075e87 100644 --- a/crates/agent-gui/test/chat/chat-file-links.test.mjs +++ b/crates/agent-gui/test/chat/chat-file-links.test.mjs @@ -17,6 +17,10 @@ const validCases = [ [String.raw`C:\work\src\a.ts`, { path: "C:/work/src/a.ts", source: "absolute" }], [String.raw`C:\\project\\file.ts`, { path: "C:/project/file.ts", source: "absolute" }], ["D:/other/a.ts", { path: "D:/other/a.ts", source: "absolute" }], + ["/D:/workspace/release/a.zip", { path: "D:/workspace/release/a.zip", source: "absolute" }], + ["/d:/workspace/release/a.zip", { path: "d:/workspace/release/a.zip", source: "absolute" }], + ["~/release/a.zip", { path: "~/release/a.zip", source: "absolute" }], + ["~/work/a.ts:12", { path: "~/work/a.ts", line: 12, source: "absolute" }], ["C:/work/src/a.ts:12", { path: "C:/work/src/a.ts", line: 12, source: "absolute" }], [ "C:/work/src/a.ts:12:4", From 23472bf0ff4864d5d5b6f19fb0166b0296bcd490 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Mon, 10 Aug 2026 17:38:31 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor(ui):=20chatFileLinks=20=E6=8A=BD?= =?UTF-8?q?=E7=A6=BB=E5=88=B0=E5=85=B1=E4=BA=AB=E5=B1=82=E6=B6=88=E9=99=A4?= =?UTF-8?q?=E5=8F=8C=E7=AB=AF=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chatFileLinks.ts 此前在 agent-gui 与 gateway/web 各存一份、 靠人工逐字节同步。迁移到 crates/agent-ui/src/lib/chat/(共享 代码唯一归宿),两端宿主与 agent-ui 内部引用统一改为 @liveagent/ui/lib/chat/chatFileLinks,宿主副本删除。 - agent-ui 内 Markdown/ThinkingActivity/openChatFileLink 改 相对导入,不再经 @liveagent/app 反查宿主 - 双端 chat-file-links 测试改为加载共享模块(按惯例测试保留 双份,守护各自宿主的 prop 链) --- .../agent-gateway/web/src/app/GatewayApp.tsx | 2 +- .../web/src/components/GatewayTranscript.tsx | 2 +- .../web/src/pages/chat/AssistantBubble.tsx | 2 +- .../chat/assistant-bubble/RoundContent.tsx | 2 +- .../web/test/chat-file-links.test.mjs | 2 +- .../agent-gui/src/lib/chat/chatFileLinks.ts | 1 - .../src/lib/chat/messages/chatFileLinks.ts | 208 ------------------ crates/agent-gui/src/pages/ChatPage.tsx | 2 +- .../pages/chat/components/AssistantBubble.tsx | 2 +- .../assistant-bubble/RoundContent.tsx | 2 +- .../chat/transcript/AssistantActivityRow.tsx | 2 +- .../chat/transcript/AssistantRenderUnit.tsx | 2 +- .../pages/chat/transcript/TranscriptList.tsx | 2 +- .../pages/chat/transcript/transcriptTypes.ts | 2 +- .../test/chat/chat-file-links.test.mjs | 2 +- crates/agent-ui/src/components/Markdown.tsx | 2 +- .../src/components/chat/ThinkingActivity.tsx | 2 +- .../src/lib/chat/chatFileLinks.ts | 0 .../agent-ui/src/lib/chat/openChatFileLink.ts | 2 +- 19 files changed, 16 insertions(+), 225 deletions(-) delete mode 100644 crates/agent-gui/src/lib/chat/chatFileLinks.ts delete mode 100644 crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts rename crates/{agent-gateway/web => agent-ui}/src/lib/chat/chatFileLinks.ts (100%) diff --git a/crates/agent-gateway/web/src/app/GatewayApp.tsx b/crates/agent-gateway/web/src/app/GatewayApp.tsx index 6152bacd8..fc7fcb635 100644 --- a/crates/agent-gateway/web/src/app/GatewayApp.tsx +++ b/crates/agent-gateway/web/src/app/GatewayApp.tsx @@ -48,7 +48,7 @@ import { } from "react"; import { ChevronDown, PanelRightClose, PanelRightOpen, Terminal } from "@/components/icons"; import { registerAskUserQuestionAnswerHandler } from "@/lib/chat/askUserQuestionBridge"; -import type { ChatFileLink } from "@/lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { ChatHistorySummary } from "@/lib/chat/chatHistory"; import { buildModelOptions } from "@/lib/chat/chatPageHelpers"; import type { HistoryMessageRef } from "@/lib/chat/conversationState"; diff --git a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx index ce23d958f..6b7a9c91a 100644 --- a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx +++ b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx @@ -39,7 +39,7 @@ import { useRef, useState, } from "react"; -import type { ChatFileLink } from "@/lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import { normalizeLiveToolStatus, VIBING_STATUS } from "@/lib/chat/chatPageHelpers"; import type { HistoryMessageRef } from "@/lib/chat/conversationState"; import { getRoundText } from "@/lib/chat/uiMessages"; diff --git a/crates/agent-gateway/web/src/pages/chat/AssistantBubble.tsx b/crates/agent-gateway/web/src/pages/chat/AssistantBubble.tsx index c7bb749d1..3949b1065 100644 --- a/crates/agent-gateway/web/src/pages/chat/AssistantBubble.tsx +++ b/crates/agent-gateway/web/src/pages/chat/AssistantBubble.tsx @@ -2,7 +2,7 @@ import { AssistantAvatar } from "@liveagent/ui/components/chat/AssistantAvatar"; import { ChangedFilesCard } from "@liveagent/ui/components/chat/ChangedFilesCard"; import { memo, useMemo } from "react"; import { collectChangedFiles } from "../../lib/chat/changedFiles"; -import type { ChatFileLink } from "../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { UiRound } from "../../lib/chat/uiMessages"; import { RoundContent } from "./assistant-bubble/RoundContent"; diff --git a/crates/agent-gateway/web/src/pages/chat/assistant-bubble/RoundContent.tsx b/crates/agent-gateway/web/src/pages/chat/assistant-bubble/RoundContent.tsx index 36d9ff969..348a343dc 100644 --- a/crates/agent-gateway/web/src/pages/chat/assistant-bubble/RoundContent.tsx +++ b/crates/agent-gateway/web/src/pages/chat/assistant-bubble/RoundContent.tsx @@ -12,7 +12,7 @@ import { useLocale } from "@liveagent/ui/i18n/index"; import { isTaskToolBlock } from "@liveagent/ui/lib/chat/taskProgress"; import { memo, useMemo, useState } from "react"; import { ChevronRight, RefreshCw } from "../../../components/icons"; -import type { ChatFileLink } from "../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import { normalizeLiveToolStatus, VIBING_STATUS } from "../../../lib/chat/chatPageHelpers"; import type { RetryAttemptRecord } from "../../../lib/chat/transcript/types"; import type { UiRound } from "../../../lib/chat/uiMessages"; diff --git a/crates/agent-gateway/web/test/chat-file-links.test.mjs b/crates/agent-gateway/web/test/chat-file-links.test.mjs index 4c74babd5..b52569c10 100644 --- a/crates/agent-gateway/web/test/chat-file-links.test.mjs +++ b/crates/agent-gateway/web/test/chat-file-links.test.mjs @@ -34,7 +34,7 @@ const { decodeChatFileLinkPayload, encodeChatFileLink, parseChatFileLink, -} = loader.loadModule("src/lib/chat/chatFileLinks.ts"); +} = loader.loadModule("@liveagent/ui/lib/chat/chatFileLinks.ts"); const validCases = [ ["C:/work/src/a.ts", { path: "C:/work/src/a.ts", source: "absolute" }], diff --git a/crates/agent-gui/src/lib/chat/chatFileLinks.ts b/crates/agent-gui/src/lib/chat/chatFileLinks.ts deleted file mode 100644 index fd3608ebe..000000000 --- a/crates/agent-gui/src/lib/chat/chatFileLinks.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./messages/chatFileLinks"; diff --git a/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts b/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts deleted file mode 100644 index ce46a77de..000000000 --- a/crates/agent-gui/src/lib/chat/messages/chatFileLinks.ts +++ /dev/null @@ -1,208 +0,0 @@ -export type ChatFileLink = { - path: string; - line?: number; - endLine?: number; - column?: number; - source: "absolute" | "relative" | "file-url"; -}; - -const WINDOWS_DRIVE_PATH_PATTERN = /^[a-zA-Z]:[\\/]/; -const WINDOWS_UNC_PATH_PATTERN = /^(?:\\\\|\/\/)/; -const FILE_URL_PATTERN = /^file:\/\//i; -const ABSOLUTE_POSIX_PATH_PATTERN = /^\//; -// Home-anchored paths ("~" or "~/...") resolve on the conversation's host -// device, so they classify as absolute; the host expands the tilde. -const HOME_ANCHORED_PATH_PATTERN = /^~(?:\/|$)/; -// URL-style Windows drive paths ("/D:/work/a.ts") keep a leading slash that -// no filesystem accepts; strip it exactly like the file-url branch does. -const URL_STYLE_DRIVE_PATH_PATTERN = /^\/([a-zA-Z]:\/)/; -const URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/; -const LOCATION_FRAGMENT_PATTERN = /^#L([1-9]\d*)(?:-L?([1-9]\d*))?$/i; -const LOCATION_SUFFIX_PATTERN = /:([1-9]\d*)(?::([1-9]\d*))?$/; -const INTERNAL_PAYLOAD_VERSION = "1"; -const MAX_LOCATION_VALUE = 0xffff_ffff; - -function parseLocationNumber(value: string) { - const parsed = Number(value); - return Number.isSafeInteger(parsed) && parsed > 0 && parsed <= MAX_LOCATION_VALUE ? parsed : null; -} - -function safeDecode(value: string) { - try { - return decodeURIComponent(value); - } catch { - return value; - } -} - -function normalizePath(path: string) { - const decoded = safeDecode(path); - const isUnc = /^(?:\\\\|\/\/[^/])/.test(decoded); - const collapsed = decoded.replace(/\\/g, "/").replace(/\/{2,}/g, "/"); - return isUnc ? `//${collapsed.replace(/^\/+/, "")}` : collapsed; -} - -function isAbsolutePath(path: string) { - return ( - WINDOWS_DRIVE_PATH_PATTERN.test(path) || - WINDOWS_UNC_PATH_PATTERN.test(path) || - ABSOLUTE_POSIX_PATH_PATTERN.test(path) || - HOME_ANCHORED_PATH_PATTERN.test(path) - ); -} - -function parseTrailingLocation(value: string) { - let path = value.trim(); - let line: number | undefined; - let endLine: number | undefined; - let column: number | undefined; - - const hashIndex = path.lastIndexOf("#"); - const hashMatch = hashIndex >= 0 ? path.slice(hashIndex).match(LOCATION_FRAGMENT_PATTERN) : null; - if (hashMatch) { - line = parseLocationNumber(hashMatch[1]) ?? undefined; - if (line === undefined) return { path: "", line, endLine, column }; - if (hashMatch[2]) { - endLine = parseLocationNumber(hashMatch[2]) ?? undefined; - if (endLine === undefined) return { path: "", line, endLine, column }; - } - path = path.slice(0, hashIndex); - } - - const lineMatch = line === undefined ? path.match(LOCATION_SUFFIX_PATTERN) : null; - if (lineMatch?.index !== undefined) { - line = parseLocationNumber(lineMatch[1]) ?? undefined; - if (line === undefined) return { path: "", line, endLine, column }; - if (lineMatch[2]) { - column = parseLocationNumber(lineMatch[2]) ?? undefined; - if (column === undefined) return { path: "", line, endLine, column }; - } - path = path.slice(0, lineMatch.index); - } - - return { path, line, endLine, column }; -} - -function createChatFileLink( - path: string, - source: ChatFileLink["source"], - location: Omit, -): ChatFileLink { - return { - path, - ...(location.line === undefined ? {} : { line: location.line }), - ...(location.endLine === undefined ? {} : { endLine: location.endLine }), - ...(location.column === undefined ? {} : { column: location.column }), - source, - }; -} - -function isSafeRelativePath(path: string) { - return ( - Boolean(path) && - path !== "." && - path !== ".." && - !path.startsWith("#") && - !path.includes("\0") && - !path.includes("\n") && - !path.includes("\r") && - !URI_SCHEME_PATTERN.test(path) - ); -} - -export function parseChatFileLink(raw: string): ChatFileLink | null { - const input = raw.trim(); - if (!input) return null; - - if (FILE_URL_PATTERN.test(input)) { - try { - const url = new URL(input); - if (url.protocol !== "file:") return null; - const hashLocation = url.hash.match(LOCATION_FRAGMENT_PATTERN); - const hashLine = hashLocation ? parseLocationNumber(hashLocation[1]) : null; - const hashEndLine = hashLocation?.[2] ? parseLocationNumber(hashLocation[2]) : null; - if (hashLocation && (hashLine === null || (hashLocation[2] && hashEndLine === null))) { - return null; - } - const locationFromHash = hashLocation - ? { - line: hashLine ?? undefined, - ...(hashEndLine === null ? {} : { endLine: hashEndLine }), - } - : {}; - const pathWithHost = url.host ? `//${safeDecode(url.host)}${url.pathname}` : url.pathname; - const trailing = parseTrailingLocation(pathWithHost); - const normalized = normalizePath(trailing.path).replace(/^\/([a-zA-Z]:)/, "$1"); - if (!normalized || !isAbsolutePath(normalized)) return null; - return createChatFileLink(normalized, "file-url", { - ...trailing, - ...locationFromHash, - }); - } catch { - return null; - } - } - - const { path, line, endLine, column } = parseTrailingLocation(input); - const normalized = normalizePath(path).replace(URL_STYLE_DRIVE_PATH_PATTERN, "$1"); - - if (isAbsolutePath(normalized)) { - return createChatFileLink(normalized, "absolute", { line, endLine, column }); - } - - if (isSafeRelativePath(normalized)) { - return createChatFileLink(normalized, "relative", { line, endLine, column }); - } - - return null; -} - -export function isChatFileLinkTarget(raw: string) { - return Boolean(parseChatFileLink(raw)); -} - -export function encodeChatFileLink(link: ChatFileLink) { - const params = new URLSearchParams(); - params.set("v", INTERNAL_PAYLOAD_VERSION); - params.set("path", link.path); - if (link.line !== undefined) params.set("line", String(link.line)); - if (link.endLine !== undefined) params.set("endLine", String(link.endLine)); - if (link.column !== undefined) params.set("column", String(link.column)); - params.set("source", link.source); - return `liveagent-file:${params.toString()}`; -} - -export function decodeChatFileLinkPayload(payload: string): ChatFileLink | null { - const params = new URLSearchParams(payload); - const allowedKeys = new Set(["v", "path", "line", "endLine", "column", "source"]); - for (const key of params.keys()) { - if (!allowedKeys.has(key) || params.getAll(key).length !== 1) return null; - } - if (params.get("v") !== INTERNAL_PAYLOAD_VERSION) return null; - const path = params.get("path") ?? ""; - const source = params.get("source"); - if (!path || (source !== "absolute" && source !== "relative" && source !== "file-url")) - return null; - - const normalized = normalizePath(path).replace(URL_STYLE_DRIVE_PATH_PATTERN, "$1"); - const sourceMatches = - source === "relative" - ? isSafeRelativePath(normalized) && !isAbsolutePath(normalized) - : isAbsolutePath(normalized); - if (!sourceMatches) return null; - - const parseLocationValue = (key: "line" | "endLine" | "column") => { - const value = params.get(key); - if (value === null) return undefined; - if (!/^[1-9]\d*$/.test(value)) return null; - return parseLocationNumber(value); - }; - const line = parseLocationValue("line"); - const endLine = parseLocationValue("endLine"); - const column = parseLocationValue("column"); - if (line === null || endLine === null || column === null) return null; - if ((endLine !== undefined || column !== undefined) && line === undefined) return null; - if (line !== undefined && endLine !== undefined && endLine < line) return null; - - return createChatFileLink(normalized, source, { line, endLine, column }); -} diff --git a/crates/agent-gui/src/pages/ChatPage.tsx b/crates/agent-gui/src/pages/ChatPage.tsx index 335cc67c7..4bab2e92d 100644 --- a/crates/agent-gui/src/pages/ChatPage.tsx +++ b/crates/agent-gui/src/pages/ChatPage.tsx @@ -56,7 +56,7 @@ import { WorkspaceCloneTaskOverlayAdapter } from "../agent-ui-adapters/workspace import { PanelRightClose, PanelRightOpen } from "../components/icons"; import { MacOsTitleBarToggle } from "../components/MacOsTitleBarSpacer"; import type { AppUpdateController } from "../lib/appUpdates"; -import type { ChatFileLink } from "../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { CompactionStatus } from "../lib/chat/compaction/types"; import { buildRequestContext, diff --git a/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx b/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx index 536641ee7..0a27d1829 100644 --- a/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx +++ b/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx @@ -6,7 +6,7 @@ import { } from "@liveagent/ui/components/chat/AssistantStatus"; import { UsagePanel } from "@liveagent/ui/components/chat/UsagePanel"; import { memo, type ReactNode } from "react"; -import type { ChatFileLink } from "../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { RetryAttemptRecord } from "../../../lib/chat/conversation/liveTranscriptStore"; import { VIBING_STATUS } from "../../../lib/chat/page/chatPageHelpers"; import type { AssistantUnitRow } from "../transcript/rowModel"; diff --git a/crates/agent-gui/src/pages/chat/components/assistant-bubble/RoundContent.tsx b/crates/agent-gui/src/pages/chat/components/assistant-bubble/RoundContent.tsx index 01adf9834..979fdd608 100644 --- a/crates/agent-gui/src/pages/chat/components/assistant-bubble/RoundContent.tsx +++ b/crates/agent-gui/src/pages/chat/components/assistant-bubble/RoundContent.tsx @@ -5,7 +5,7 @@ import { Markdown } from "@liveagent/ui/components/Markdown"; import { useLocale } from "@liveagent/ui/i18n/index"; import { memo, type ReactNode, useState } from "react"; import { ChevronRight, RefreshCw } from "../../../../components/icons"; -import type { ChatFileLink } from "../../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { RetryAttemptRecord } from "../../../../lib/chat/conversation/liveTranscriptStore"; import type { GroupedRoundBlock } from "./assistantBubbleUtils"; import { MemoToolCallItem } from "./ToolCallItem"; diff --git a/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx b/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx index c8c00483a..e3b90e532 100644 --- a/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx +++ b/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx @@ -1,6 +1,6 @@ import { memo } from "react"; -import type { ChatFileLink } from "../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { HistoryMessageRef } from "../../../lib/chat/conversation/conversationState"; import type { RetryAttemptRecord } from "../../../lib/chat/conversation/liveTranscriptStore"; import type { PendingUploadedFile } from "../../../lib/chat/messages/uploadedFiles"; diff --git a/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx b/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx index e994a6f46..b8f34cceb 100644 --- a/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx +++ b/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx @@ -1,6 +1,6 @@ import { ChangedFilesCard } from "@liveagent/ui/components/chat/ChangedFilesCard"; import { memo, useMemo } from "react"; -import type { ChatFileLink } from "../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { HistoryMessageRef } from "../../../lib/chat/conversation/conversationState"; import type { RetryAttemptRecord } from "../../../lib/chat/conversation/liveTranscriptStore"; import { collectChangedFiles } from "../../../lib/chat/messages/changedFiles"; diff --git a/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx b/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx index 70b628411..1a14d0224 100644 --- a/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx +++ b/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx @@ -21,7 +21,7 @@ import { useSyncExternalStore, } from "react"; import { CheckCircle2, ChevronDown } from "../../../components/icons"; -import type { ChatFileLink } from "../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { HistoryMessageRef, RenderSummaryCard, diff --git a/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts b/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts index 3f462b171..09525c354 100644 --- a/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts +++ b/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts @@ -1,7 +1,7 @@ import type { ScrollFollowHandle } from "@liveagent/ui/lib/chat-scroll/useScrollFollow"; import type { GitClient } from "@liveagent/ui/lib/git/types"; import type { MutableRefObject } from "react"; -import type { ChatFileLink } from "../../../lib/chat/chatFileLinks"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { HistoryMessageRef, RenderTimelineItem, diff --git a/crates/agent-gui/test/chat/chat-file-links.test.mjs b/crates/agent-gui/test/chat/chat-file-links.test.mjs index 68e075e87..b5aa0860e 100644 --- a/crates/agent-gui/test/chat/chat-file-links.test.mjs +++ b/crates/agent-gui/test/chat/chat-file-links.test.mjs @@ -10,7 +10,7 @@ const { decodeChatFileLinkPayload, encodeChatFileLink, parseChatFileLink, -} = loader.loadModule("src/lib/chat/messages/chatFileLinks.ts"); +} = loader.loadModule("@liveagent/ui/lib/chat/chatFileLinks.ts"); const validCases = [ ["C:/work/src/a.ts", { path: "C:/work/src/a.ts", source: "absolute" }], diff --git a/crates/agent-ui/src/components/Markdown.tsx b/crates/agent-ui/src/components/Markdown.tsx index 194ee5671..1315f53c8 100644 --- a/crates/agent-ui/src/components/Markdown.tsx +++ b/crates/agent-ui/src/components/Markdown.tsx @@ -11,7 +11,7 @@ import { decodeChatFileLinkPayload, encodeChatFileLink, parseChatFileLink, -} from "@liveagent/app/lib/chat/chatFileLinks"; +} from "../lib/chat/chatFileLinks"; import { openUrl } from "@liveagent/app/shims/tauriOpener"; import { useLocale } from "@liveagent/ui/i18n/index"; import { cjk } from "@streamdown/cjk"; diff --git a/crates/agent-ui/src/components/chat/ThinkingActivity.tsx b/crates/agent-ui/src/components/chat/ThinkingActivity.tsx index bcc313fb2..4466ebdf7 100644 --- a/crates/agent-ui/src/components/chat/ThinkingActivity.tsx +++ b/crates/agent-ui/src/components/chat/ThinkingActivity.tsx @@ -1,5 +1,5 @@ import { ChevronRight, Lightbulb } from "@liveagent/app/components/icons"; -import type { ChatFileLink } from "@liveagent/app/lib/chat/chatFileLinks"; +import type { ChatFileLink } from "../../lib/chat/chatFileLinks"; import { useLocale } from "@liveagent/ui/i18n/index"; import { useEffect, useRef, useState } from "react"; import { Markdown } from "../Markdown"; diff --git a/crates/agent-gateway/web/src/lib/chat/chatFileLinks.ts b/crates/agent-ui/src/lib/chat/chatFileLinks.ts similarity index 100% rename from crates/agent-gateway/web/src/lib/chat/chatFileLinks.ts rename to crates/agent-ui/src/lib/chat/chatFileLinks.ts diff --git a/crates/agent-ui/src/lib/chat/openChatFileLink.ts b/crates/agent-ui/src/lib/chat/openChatFileLink.ts index 200d2d202..fa751eb36 100644 --- a/crates/agent-ui/src/lib/chat/openChatFileLink.ts +++ b/crates/agent-ui/src/lib/chat/openChatFileLink.ts @@ -1,4 +1,4 @@ -import type { ChatFileLink } from "@liveagent/app/lib/chat/chatFileLinks"; +import type { ChatFileLink } from "./chatFileLinks"; import { invoke } from "@liveagent/app/shims/tauriCore"; export type OpenChatFileLinkParams = ChatFileLink & { From 735daf86a0d7bf4ad559857162881eb8fc874f04 Mon Sep 17 00:00:00 2001 From: su-fen <715041@qq.com> Date: Mon, 10 Aug 2026 20:33:39 +0800 Subject: [PATCH 3/3] =?UTF-8?q?style(ui):=20=E5=90=88=E5=B9=B6=E5=90=8E=20?= =?UTF-8?q?import=20=E6=8E=92=E5=BA=8F=E4=BF=AE=E5=A4=8D=20biome=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/agent-gateway/web/src/app/GatewayApp.tsx | 2 +- crates/agent-gateway/web/src/components/GatewayTranscript.tsx | 2 +- crates/agent-gui/src/pages/ChatPage.tsx | 2 +- crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx | 2 +- .../src/pages/chat/transcript/AssistantActivityRow.tsx | 3 +-- .../src/pages/chat/transcript/AssistantRenderUnit.tsx | 2 +- crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx | 2 +- crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/agent-gateway/web/src/app/GatewayApp.tsx b/crates/agent-gateway/web/src/app/GatewayApp.tsx index 03e952f8c..42a669e1d 100644 --- a/crates/agent-gateway/web/src/app/GatewayApp.tsx +++ b/crates/agent-gateway/web/src/app/GatewayApp.tsx @@ -20,6 +20,7 @@ import { useConfirmDialog } from "@liveagent/ui/components/ui/confirm-dialog"; import { ScrollArea } from "@liveagent/ui/components/ui/scroll-area"; import { WorkspaceOverlayHost } from "@liveagent/ui/components/workspace-editor/WorkspaceOverlayHost"; import { LocaleContext, t as translate } from "@liveagent/ui/i18n/index"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import { normalizeLogicalLineEndings } from "@liveagent/ui/lib/chat/composerText"; import { openChatFileLink } from "@liveagent/ui/lib/chat/openChatFileLink"; import { queuedChatTurnHasContent } from "@liveagent/ui/lib/chat/queuedChatTurn"; @@ -52,7 +53,6 @@ import { } from "react"; import { ChevronDown, PanelRightClose, PanelRightOpen, Terminal } from "@/components/icons"; import { registerAskUserQuestionAnswerHandler } from "@/lib/chat/askUserQuestionBridge"; -import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { ChatHistorySummary } from "@/lib/chat/chatHistory"; import { buildModelOptions } from "@/lib/chat/chatPageHelpers"; import type { HistoryMessageRef } from "@/lib/chat/conversationState"; diff --git a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx index 3226a998d..3232e8795 100644 --- a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx +++ b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx @@ -15,6 +15,7 @@ import { } from "@liveagent/ui/components/chat/TranscriptMessageActions"; import { Markdown } from "@liveagent/ui/components/Markdown"; import { useLocale } from "@liveagent/ui/i18n/LocaleContext"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import { getUploadedImagePreviewCacheKey, loadUploadedImagePreview, @@ -47,7 +48,6 @@ import { useRef, useState, } from "react"; -import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import { normalizeLiveToolStatus, VIBING_STATUS } from "@/lib/chat/chatPageHelpers"; import type { HistoryMessageRef } from "@/lib/chat/conversationState"; import { getRoundText } from "@/lib/chat/uiMessages"; diff --git a/crates/agent-gui/src/pages/ChatPage.tsx b/crates/agent-gui/src/pages/ChatPage.tsx index 835706fc2..2bc69452a 100644 --- a/crates/agent-gui/src/pages/ChatPage.tsx +++ b/crates/agent-gui/src/pages/ChatPage.tsx @@ -25,6 +25,7 @@ import { useConfirmDialog } from "@liveagent/ui/components/ui/confirm-dialog"; import { WorkspaceOverlayHost } from "@liveagent/ui/components/workspace-editor/WorkspaceOverlayHost"; import { useLocale } from "@liveagent/ui/i18n/index"; import { getAutomationState, useAutomation } from "@liveagent/ui/lib/automation/index"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import { openChatFileLink } from "@liveagent/ui/lib/chat/openChatFileLink"; import { selectLatestTaskProgress } from "@liveagent/ui/lib/chat/taskProgress"; import type { ScrollFollowHandle } from "@liveagent/ui/lib/chat-scroll/useScrollFollow"; @@ -58,7 +59,6 @@ import { WorkspaceCloneTaskOverlayAdapter } from "../agent-ui-adapters/workspace import { PanelRightClose, PanelRightOpen } from "../components/icons"; import { MacOsTitleBarToggle } from "../components/MacOsTitleBarSpacer"; import type { AppUpdateController } from "../lib/appUpdates"; -import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { CompactionStatus } from "../lib/chat/compaction/types"; import { buildRequestContext, diff --git a/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx b/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx index 18fdbc30c..ffbe9d096 100644 --- a/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx +++ b/crates/agent-gui/src/pages/chat/components/AssistantBubble.tsx @@ -9,8 +9,8 @@ import { RoundBlockContent, } from "@liveagent/ui/components/chat/assistant-bubble/RoundContent"; import { UsagePanel } from "@liveagent/ui/components/chat/UsagePanel"; -import { memo, type ReactNode } from "react"; import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; +import { memo, type ReactNode } from "react"; import type { RetryAttemptRecord } from "../../../lib/chat/conversation/liveTranscriptStore"; import { VIBING_STATUS } from "../../../lib/chat/page/chatPageHelpers"; import type { AssistantUnitRow } from "../transcript/rowModel"; diff --git a/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx b/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx index e3b90e532..fc082170e 100644 --- a/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx +++ b/crates/agent-gui/src/pages/chat/transcript/AssistantActivityRow.tsx @@ -1,6 +1,5 @@ -import { memo } from "react"; - import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; +import { memo } from "react"; import type { HistoryMessageRef } from "../../../lib/chat/conversation/conversationState"; import type { RetryAttemptRecord } from "../../../lib/chat/conversation/liveTranscriptStore"; import type { PendingUploadedFile } from "../../../lib/chat/messages/uploadedFiles"; diff --git a/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx b/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx index b8f34cceb..8a1c805fa 100644 --- a/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx +++ b/crates/agent-gui/src/pages/chat/transcript/AssistantRenderUnit.tsx @@ -1,6 +1,6 @@ import { ChangedFilesCard } from "@liveagent/ui/components/chat/ChangedFilesCard"; -import { memo, useMemo } from "react"; import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; +import { memo, useMemo } from "react"; import type { HistoryMessageRef } from "../../../lib/chat/conversation/conversationState"; import type { RetryAttemptRecord } from "../../../lib/chat/conversation/liveTranscriptStore"; import { collectChangedFiles } from "../../../lib/chat/messages/changedFiles"; diff --git a/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx b/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx index 1a14d0224..0f7a201b8 100644 --- a/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx +++ b/crates/agent-gui/src/pages/chat/transcript/TranscriptList.tsx @@ -1,5 +1,6 @@ import { Markdown } from "@liveagent/ui/components/Markdown"; import { useLocale } from "@liveagent/ui/i18n/index"; +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { GitClient } from "@liveagent/ui/lib/git/types"; import { createEntranceRegistry } from "@liveagent/ui/lib/transcript-virtual/entranceOnce"; import { createLiveRowScrollAdjustPolicy } from "@liveagent/ui/lib/transcript-virtual/liveScrollAdjustPolicy"; @@ -21,7 +22,6 @@ import { useSyncExternalStore, } from "react"; import { CheckCircle2, ChevronDown } from "../../../components/icons"; -import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { HistoryMessageRef, RenderSummaryCard, diff --git a/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts b/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts index 09525c354..2f8b4e332 100644 --- a/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts +++ b/crates/agent-gui/src/pages/chat/transcript/transcriptTypes.ts @@ -1,7 +1,7 @@ +import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { ScrollFollowHandle } from "@liveagent/ui/lib/chat-scroll/useScrollFollow"; import type { GitClient } from "@liveagent/ui/lib/git/types"; import type { MutableRefObject } from "react"; -import type { ChatFileLink } from "@liveagent/ui/lib/chat/chatFileLinks"; import type { HistoryMessageRef, RenderTimelineItem,