mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
parent
80e04a4b30
commit
970bd97668
39 changed files with 841 additions and 1675 deletions
|
|
@ -1,113 +1,94 @@
|
|||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo } from "react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { useState, memo } from "react"
|
||||
import { Trans } from "react-i18next"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@src/components/ui"
|
||||
|
||||
interface AnnouncementProps {
|
||||
hideAnnouncement: () => void
|
||||
}
|
||||
/*
|
||||
You must update the latestAnnouncementId in ClineProvider for new announcements to show to users. This new id will be compared with whats in state for the 'last announcement shown', and if it's different then the announcement will render. As soon as an announcement is shown, the id will be updated in state. This ensures that announcements are not shown more than once, even if the user doesn't close it themselves.
|
||||
*/
|
||||
|
||||
/**
|
||||
* You must update the `latestAnnouncementId` in ClineProvider for new
|
||||
* announcements to show to users. This new id will be compared with what's in
|
||||
* state for the 'last announcement shown', and if it's different then the
|
||||
* announcement will render. As soon as an announcement is shown, the id will be
|
||||
* updated in state. This ensures that announcements are not shown more than
|
||||
* once, even if the user doesn't close it themselves.
|
||||
*/
|
||||
|
||||
const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const discordLink = (
|
||||
<VSCodeLink
|
||||
href="https://discord.gg/roocode"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.postMessage(
|
||||
{ type: "action", action: "openExternal", data: { url: "https://discord.gg/roocode" } },
|
||||
"*",
|
||||
)
|
||||
}}>
|
||||
Discord
|
||||
</VSCodeLink>
|
||||
)
|
||||
|
||||
const redditLink = (
|
||||
<VSCodeLink
|
||||
href="https://reddit.com/r/RooCode"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.postMessage(
|
||||
{ type: "action", action: "openExternal", data: { url: "https://reddit.com/r/RooCode" } },
|
||||
"*",
|
||||
)
|
||||
}}>
|
||||
Reddit
|
||||
</VSCodeLink>
|
||||
)
|
||||
const [open, setOpen] = useState(true)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center absolute top-0 bottom-0 left-0 right-0 z-50 p-10 bg-black/50">
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
borderRadius: "3px",
|
||||
padding: "12px 16px",
|
||||
margin: "5px 15px 5px 15px",
|
||||
position: "relative",
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={hideAnnouncement}
|
||||
title={t("chat:announcement.hideButton")}
|
||||
style={{ position: "absolute", top: "8px", right: "8px" }}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
<h2 style={{ margin: "0 0 8px" }}>{t("chat:announcement.title")}</h2>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
setOpen(open)
|
||||
|
||||
<p style={{ margin: "5px 0px" }}>{t("chat:announcement.description")}</p>
|
||||
|
||||
<h3 style={{ margin: "12px 0 5px", fontSize: "14px" }}>{t("chat:announcement.whatsNew")}</h3>
|
||||
<ul style={{ margin: "5px 0" }}>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans
|
||||
i18nKey="chat:announcement.feature1"
|
||||
components={{
|
||||
bold: <b />,
|
||||
code: <code />,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans
|
||||
i18nKey="chat:announcement.feature2"
|
||||
components={{
|
||||
bold: <b />,
|
||||
code: <code />,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans
|
||||
i18nKey="chat:announcement.feature3"
|
||||
components={{
|
||||
bold: <b />,
|
||||
code: <code />,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p style={{ margin: "10px 0px 0px" }}>
|
||||
if (!open) {
|
||||
hideAnnouncement()
|
||||
}
|
||||
}}>
|
||||
<DialogContent className="max-w-96">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("chat:announcement.title")}</DialogTitle>
|
||||
<DialogDescription>{t("chat:announcement.description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<h3>{t("chat:announcement.whatsNew")}</h3>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans i18nKey="chat:announcement.feature1" components={{ bold: <b />, code: <code /> }} />
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans i18nKey="chat:announcement.feature2" components={{ bold: <b />, code: <code /> }} />
|
||||
</li>
|
||||
<li>
|
||||
•{" "}
|
||||
<Trans i18nKey="chat:announcement.feature3" components={{ bold: <b />, code: <code /> }} />
|
||||
</li>
|
||||
</ul>
|
||||
<Trans
|
||||
i18nKey="chat:announcement.detailsDiscussLinks"
|
||||
components={{
|
||||
discordLink: discordLink,
|
||||
redditLink: redditLink,
|
||||
}}
|
||||
components={{ discordLink: <DiscordLink />, redditLink: <RedditLink /> }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const DiscordLink = () => (
|
||||
<VSCodeLink
|
||||
href="https://discord.gg/roocode"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.postMessage(
|
||||
{ type: "action", action: "openExternal", data: { url: "https://discord.gg/roocode" } },
|
||||
"*",
|
||||
)
|
||||
}}>
|
||||
Discord
|
||||
</VSCodeLink>
|
||||
)
|
||||
|
||||
const RedditLink = () => (
|
||||
<VSCodeLink
|
||||
href="https://reddit.com/r/RooCode"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.postMessage(
|
||||
{ type: "action", action: "openExternal", data: { url: "https://reddit.com/r/RooCode" } },
|
||||
"*",
|
||||
)
|
||||
}}>
|
||||
Reddit
|
||||
</VSCodeLink>
|
||||
)
|
||||
|
||||
export default memo(Announcement)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react"
|
|||
import { Trans } from "react-i18next"
|
||||
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
|
|
@ -118,23 +119,13 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "0 15px",
|
||||
userSelect: "none",
|
||||
borderTop: isExpanded
|
||||
? `0.5px solid color-mix(in srgb, var(--vscode-titleBar-inactiveForeground) 20%, transparent)`
|
||||
: "none",
|
||||
overflowY: "auto",
|
||||
...style,
|
||||
}}>
|
||||
className={cn(
|
||||
"px-[15px] select-none overflow-y-auto",
|
||||
isExpanded && "border-t-[0.5px] border-vscode-titleBar-inactiveForeground-20",
|
||||
)}
|
||||
style={style}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
padding: isExpanded ? "8px 0" : "8px 0 0 0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className={`flex items-center gap-2 cursor-pointer ${isExpanded ? "py-2" : "pt-2 pb-0"}`}
|
||||
onClick={toggleExpanded}>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<VSCodeCheckbox
|
||||
|
|
@ -146,49 +137,22 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{t("chat:autoApprove.title")}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
}}>
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="text-vscode-foreground flex-shrink-0">{t("chat:autoApprove.title")}</span>
|
||||
<span className="text-vscode-descriptionForeground overflow-hidden text-ellipsis whitespace-nowrap flex-1 min-w-0">
|
||||
{enabledActionsList || t("chat:autoApprove.none")}
|
||||
</span>
|
||||
<span
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
marginLeft: isExpanded ? "2px" : "-2px",
|
||||
}}
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"} flex-shrink-0 ${
|
||||
isExpanded ? "ml-0.5" : "ml-[-2px]"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
<div className="text-vscode-descriptionForeground text-xs">
|
||||
<Trans
|
||||
i18nKey="chat:autoApprove.description"
|
||||
components={{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import { BrowserAction, BrowserActionResult, ClineMessage, ClineSayBrowserAction
|
|||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
||||
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import CodeBlock from "../common/CodeBlock"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ChatRowContent } from "./ChatRow"
|
||||
import { ProgressIndicator } from "./ProgressIndicator"
|
||||
|
||||
|
|
@ -233,53 +234,26 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
|||
: displayState.mousePosition || defaultMousePosition
|
||||
|
||||
const [browserSessionRow, { height: rowHeight }] = useSize(
|
||||
<div style={{ padding: "10px 6px 10px 15px", marginBottom: -10 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: "10px" }}>
|
||||
<div className="px-[15px] py-[10px] pr-[6px] mb-[-10px]">
|
||||
<div className="flex items-center gap-2.5 mb-2.5">
|
||||
{isBrowsing ? (
|
||||
<ProgressIndicator />
|
||||
) : (
|
||||
<span
|
||||
className={`codicon codicon-inspect`}
|
||||
style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
|
||||
<span className={`codicon codicon-inspect text-vscode-foreground mb-[-1.5px]`}></span>
|
||||
)}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
<>{t("chat:browser.rooWantsToUse")}</>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 3,
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
overflow: "hidden",
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<div className="rounded-[3px] border border-vscode-editorGroup-border overflow-hidden mb-2.5 bg-background">
|
||||
{/* URL Bar */}
|
||||
<div
|
||||
style={{
|
||||
margin: "5px auto",
|
||||
width: "calc(100% - 10px)",
|
||||
boxSizing: "border-box", // includes padding in width calculation
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: "4px",
|
||||
padding: "3px 5px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: displayState.url
|
||||
? "var(--vscode-input-foreground)"
|
||||
: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
}}>
|
||||
className={cn(
|
||||
"my-[5px] mx-auto w-[calc(100%-10px)] box-border bg-vscode-input-background border border-vscode-input-border rounded p-[3px_5px] flex items-center justify-center text-xs",
|
||||
// Conditional color class:
|
||||
displayState.url ? "text-vscode-input-foreground" : "text-vscode-descriptionForeground",
|
||||
)}>
|
||||
<div className="text-ellipsis overflow-hidden whitespace-nowrap w-full text-center">
|
||||
{displayState.url || "http"}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -287,25 +261,15 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
|||
{/* Screenshot Area */}
|
||||
<div
|
||||
data-testid="screenshot-container"
|
||||
style={{
|
||||
width: "100%",
|
||||
paddingBottom: `${aspectRatio}%`, // height/width ratio
|
||||
position: "relative",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
}}>
|
||||
className={cn(
|
||||
"w-full relative bg-vscode-input-background",
|
||||
`[padding-bottom:${aspectRatio}%]`, // Converted paddingBottom to arbitrary property
|
||||
)}>
|
||||
{displayState.screenshot ? (
|
||||
<img
|
||||
src={displayState.screenshot}
|
||||
alt={t("chat:browser.screenshot")}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className="absolute top-0 left-0 w-full h-full object-contain cursor-pointer"
|
||||
onClick={() =>
|
||||
vscode.postMessage({
|
||||
type: "openImage",
|
||||
|
|
@ -314,47 +278,34 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
|||
}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
}}>
|
||||
<span
|
||||
className="codicon codicon-globe"
|
||||
style={{ fontSize: "80px", color: "var(--vscode-descriptionForeground)" }}
|
||||
/>
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||
<span className="codicon codicon-globe text-[80px] text-vscode-descriptionForeground" />
|
||||
</div>
|
||||
)}
|
||||
{displayState.mousePosition && (
|
||||
<BrowserCursor
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: `${(parseInt(mousePosition.split(",")[1]) / viewportHeight) * 100}%`,
|
||||
left: `${(parseInt(mousePosition.split(",")[0]) / viewportWidth) * 100}%`,
|
||||
transition: "top 0.3s ease-out, left 0.3s ease-out",
|
||||
}}
|
||||
className={cn(
|
||||
"absolute",
|
||||
// Dynamic position using arbitrary properties:
|
||||
`[top:${(parseInt(mousePosition.split(",")[1]) / viewportHeight) * 100}%]`,
|
||||
`[left:${(parseInt(mousePosition.split(",")[0]) / viewportWidth) * 100}%]`,
|
||||
// Static transition classes:
|
||||
"transition-[top,left] duration-300 ease-out",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="w-full">
|
||||
<div
|
||||
onClick={() => {
|
||||
setConsoleLogsExpanded(!consoleLogsExpanded)
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
cursor: "pointer",
|
||||
padding: `9px 8px ${consoleLogsExpanded ? 0 : 8}px 8px`,
|
||||
}}>
|
||||
className={`flex items-center gap-1 w-full justify-start cursor-pointer px-2 ${
|
||||
consoleLogsExpanded ? "pt-[9px] pb-0" : "py-[9px]"
|
||||
}`}>
|
||||
<span className={`codicon codicon-chevron-${consoleLogsExpanded ? "down" : "right"}`}></span>
|
||||
<span style={{ fontSize: "0.8em" }}>{t("chat:browser.consoleLogs")}</span>
|
||||
<span className="text-[0.8em]">{t("chat:browser.consoleLogs")}</span>
|
||||
</div>
|
||||
{consoleLogsExpanded && (
|
||||
<CodeBlock source={displayState.consoleLogs || t("chat:browser.noNewLogs")} language="shell" />
|
||||
|
|
@ -363,23 +314,15 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
|||
</div>
|
||||
|
||||
{/* Action content with min height */}
|
||||
<div style={{ minHeight: maxActionHeight }}>{actionContent}</div>
|
||||
<div className={cn(maxActionHeight > 0 && `[min-height:${maxActionHeight}px]`)}>{actionContent}</div>
|
||||
|
||||
{/* Pagination moved to bottom */}
|
||||
{pages.length > 1 && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "8px 0px",
|
||||
marginTop: "15px",
|
||||
borderTop: "1px solid var(--vscode-editorGroup-border)",
|
||||
}}>
|
||||
<div className="flex justify-between items-center py-2 mt-[15px] border-t border-vscode-editorGroup-border">
|
||||
<div>
|
||||
{t("chat:browser.navigation.step", { current: currentPageIndex + 1, total: pages.length })}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "4px" }}>
|
||||
<div className="flex gap-1">
|
||||
<VSCodeButton
|
||||
disabled={currentPageIndex === 0 || isBrowsing}
|
||||
onClick={() => setCurrentPageIndex((i) => i - 1)}>
|
||||
|
|
@ -426,13 +369,7 @@ const BrowserSessionRowContent = ({
|
|||
isStreaming,
|
||||
}: BrowserSessionRowContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const headerStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
marginBottom: "10px",
|
||||
wordBreak: "break-word",
|
||||
}
|
||||
const headerClassNames = "flex items-center gap-2.5 mb-2.5 break-words"
|
||||
|
||||
switch (message.type) {
|
||||
case "say":
|
||||
|
|
@ -440,7 +377,7 @@ const BrowserSessionRowContent = ({
|
|||
case "api_req_started":
|
||||
case "text":
|
||||
return (
|
||||
<div style={{ padding: "10px 0 10px 0" }}>
|
||||
<div className="py-2.5">
|
||||
<ChatRowContent
|
||||
message={message}
|
||||
isExpanded={isExpanded(message.ts)}
|
||||
|
|
@ -476,16 +413,10 @@ const BrowserSessionRowContent = ({
|
|||
case "browser_action_launch":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<span style={{ fontWeight: "bold" }}>{t("chat:browser.sessionStarted")}</span>
|
||||
<div className={headerClassNames}>
|
||||
<span className="font-bold">{t("chat:browser.sessionStarted")}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 3,
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
overflow: "hidden",
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
}}>
|
||||
<div className="rounded-[3px] border border-vscode-editorGroup-border overflow-hidden bg-background">
|
||||
<CodeBlock source={message.text} language="shell" />
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -526,26 +457,11 @@ const BrowserActionBox = ({
|
|||
}
|
||||
}
|
||||
return (
|
||||
<div style={{ padding: "10px 0 0 0" }}>
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 3,
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
overflow: "hidden",
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "9px 10px",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<span style={{ fontWeight: 500 }}>{t("chat:browser.actions.title")}</span>
|
||||
<div className="pt-2.5">
|
||||
<div className="rounded-[3px] overflow-hidden border border-vscode-editorGroup-border bg-background">
|
||||
<div className="flex items-center p-[9px_10px]">
|
||||
<span className="whitespace-normal break-words">
|
||||
<span className="font-medium">{t("chat:browser.actions.title")}</span>
|
||||
{getBrowserActionText(action, coordinate, text)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -554,7 +470,12 @@ const BrowserActionBox = ({
|
|||
)
|
||||
}
|
||||
|
||||
const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) => {
|
||||
interface BrowserCursorProps {
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
}
|
||||
|
||||
const BrowserCursor: React.FC<BrowserCursorProps> = ({ style, className }) => {
|
||||
const { t } = useTranslation()
|
||||
// (can't use svgs in vsc extensions)
|
||||
const cursorBase64 =
|
||||
|
|
@ -563,13 +484,16 @@ const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) =>
|
|||
return (
|
||||
<img
|
||||
src={cursorBase64}
|
||||
style={{
|
||||
width: "17px",
|
||||
height: "22px",
|
||||
...style,
|
||||
}}
|
||||
alt={t("chat:browser.cursor")}
|
||||
aria-label={t("chat:browser.cursor")}
|
||||
title={t("chat:browser.cursor")}
|
||||
className={cn(
|
||||
className,
|
||||
"w-[17px] h-[24px]",
|
||||
"translate-x-[-2px] translate-y-[-2px]", // transform
|
||||
"pointer-events-none",
|
||||
"z-[1000]",
|
||||
)}
|
||||
style={style} // Keep passed style prop for overrides
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { ClineApiReqInfo, ClineAskUseMcpServer, ClineMessage, ClineSayTool } fro
|
|||
import { COMMAND_OUTPUT_STRING } from "@roo/shared/combineCommandSequences"
|
||||
import { safeJsonParse } from "@roo/shared/safeJsonParse"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
|
||||
|
|
@ -15,7 +16,7 @@ import { vscode } from "@src/utils/vscode"
|
|||
import { Button } from "@src/components/ui"
|
||||
|
||||
import CodeAccordian, { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
|
||||
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import CodeBlock from "../common/CodeBlock"
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import { ReasoningBlock } from "./ReasoningBlock"
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
|
|
@ -118,37 +119,26 @@ export const ChatRowContent = ({
|
|||
|
||||
const type = message.type === "ask" ? message.ask : message.say
|
||||
|
||||
const normalColor = "var(--vscode-foreground)"
|
||||
const errorColor = "var(--vscode-errorForeground)"
|
||||
const successColor = "var(--vscode-charts-green)"
|
||||
const cancelledColor = "var(--vscode-descriptionForeground)"
|
||||
|
||||
const [icon, title] = useMemo(() => {
|
||||
const [icon, title] = useMemo((): [React.ReactNode | null, React.ReactNode | null] => {
|
||||
switch (type) {
|
||||
case "error":
|
||||
return [
|
||||
<span
|
||||
className="codicon codicon-error"
|
||||
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>{t("chat:error")}</span>,
|
||||
<span className="codicon codicon-error text-vscode-errorForeground mb-[-1.5px]" />,
|
||||
<span className="font-bold text-vscode-errorForeground">{t("chat:error")}</span>,
|
||||
]
|
||||
case "mistake_limit_reached":
|
||||
return [
|
||||
<span
|
||||
className="codicon codicon-error"
|
||||
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>{t("chat:troubleMessage")}</span>,
|
||||
<span className="codicon codicon-error text-vscode-errorForeground mb-[-1.5px]" />, // Self-closing
|
||||
<span className="font-bold text-vscode-errorForeground">{t("chat:troubleMessage")}</span>,
|
||||
]
|
||||
case "command":
|
||||
return [
|
||||
isCommandExecuting ? (
|
||||
<ProgressIndicator />
|
||||
) : (
|
||||
<span
|
||||
className="codicon codicon-terminal"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
|
||||
<span className="codicon codicon-terminal text-vscode-foreground mb-[-1.5px]" /> // Self-closing
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:runCommand.title")}:</span>,
|
||||
<span className="font-bold text-vscode-foreground">{t("chat:runCommand.title")}:</span>,
|
||||
]
|
||||
case "use_mcp_server":
|
||||
const mcpServerUse = safeJsonParse<ClineAskUseMcpServer>(message.text)
|
||||
|
|
@ -159,11 +149,9 @@ export const ChatRowContent = ({
|
|||
isMcpServerResponding ? (
|
||||
<ProgressIndicator />
|
||||
) : (
|
||||
<span
|
||||
className="codicon codicon-server"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
|
||||
<span className="codicon codicon-server text-vscode-foreground mb-[-1.5px]" /> // Self-closing
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
||||
<span className="font-bold text-vscode-foreground">
|
||||
{mcpServerUse.type === "use_mcp_tool"
|
||||
? t("chat:mcp.wantsToUseTool", { serverName: mcpServerUse.serverName })
|
||||
: t("chat:mcp.wantsToAccessResource", { serverName: mcpServerUse.serverName })}
|
||||
|
|
@ -171,88 +159,59 @@ export const ChatRowContent = ({
|
|||
]
|
||||
case "completion_result":
|
||||
return [
|
||||
<span
|
||||
className="codicon codicon-check"
|
||||
style={{ color: successColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: successColor, fontWeight: "bold" }}>{t("chat:taskCompleted")}</span>,
|
||||
<span className="codicon codicon-check text-vscode-charts-green mb-[-1.5px]" />, // Self-closing
|
||||
<span className="font-bold text-vscode-charts-green">{t("chat:taskCompleted")}</span>,
|
||||
]
|
||||
case "api_req_retry_delayed":
|
||||
return []
|
||||
return [null, null] // Explicitly return [null, null] for this case
|
||||
case "api_req_started":
|
||||
const getIconSpan = (iconName: string, color: string) => (
|
||||
<div
|
||||
style={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<span
|
||||
className={`codicon codicon-${iconName}`}
|
||||
style={{ color, fontSize: 16, marginBottom: "-1.5px" }}
|
||||
/>
|
||||
const getIconSpan = (iconName: string, colorClassName: string) => (
|
||||
<div className="w-4 h-4 flex items-center justify-center">
|
||||
<span className={`codicon codicon-${iconName} ${colorClassName} text-[16px] mb-[-1.5px]`} />
|
||||
</div>
|
||||
)
|
||||
return [
|
||||
apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
|
||||
apiReqCancelReason === "user_cancelled" ? (
|
||||
getIconSpan("error", cancelledColor)
|
||||
getIconSpan("error", "text-vscode-descriptionForeground")
|
||||
) : (
|
||||
getIconSpan("error", errorColor)
|
||||
getIconSpan("error", "text-vscode-errorForeground")
|
||||
)
|
||||
) : cost !== null && cost !== undefined ? (
|
||||
getIconSpan("check", successColor)
|
||||
getIconSpan("check", "text-vscode-charts-green")
|
||||
) : apiRequestFailedMessage ? (
|
||||
getIconSpan("error", errorColor)
|
||||
getIconSpan("error", "text-vscode-errorForeground")
|
||||
) : (
|
||||
<ProgressIndicator />
|
||||
),
|
||||
apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
|
||||
apiReqCancelReason === "user_cancelled" ? (
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
||||
{t("chat:apiRequest.cancelled")}
|
||||
</span>
|
||||
<span className="font-bold text-vscode-foreground">{t("chat:apiRequest.cancelled")}</span>
|
||||
) : (
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>
|
||||
<span className="font-bold text-vscode-errorForeground">
|
||||
{t("chat:apiRequest.streamingFailed")}
|
||||
</span>
|
||||
)
|
||||
) : cost !== null && cost !== undefined ? (
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:apiRequest.title")}</span>
|
||||
<span className="font-bold text-vscode-foreground">{t("chat:apiRequest.title")}</span>
|
||||
) : apiRequestFailedMessage ? (
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>{t("chat:apiRequest.failed")}</span>
|
||||
<span className="font-bold text-vscode-errorForeground">{t("chat:apiRequest.failed")}</span>
|
||||
) : (
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:apiRequest.streaming")}</span>
|
||||
<span className="font-bold text-vscode-foreground">{t("chat:apiRequest.streaming")}</span>
|
||||
),
|
||||
]
|
||||
case "followup":
|
||||
return [
|
||||
<span
|
||||
className="codicon codicon-question"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}
|
||||
/>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:questions.hasQuestion")}</span>,
|
||||
<span className="codicon codicon-question text-vscode-foreground mb-[-1.5px]" />,
|
||||
<span className="font-bold text-vscode-foreground">{t("chat:questions.hasQuestion")}</span>,
|
||||
]
|
||||
default:
|
||||
return [null, null]
|
||||
return [null, null] // Explicitly return [null, null] for default case
|
||||
}
|
||||
}, [type, isCommandExecuting, message, isMcpServerResponding, apiReqCancelReason, cost, apiRequestFailedMessage, t])
|
||||
|
||||
const headerStyle: React.CSSProperties = {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
marginBottom: "10px",
|
||||
wordBreak: "break-word",
|
||||
}
|
||||
|
||||
const pStyle: React.CSSProperties = {
|
||||
margin: 0,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}
|
||||
const headerClasses = "flex items-center gap-[10px] mb-[10px] break-words"
|
||||
const pClasses = "m-0 whitespace-pre-wrap break-words overflow-anywhere"
|
||||
|
||||
const tool = useMemo(
|
||||
() => (message.ask === "tool" ? safeJsonParse<ClineSayTool>(message.text) : null),
|
||||
|
|
@ -268,9 +227,7 @@ export const ChatRowContent = ({
|
|||
|
||||
if (tool) {
|
||||
const toolIcon = (name: string) => (
|
||||
<span
|
||||
className={`codicon codicon-${name}`}
|
||||
style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
|
||||
<span className={`codicon codicon-${name} text-vscode-foreground mb-[-1.5px]`} /> // Converted to Tailwind & self-closing
|
||||
)
|
||||
|
||||
switch (tool.tool) {
|
||||
|
|
@ -278,9 +235,9 @@ export const ChatRowContent = ({
|
|||
case "appliedDiff":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{tool.isOutsideWorkspace
|
||||
? t("chat:fileOperations.wantsToEditOutsideWorkspace")
|
||||
: t("chat:fileOperations.wantsToEdit")}
|
||||
|
|
@ -299,9 +256,9 @@ export const ChatRowContent = ({
|
|||
case "insertContent":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("insert")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{tool.isOutsideWorkspace
|
||||
? t("chat:fileOperations.wantsToEditOutsideWorkspace")
|
||||
: tool.lineNumber === 0
|
||||
|
|
@ -324,9 +281,9 @@ export const ChatRowContent = ({
|
|||
case "searchAndReplace":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("replace")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask"
|
||||
? t("chat:fileOperations.wantsToSearchReplace")
|
||||
: t("chat:fileOperations.didSearchReplace")}
|
||||
|
|
@ -345,9 +302,9 @@ export const ChatRowContent = ({
|
|||
case "newFileCreated":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("new-file")}
|
||||
<span style={{ fontWeight: "bold" }}>{t("chat:fileOperations.wantsToCreate")}</span>
|
||||
<span className="font-bold">{t("chat:fileOperations.wantsToCreate")}</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
isLoading={message.partial}
|
||||
|
|
@ -361,9 +318,9 @@ export const ChatRowContent = ({
|
|||
case "readFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask"
|
||||
? tool.isOutsideWorkspace
|
||||
? t("chat:fileOperations.wantsToReadOutsideWorkspace")
|
||||
|
|
@ -371,45 +328,19 @@ export const ChatRowContent = ({
|
|||
: t("chat:fileOperations.didRead")}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 3,
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
overflow: "hidden",
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
}}>
|
||||
<div className="rounded-[3px] overflow-hidden border border-vscode-editorGroup-border bg-vscode-code-block-background">
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "9px 10px",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
}}
|
||||
className="text-vscode-descriptionForeground flex items-center p-[9px_10px] cursor-pointer select-none"
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openFile", text: tool.content })
|
||||
}}>
|
||||
{tool.path?.startsWith(".") && <span>.</span>}
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
marginRight: "8px",
|
||||
direction: "rtl",
|
||||
textAlign: "left",
|
||||
}}>
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis mr-2 rtl text-left">
|
||||
{removeLeadingNonAlphanumeric(tool.path ?? "") + "\u200E"}
|
||||
{tool.reason}
|
||||
</span>
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
<span
|
||||
className={`codicon codicon-link-external`}
|
||||
style={{ fontSize: 13.5, margin: "1px 0" }}></span>
|
||||
<div className="flex-grow"></div>
|
||||
<span className="codicon codicon-link-external text-[13.5px] m-[1px_0]"></span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -417,9 +348,9 @@ export const ChatRowContent = ({
|
|||
case "fetchInstructions":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>{t("chat:instructions.wantsToFetch")}</span>
|
||||
<span className="font-bold">{t("chat:instructions.wantsToFetch")}</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
isLoading={message.partial}
|
||||
|
|
@ -432,9 +363,9 @@ export const ChatRowContent = ({
|
|||
case "listFilesTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask"
|
||||
? t("chat:directoryOperations.wantsToViewTopLevel")
|
||||
: t("chat:directoryOperations.didViewTopLevel")}
|
||||
|
|
@ -452,9 +383,9 @@ export const ChatRowContent = ({
|
|||
case "listFilesRecursive":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask"
|
||||
? t("chat:directoryOperations.wantsToViewRecursive")
|
||||
: t("chat:directoryOperations.didViewRecursive")}
|
||||
|
|
@ -472,9 +403,9 @@ export const ChatRowContent = ({
|
|||
case "listCodeDefinitionNames":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask"
|
||||
? t("chat:directoryOperations.wantsToViewDefinitions")
|
||||
: t("chat:directoryOperations.didViewDefinitions")}
|
||||
|
|
@ -491,9 +422,9 @@ export const ChatRowContent = ({
|
|||
case "searchFiles":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("search")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask" ? (
|
||||
<Trans
|
||||
i18nKey="chat:directoryOperations.wantsToSearch"
|
||||
|
|
@ -521,9 +452,9 @@ export const ChatRowContent = ({
|
|||
case "switchMode":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("symbol-enum")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
{message.type === "ask" ? (
|
||||
<>
|
||||
{tool.reason ? (
|
||||
|
|
@ -564,9 +495,9 @@ export const ChatRowContent = ({
|
|||
case "newTask":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("tasklist")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<span className="font-bold">
|
||||
<Trans
|
||||
i18nKey="chat:subtasks.wantsToCreate"
|
||||
components={{ code: <code>{tool.mode}</code> }}
|
||||
|
|
@ -607,9 +538,9 @@ export const ChatRowContent = ({
|
|||
case "finishTask":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{toolIcon("check-all")}
|
||||
<span style={{ fontWeight: "bold" }}>{t("chat:subtasks.wantsToFinish")}</span>
|
||||
<span className="font-bold">{t("chat:subtasks.wantsToFinish")}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -788,22 +719,16 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
...headerStyle,
|
||||
marginBottom:
|
||||
((cost === null || cost === undefined) && apiRequestFailedMessage) ||
|
||||
className={cn(
|
||||
headerClasses,
|
||||
"justify-between cursor-pointer select-none",
|
||||
((cost === null || cost === undefined) && apiRequestFailedMessage) ||
|
||||
apiReqStreamingFailedMessage
|
||||
? 10
|
||||
: 0,
|
||||
justifyContent: "space-between",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
}}
|
||||
? "mb-[10px]"
|
||||
: "mb-0",
|
||||
)}
|
||||
onClick={onToggleExpand}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
|
||||
<div className="flex items-center gap-[10px] flex-grow">
|
||||
{icon}
|
||||
{title}
|
||||
<VSCodeBadge
|
||||
|
|
@ -816,7 +741,7 @@ export const ChatRowContent = ({
|
|||
{(((cost === null || cost === undefined) && apiRequestFailedMessage) ||
|
||||
apiReqStreamingFailedMessage) && (
|
||||
<>
|
||||
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>
|
||||
<p className={cn(pClasses, "text-vscode-errorForeground")}>
|
||||
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
|
||||
{apiRequestFailedMessage?.toLowerCase().includes("powershell") && (
|
||||
<>
|
||||
|
|
@ -898,22 +823,22 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
{title && (
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>{message.text}</p>
|
||||
<p className={cn(pClasses, "text-vscode-errorForeground")}>{message.text}</p>
|
||||
</>
|
||||
)
|
||||
case "completion_result":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
|
||||
<div className="text-vscode-charts-green pt-[10px]">
|
||||
<Markdown markdown={message.text} />
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -923,16 +848,8 @@ export const ChatRowContent = ({
|
|||
case "mcp_server_response":
|
||||
return (
|
||||
<>
|
||||
<div style={{ paddingTop: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "4px",
|
||||
opacity: 0.8,
|
||||
fontSize: "12px",
|
||||
textTransform: "uppercase",
|
||||
}}>
|
||||
{t("chat:response")}
|
||||
</div>
|
||||
<div className="pt-0">
|
||||
<div className="mb-1 opacity-80 text-xs uppercase">{t("chat:response")}</div>
|
||||
<CodeAccordian
|
||||
code={message.text}
|
||||
language="json"
|
||||
|
|
@ -955,12 +872,12 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
{title && (
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ paddingTop: 10 }}>
|
||||
<div className="pt-[10px]">
|
||||
<Markdown markdown={message.text} partial={message.partial} />
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -971,17 +888,17 @@ export const ChatRowContent = ({
|
|||
case "mistake_limit_reached":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>{message.text}</p>
|
||||
<p className={cn(pClasses, "text-vscode-errorForeground")}>{message.text}</p>
|
||||
</>
|
||||
)
|
||||
case "command":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
|
|
@ -999,17 +916,11 @@ export const ChatRowContent = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
background: "var(--vscode-textCodeBlock-background)",
|
||||
borderRadius: "3px",
|
||||
padding: "8px 10px",
|
||||
marginTop: "8px",
|
||||
}}>
|
||||
<div className="bg-vscode-textCodeBlock-background rounded-[3px] p-[8px_10px] mt-2">
|
||||
{useMcpServer.type === "access_mcp_resource" && (
|
||||
<McpResourceRow
|
||||
item={{
|
||||
|
|
@ -1048,14 +959,8 @@ export const ChatRowContent = ({
|
|||
/>
|
||||
</div>
|
||||
{useMcpServer.arguments && useMcpServer.arguments !== "{}" && (
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "4px",
|
||||
opacity: 0.8,
|
||||
fontSize: "12px",
|
||||
textTransform: "uppercase",
|
||||
}}>
|
||||
<div className="mt-2">
|
||||
<div className="mb-1 opacity-80 text-xs uppercase">
|
||||
{t("chat:arguments")}
|
||||
</div>
|
||||
<CodeAccordian
|
||||
|
|
@ -1075,11 +980,11 @@ export const ChatRowContent = ({
|
|||
if (message.text) {
|
||||
return (
|
||||
<div>
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
|
||||
<div className="text-vscode-charts-green pt-[10px]">
|
||||
<Markdown markdown={message.text} partial={message.partial} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1091,15 +996,15 @@ export const ChatRowContent = ({
|
|||
return (
|
||||
<>
|
||||
{title && (
|
||||
<div style={headerStyle}>
|
||||
<div className={headerClasses}>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ paddingTop: 10, paddingBottom: 15 }}>
|
||||
<Markdown
|
||||
markdown={message.partial === true ? message?.text : followUpData?.question}
|
||||
/>
|
||||
<div className="pt-[10px] pb-[15px]">
|
||||
<p className={pClasses}>
|
||||
{message.partial === true ? message?.text : followUpData?.question}
|
||||
</p>
|
||||
</div>
|
||||
<FollowUpSuggest
|
||||
suggestions={followUpData?.suggest}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import TaskHeader from "./TaskHeader"
|
|||
import AutoApproveMenu from "./AutoApproveMenu"
|
||||
import SystemPromptWarning from "./SystemPromptWarning"
|
||||
import { CheckpointWarning } from "./CheckpointWarning"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface ChatViewProps {
|
||||
isHidden: boolean
|
||||
|
|
@ -1248,17 +1249,24 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
</div>
|
||||
)}
|
||||
<div
|
||||
className={` w-full flex flex-col gap-4 m-auto ${isExpanded && tasks.length > 0 ? "mt-0" : ""} px-3.5 min-[370px]:px-10 pt-5 transition-all duration-300`}>
|
||||
className={cn(
|
||||
"w-full flex flex-col gap-4 m-auto px-3.5 min-[370px]:px-10 pt-5 transition-all duration-300",
|
||||
isExpanded && tasks.length > 0 ? "mt-0" : "",
|
||||
)}>
|
||||
<RooHero />
|
||||
{telemetrySetting === "unset" && <TelemetryBanner />}
|
||||
{/* Show the task history preview if expanded and tasks exist */}
|
||||
{taskHistory.length > 0 && isExpanded && <HistoryPreview />}
|
||||
<p className="ext-vscode-editor-foreground leading-tight font-vscode text-center">
|
||||
<p className="text-vscode-editor-foreground leading-tight font-vscode-font-family text-center">
|
||||
<Trans
|
||||
i18nKey="chat:about"
|
||||
components={{
|
||||
DocsLink: (
|
||||
<a href="https://docs.roocode.com/" target="_blank" rel="noopener noreferrer">
|
||||
<a
|
||||
href="https://docs.roocode.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline">
|
||||
the docs
|
||||
</a>
|
||||
),
|
||||
|
|
@ -1320,7 +1328,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
{showScrollToBottom ? (
|
||||
<div className="flex px-[15px] pt-[10px]">
|
||||
<div
|
||||
className="bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_55%,_transparent)] rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_90%,_transparent)] active:bg-[color-mix(in_srgb,_var(--vscode-toolbar-hoverBackground)_70%,_transparent)]"
|
||||
className="bg-vscode-toolbar-hoverBackground/55 rounded-[3px] overflow-hidden cursor-pointer flex justify-center items-center flex-1 h-[25px] hover:bg-vscode-toolbar-hoverBackground/90 active:bg-vscode-toolbar-hoverBackground/70"
|
||||
onClick={() => {
|
||||
scrollToBottomSmooth()
|
||||
disableAutoScrollRef.current = false
|
||||
|
|
|
|||
|
|
@ -27,10 +27,7 @@ export const CommandExecutionError = () => {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
href="http://docs.roocode.com/troubleshooting/shell-integration/"
|
||||
className="underline"
|
||||
style={{ color: "inherit" }}>
|
||||
<a href="http://docs.roocode.com/troubleshooting/shell-integration/" className="underline text-inherit">
|
||||
{t("chat:shellIntegration.troubleshooting")}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,18 +68,10 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
switch (option.type) {
|
||||
case ContextMenuOptionType.Mode:
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
|
||||
<span style={{ lineHeight: "1.2" }}>{option.label}</span>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="leading-tight">{option.label}</span>
|
||||
{option.description && (
|
||||
<span
|
||||
style={{
|
||||
opacity: 0.5,
|
||||
fontSize: "0.9em",
|
||||
lineHeight: "1.2",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}>
|
||||
<span className="opacity-50 text-[0.9em] leading-tight whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{option.description}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -96,17 +88,9 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
case ContextMenuOptionType.Git:
|
||||
if (option.value) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
|
||||
<span style={{ lineHeight: "1.2" }}>{option.label}</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.85em",
|
||||
opacity: 0.7,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
lineHeight: "1.2",
|
||||
}}>
|
||||
<div className="flex flex-col">
|
||||
<span className="leading-tight">{option.label}</span>
|
||||
<span className="text-[0.85em] opacity-70 whitespace-nowrap overflow-hidden text-ellipsis leading-tight">
|
||||
{option.description}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -124,29 +108,9 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
const filename = pathList.at(-1)
|
||||
const folderPath = pathList.slice(0, -1).join("/")
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
gap: "0.5em",
|
||||
whiteSpace: "nowrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
textAlign: "left",
|
||||
}}>
|
||||
<div className="flex-1 overflow-hidden flex gap-[0.5em] whitespace-nowrap items-center justify-between text-left">
|
||||
<span>{filename}</span>
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
direction: "rtl",
|
||||
textAlign: "right",
|
||||
flex: 1,
|
||||
opacity: 0.75,
|
||||
fontSize: "0.75em",
|
||||
}}>
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis rtl text-right flex-1 opacity-75 text-[0.75em]">
|
||||
{folderPath}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -197,68 +161,32 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "calc(100% - 10px)",
|
||||
left: 15,
|
||||
right: 15,
|
||||
overflowX: "hidden",
|
||||
}}
|
||||
className="absolute bottom-[calc(100%-10px)] left-[15px] right-[15px] overflow-x-hidden"
|
||||
onMouseDown={onMouseDown}>
|
||||
<div
|
||||
ref={menuRef}
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-dropdown-background)",
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
borderRadius: "3px",
|
||||
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.25)",
|
||||
zIndex: 1000,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
maxHeight: "200px",
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
className="bg-vscode-dropdown-background border border-vscode-editorGroup-border rounded-[3px] shadow-[0_4px_10px_rgba(0,0,0,0.25)] z-[1000] flex flex-col max-h-[200px] overflow-y-auto">
|
||||
{filteredOptions && filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
key={`${option.type}-${option.value || index}`}
|
||||
onClick={() => isOptionSelectable(option) && onSelect(option.type, option.value)}
|
||||
style={{
|
||||
padding: "4px 6px",
|
||||
cursor: isOptionSelectable(option) ? "pointer" : "default",
|
||||
color: "var(--vscode-dropdown-foreground)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
...(index === selectedIndex && isOptionSelectable(option)
|
||||
? {
|
||||
backgroundColor: "var(--vscode-list-activeSelectionBackground)",
|
||||
color: "var(--vscode-list-activeSelectionForeground)",
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
className={`p-[4px_6px] text-vscode-dropdown-foreground flex items-center justify-between ${
|
||||
isOptionSelectable(option) ? "cursor-pointer" : "cursor-default"
|
||||
} ${
|
||||
index === selectedIndex && isOptionSelectable(option)
|
||||
? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground"
|
||||
: ""
|
||||
}`}
|
||||
onMouseEnter={() => isOptionSelectable(option) && setSelectedIndex(index)}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
paddingTop: 0,
|
||||
}}>
|
||||
<div className="flex items-center flex-1 min-w-0 overflow-hidden pt-0">
|
||||
{(option.type === ContextMenuOptionType.File ||
|
||||
option.type === ContextMenuOptionType.Folder ||
|
||||
option.type === ContextMenuOptionType.OpenedFile) && (
|
||||
<img
|
||||
src={getMaterialIconForOption(option)}
|
||||
alt="Mode"
|
||||
style={{
|
||||
marginRight: "6px",
|
||||
flexShrink: 0,
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
}}
|
||||
className="mr-[6px] flex-shrink-0 w-4 h-4"
|
||||
/>
|
||||
)}
|
||||
{option.type !== ContextMenuOptionType.Mode &&
|
||||
|
|
@ -267,13 +195,9 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
option.type !== ContextMenuOptionType.OpenedFile &&
|
||||
getIconForOption(option) && (
|
||||
<i
|
||||
className={`codicon codicon-${getIconForOption(option)}`}
|
||||
style={{
|
||||
marginRight: "6px",
|
||||
flexShrink: 0,
|
||||
fontSize: "14px",
|
||||
marginTop: 0,
|
||||
}}
|
||||
className={`codicon codicon-${getIconForOption(
|
||||
option,
|
||||
)} mr-[6px] flex-shrink-0 text-sm mt-0`}
|
||||
/>
|
||||
)}
|
||||
{renderOptionContent(option)}
|
||||
|
|
@ -282,23 +206,12 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
option.type === ContextMenuOptionType.Folder ||
|
||||
option.type === ContextMenuOptionType.Git) &&
|
||||
!option.value && (
|
||||
<i
|
||||
className="codicon codicon-chevron-right"
|
||||
style={{ fontSize: "10px", flexShrink: 0, marginLeft: 8 }}
|
||||
/>
|
||||
<i className="codicon codicon-chevron-right text-[10px] flex-shrink-0 ml-2" />
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
padding: "4px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "var(--vscode-foreground)",
|
||||
opacity: 0.7,
|
||||
}}>
|
||||
<div className="p-1 flex items-center justify-center text-vscode-foreground opacity-70">
|
||||
<span>No results found</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -306,5 +219,4 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContextMenu
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ export const IconButton: React.FC<IconButtonProps> = ({
|
|||
<button
|
||||
aria-label={title}
|
||||
title={title}
|
||||
className={buttonClasses}
|
||||
className={`${buttonClasses} text-[16.5px]`}
|
||||
onClick={!disabled ? onClick : undefined}
|
||||
style={{ fontSize: 16.5, ...style }}
|
||||
style={style}
|
||||
{...props}>
|
||||
<span className={iconClasses} />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { memo, useState } from "react"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
|
||||
export const Markdown = memo(({ markdown, partial }: { markdown?: string; partial?: boolean }) => {
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const [copySuccess, setCopySuccess] = useState(false)
|
||||
|
||||
// Shorter feedback duration for copy button flash.
|
||||
const { copyWithFeedback } = useCopyToClipboard(200)
|
||||
|
|
@ -16,43 +18,25 @@ export const Markdown = memo(({ markdown, partial }: { markdown?: string; partia
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
style={{ position: "relative" }}>
|
||||
<div style={{ wordBreak: "break-word", overflowWrap: "anywhere", marginBottom: -15, marginTop: -15 }}>
|
||||
<div onMouseEnter={() => setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className="relative">
|
||||
<div className="break-words overflow-wrap-anywhere mb-[-15px] mt-[-15px]">
|
||||
<MarkdownBlock markdown={markdown} />
|
||||
</div>
|
||||
{markdown && !partial && isHovering && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "-4px",
|
||||
right: "8px",
|
||||
opacity: 0,
|
||||
animation: "fadeIn 0.2s ease-in-out forwards",
|
||||
borderRadius: "4px",
|
||||
}}>
|
||||
<style>{`@keyframes fadeIn { from { opacity: 0; } to { opacity: 1.0; } }`}</style>
|
||||
<div className="absolute bottom-[-4px] right-2 opacity-0 rounded animate-fadeIn duration-200 ease-in-out forwards">
|
||||
<VSCodeButton
|
||||
className="copy-button"
|
||||
className={cn(
|
||||
"copy-button h-6 border-none bg-vscode-editor-background transition-colors duration-200 ease-in-out",
|
||||
copySuccess && "bg-vscode-button-background",
|
||||
)}
|
||||
appearance="icon"
|
||||
style={{
|
||||
height: "24px",
|
||||
border: "none",
|
||||
background: "var(--vscode-editor-background)",
|
||||
transition: "background 0.2s ease-in-out",
|
||||
}}
|
||||
onClick={async () => {
|
||||
const success = await copyWithFeedback(markdown)
|
||||
if (success) {
|
||||
const button = document.activeElement as HTMLElement
|
||||
if (button) {
|
||||
button.style.background = "var(--vscode-button-background)"
|
||||
setTimeout(() => {
|
||||
button.style.background = ""
|
||||
}, 200)
|
||||
}
|
||||
setCopySuccess(true)
|
||||
setTimeout(() => {
|
||||
setCopySuccess(false)
|
||||
}, 200)
|
||||
}
|
||||
}}
|
||||
title="Copy as markdown">
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
export const ProgressIndicator = () => (
|
||||
<div
|
||||
style={{
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<div style={{ transform: "scale(0.55)", transformOrigin: "center" }}>
|
||||
<div className="w-4 h-4 flex items-center justify-center">
|
||||
<div className="scale-[.55] origin-center">
|
||||
<VSCodeProgressRing />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { memo, useMemo } from "react"
|
||||
import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
|
||||
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
|
||||
import { cn } from "@/lib/utils"
|
||||
import CodeBlock from "./CodeBlock"
|
||||
import { ToolProgressStatus } from "@roo/shared/ExtensionMessage"
|
||||
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
|
|
@ -46,64 +47,31 @@ const CodeAccordian = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 3,
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
}}>
|
||||
<div className="rounded-[3px] overflow-hidden border border-vscode-editorGroup-border bg-vscode-code-block-background">
|
||||
{(path || isFeedback || isConsoleLogs) && (
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "9px 10px",
|
||||
cursor: isLoading ? "wait" : "pointer",
|
||||
opacity: isLoading ? 0.7 : 1,
|
||||
// pointerEvents: isLoading ? "none" : "auto",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
}}
|
||||
className={`${isLoading ? "animate-pulse" : ""}`}
|
||||
className={cn(
|
||||
"text-vscode-descriptionForeground flex items-center p-[9px_10px] select-none",
|
||||
isLoading ? "cursor-wait opacity-70 animate-pulse" : "cursor-pointer opacity-100",
|
||||
)}
|
||||
onClick={isLoading ? undefined : onToggleExpand}>
|
||||
{isLoading && <VSCodeProgressRing className="size-3 mr-2" />}
|
||||
{isFeedback || isConsoleLogs ? (
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<span
|
||||
className={`codicon codicon-${isFeedback ? "feedback" : "output"}`}
|
||||
style={{ marginRight: "6px" }}></span>
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
marginRight: "8px",
|
||||
}}>
|
||||
<div className="flex items-center">
|
||||
<span className={`codicon codicon-${isFeedback ? "feedback" : "output"} mr-[6px]`}></span>
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis mr-2">
|
||||
{isFeedback ? "User Edits" : "Console Logs"}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{path?.startsWith(".") && <span>.</span>}
|
||||
<span
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
marginRight: "8px",
|
||||
// trick to get ellipsis at beginning of string
|
||||
direction: "rtl",
|
||||
textAlign: "left",
|
||||
}}>
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis mr-2 rtl text-left">
|
||||
{removeLeadingNonAlphanumeric(path ?? "") + "\u200E"}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
<div className="flex-grow"></div>
|
||||
{progressStatus && progressStatus.text && (
|
||||
<>
|
||||
{progressStatus.icon && <span className={`codicon codicon-${progressStatus.icon} mr-1`} />}
|
||||
|
|
@ -116,12 +84,7 @@ const CodeAccordian = ({
|
|||
</div>
|
||||
)}
|
||||
{(!(path || isFeedback || isConsoleLogs) || isExpanded) && (
|
||||
<div
|
||||
style={{
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden",
|
||||
maxWidth: "100%",
|
||||
}}>
|
||||
<div className="overflow-x-auto overflow-y-hidden max-w-full">
|
||||
<CodeBlock
|
||||
source={(code ?? diff ?? "").trim()}
|
||||
language={diff !== undefined ? "diff" : inferredLanguage}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { memo, useEffect, useRef, useCallback, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import React, { memo, useEffect, useRef, useCallback, useState } from "react"
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
import { getHighlighter, isLanguageLoaded, normalizeLanguage, ExtendedLanguage } from "@src/utils/highlighter"
|
||||
import { bundledLanguages } from "shiki"
|
||||
import type { ShikiTransformer } from "shiki"
|
||||
import { ChevronDown, ChevronUp, WrapText, AlignJustify, Copy, Check } from "lucide-react"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
export const CODE_BLOCK_BG_COLOR = "var(--vscode-editor-background, --vscode-sideBar-background, rgb(30 30 30))"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export const WRAPPER_ALPHA = "cc" // 80% opacity
|
||||
// Configuration constants
|
||||
export const WINDOW_SHADE_SETTINGS = {
|
||||
|
|
@ -36,179 +36,116 @@ interface CodeBlockProps {
|
|||
onLanguageChange?: (language: string) => void
|
||||
}
|
||||
|
||||
const CodeBlockButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--vscode-foreground);
|
||||
cursor: var(--copy-button-cursor, default);
|
||||
padding: 4px;
|
||||
margin: 0 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.4;
|
||||
border-radius: 3px;
|
||||
pointer-events: var(--copy-button-events, none);
|
||||
margin-left: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
interface CodeBlockButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--vscode-toolbar-hoverBackground);
|
||||
opacity: 1;
|
||||
}
|
||||
const CodeBlockButton = ({ children, ...props }: CodeBlockButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
className="bg-transparent border-none text-vscode-foreground p-1 mx-0 flex items-center justify-center opacity-40 rounded-[3px] ml-1 h-6 w-6 hover:bg-vscode-toolbar-hoverBackground hover:opacity-100"
|
||||
style={{
|
||||
cursor: "var(--copy-button-cursor, default)",
|
||||
pointerEvents: "var(--copy-button-events, none)" as any,
|
||||
}}
|
||||
{...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
/* Style for Lucide icons to ensure consistent sizing and positioning */
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
interface CodeBlockButtonWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const CodeBlockButtonWrapper = styled.div`
|
||||
position: fixed;
|
||||
top: var(--copy-button-top);
|
||||
right: var(--copy-button-right, 8px);
|
||||
height: auto;
|
||||
z-index: 100;
|
||||
background: ${CODE_BLOCK_BG_COLOR}${WRAPPER_ALPHA};
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
opacity: var(--copy-button-opacity, 0);
|
||||
padding: 4px 6px;
|
||||
border-radius: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
const CodeBlockButtonWrapper = React.forwardRef<HTMLDivElement, CodeBlockButtonWrapperProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="fixed h-auto z-[100] overflow-visible pointer-events-none p-[4px_6px] rounded-[3px] inline-flex items-center justify-center hover:bg-vscode-editor-background hover:!opacity-100 bg-vscode-editor-background/[.80]"
|
||||
style={{
|
||||
top: "var(--copy-button-top)",
|
||||
right: "var(--copy-button-right, 8px)",
|
||||
opacity: "var(--copy-button-opacity, 0)",
|
||||
}}
|
||||
{...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
CodeBlockButtonWrapper.displayName = "CodeBlockButtonWrapper"
|
||||
|
||||
&:hover {
|
||||
background: var(--vscode-editor-background);
|
||||
opacity: 1 !important;
|
||||
}
|
||||
interface CodeBlockContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode
|
||||
"data-partially-visible"?: boolean
|
||||
}
|
||||
|
||||
${CodeBlockButton} {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
`
|
||||
const CodeBlockContainer = React.forwardRef<HTMLDivElement, CodeBlockContainerProps>(
|
||||
({ children, "data-partially-visible": partiallyVisible, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="relative overflow-hidden border-b-4 border-vscode-sideBar-background bg-background"
|
||||
data-partially-visible={partiallyVisible}
|
||||
{...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
CodeBlockContainer.displayName = "CodeBlockContainer"
|
||||
|
||||
const CodeBlockContainer = styled.div`
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-bottom: 4px solid var(--vscode-sideBar-background);
|
||||
background-color: ${CODE_BLOCK_BG_COLOR};
|
||||
|
||||
${CodeBlockButtonWrapper} {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s; /* Keep opacity transition for buttons */
|
||||
}
|
||||
|
||||
&[data-partially-visible="true"]:hover ${CodeBlockButtonWrapper} {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledPre = styled.div<{
|
||||
interface StyledPreProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
preStyle?: React.CSSProperties
|
||||
wordwrap?: "true" | "false" | undefined
|
||||
windowshade?: "true" | "false"
|
||||
collapsedHeight?: number
|
||||
}>`
|
||||
background-color: ${CODE_BLOCK_BG_COLOR};
|
||||
max-height: ${({ windowshade, collapsedHeight }) =>
|
||||
windowshade === "true" ? `${collapsedHeight || WINDOW_SHADE_SETTINGS.collapsedHeight}px` : "none"};
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
// transition: max-height ${WINDOW_SHADE_SETTINGS.transitionDelayS} ease-out;
|
||||
border-radius: 5px;
|
||||
${({ preStyle }) => preStyle && { ...preStyle }}
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: ${CODE_BLOCK_BG_COLOR};
|
||||
border-radius: 5px;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
export const StyledPre = React.forwardRef<HTMLDivElement, StyledPreProps>(
|
||||
({ preStyle, wordwrap, windowshade, collapsedHeight, children, className, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"overflow-y-auto p-[10px] rounded-[5px]",
|
||||
"text-vscode-editor-font-size font-vscode-editor-font-family",
|
||||
"bg-background",
|
||||
windowshade === "true"
|
||||
? `[max-height:${collapsedHeight || WINDOW_SHADE_SETTINGS.collapsedHeight}px]`
|
||||
: "max-h-none",
|
||||
wordwrap === "false" ? "whitespace-pre" : "whitespace-pre-wrap",
|
||||
"break-normal",
|
||||
wordwrap === "false" ? "[overflow-wrap:normal]" : "break-words",
|
||||
className,
|
||||
)}
|
||||
style={preStyle}
|
||||
{...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
StyledPre.displayName = "StyledPre"
|
||||
|
||||
pre,
|
||||
code {
|
||||
/* Undefined wordwrap defaults to true (pre-wrap) behavior */
|
||||
white-space: ${({ wordwrap }) => (wordwrap === "false" ? "pre" : "pre-wrap")};
|
||||
word-break: ${({ wordwrap }) => (wordwrap === "false" ? "normal" : "normal")};
|
||||
overflow-wrap: ${({ wordwrap }) => (wordwrap === "false" ? "normal" : "break-word")};
|
||||
font-size: var(--vscode-editor-font-size, var(--vscode-font-size, 12px));
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
}
|
||||
|
||||
pre > code {
|
||||
.hljs-deletion {
|
||||
background-color: var(--vscode-diffEditor-removedTextBackground);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
.hljs-addition {
|
||||
background-color: var(--vscode-diffEditor-insertedTextBackground);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: var(--vscode-editor-foreground, #fff);
|
||||
background-color: ${CODE_BLOCK_BG_COLOR};
|
||||
}
|
||||
`
|
||||
|
||||
const LanguageSelect = styled.select`
|
||||
font-size: 12px;
|
||||
color: var(--vscode-foreground);
|
||||
opacity: 0.4;
|
||||
font-family: monospace;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
height: 24px;
|
||||
|
||||
& option {
|
||||
background: var(--vscode-editor-background);
|
||||
color: var(--vscode-foreground);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--vscode-scrollbarSlider-background);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: var(--vscode-toolbar-hoverBackground);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
outline: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
`
|
||||
interface LanguageSelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
||||
children: React.ReactNode
|
||||
}
|
||||
const LanguageSelect = ({ children, className, ...props }: LanguageSelectProps) => {
|
||||
return (
|
||||
<select
|
||||
className={cn(
|
||||
"text-xs text-vscode-foreground opacity-40 font-mono appearance-none bg-transparent border-none cursor-pointer p-1 m-0 align-middle h-6 hover:opacity-100 hover:bg-vscode-toolbar-hoverBackground hover:rounded-[3px] focus:opacity-100 focus:outline-none focus:rounded-[3px]",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
||||
const CodeBlock = memo(
|
||||
({
|
||||
|
|
@ -243,7 +180,7 @@ const CodeBlock = memo(
|
|||
|
||||
// Syntax highlighting with cached Shiki instance
|
||||
useEffect(() => {
|
||||
const fallback = `<pre style="padding: 0; margin: 0;"><code class="hljs language-${currentLanguage || "txt"}">${source || ""}</code></pre>`
|
||||
const fallback = `<pre class="p-0 m-0"><code class="hljs language-${currentLanguage || "txt"}">${source || ""}</code></pre>`
|
||||
const highlight = async () => {
|
||||
// Show plain text if language needs to be loaded
|
||||
if (currentLanguage && !isLanguageLoaded(currentLanguage)) {
|
||||
|
|
@ -257,7 +194,7 @@ const CodeBlock = memo(
|
|||
transformers: [
|
||||
{
|
||||
pre(node) {
|
||||
node.properties.style = "padding: 0; margin: 0;"
|
||||
node.properties.class = "p-0 m-0"
|
||||
return node
|
||||
},
|
||||
code(node) {
|
||||
|
|
@ -620,7 +557,7 @@ const CodeBlock = memo(
|
|||
<CodeBlockButtonWrapper
|
||||
ref={copyButtonWrapperRef}
|
||||
onMouseOver={() => updateCodeBlockButtonPosition()}
|
||||
style={{ gap: 0 }}>
|
||||
className="gap-0">
|
||||
{language && (
|
||||
<LanguageSelect
|
||||
value={currentLanguage}
|
||||
|
|
@ -643,7 +580,7 @@ const CodeBlock = memo(
|
|||
language && (
|
||||
<option
|
||||
value={normalizeLanguage(language)}
|
||||
style={{ fontWeight: "bold", textAlign: "left", fontSize: "1.2em" }}>
|
||||
className="font-bold text-left text-[1.2em]">
|
||||
{normalizeLanguage(language)}
|
||||
</option>
|
||||
)
|
||||
|
|
@ -658,13 +595,12 @@ const CodeBlock = memo(
|
|||
<option
|
||||
key={normalizedLang}
|
||||
value={normalizedLang}
|
||||
style={{
|
||||
fontWeight:
|
||||
normalizedLang === currentLanguage ? "bold" : "normal",
|
||||
textAlign: "left",
|
||||
fontSize:
|
||||
normalizedLang === currentLanguage ? "1.2em" : "inherit",
|
||||
}}>
|
||||
className={cn(
|
||||
"text-left",
|
||||
normalizedLang === currentLanguage
|
||||
? "font-bold text-[1.2em]"
|
||||
: "font-normal",
|
||||
)}>
|
||||
{normalizedLang}
|
||||
</option>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { memo, useEffect } from "react"
|
||||
import React, { memo, useEffect, FC, PropsWithChildren } from "react"
|
||||
import { useRemark } from "react-remark"
|
||||
import styled from "styled-components"
|
||||
import { visit } from "unist-util-visit"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
|
@ -55,71 +54,45 @@ const remarkUrlToLink = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const StyledMarkdown = styled.div`
|
||||
code:not(pre > code) {
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
filter: saturation(110%) brightness(95%);
|
||||
color: var(--vscode-textPreformat-foreground) !important;
|
||||
background-color: var(--vscode-textPreformat-background) !important;
|
||||
padding: 0px 2px;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Target only Dark High Contrast theme using the data attribute VS Code adds to the body */
|
||||
body[data-vscode-theme-kind="vscode-high-contrast"] & code:not(pre > code) {
|
||||
color: var(
|
||||
--vscode-editorInlayHint-foreground,
|
||||
var(--vscode-symbolIcon-stringForeground, var(--vscode-charts-orange, #e9a700))
|
||||
);
|
||||
}
|
||||
|
||||
font-family:
|
||||
var(--vscode-font-family),
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
|
||||
font-size: var(--vscode-font-size, 13px);
|
||||
|
||||
p,
|
||||
li,
|
||||
ol,
|
||||
ul {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2.5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--vscode-textLink-foreground);
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-decoration-color: var(--vscode-textLink-foreground);
|
||||
&:hover {
|
||||
color: var(--vscode-textLink-activeForeground);
|
||||
text-decoration-style: solid;
|
||||
text-decoration-color: var(--vscode-textLink-activeForeground);
|
||||
}
|
||||
}
|
||||
`
|
||||
const StyledMarkdown: FC<PropsWithChildren<unknown>> = ({ children }) => {
|
||||
// Note: Tailwind doesn't have a direct equivalent for targeting based on body data attributes like `body[data-vscode-theme-kind="vscode-high-contrast"]`.
|
||||
// This specific high-contrast styling might need to be handled differently, possibly via a theme-aware context or by adding a class to this component when high contrast is active.
|
||||
// For now, the general styles are applied.
|
||||
return (
|
||||
<div
|
||||
className="
|
||||
font-vscode-font-family
|
||||
text-vscode-font-size
|
||||
[&_p]:leading-tight
|
||||
[&_li]:leading-tight
|
||||
[&_ol]:leading-tight
|
||||
[&_ul]:leading-tight
|
||||
[&_ol]:pl-[2.5em]
|
||||
[&_ul]:pl-[2.5em]
|
||||
[&_ol]:ml-0
|
||||
[&_ul]:ml-0
|
||||
[&_p]:whitespace-pre-wrap
|
||||
[&_a]:text-vscode-textLink-foreground
|
||||
[&_a]:underline
|
||||
[&_a]:decoration-dotted
|
||||
[&_a]:decoration-vscode-textLink-foreground
|
||||
hover:[&_a]:text-vscode-textLink-activeForeground
|
||||
hover:[&_a]:decoration-solid
|
||||
hover:[&_a]:decoration-vscode-textLink-activeForeground
|
||||
[&_code:not(pre>code)]:font-vscode-editor-font-family
|
||||
[&_code:not(pre>code)]:saturate-[1.1]
|
||||
[&_code:not(pre>code)]:brightness-95
|
||||
[&_code:not(pre>code)]:text-vscode-textPreformat-foreground!
|
||||
[&_code:not(pre>code)]:bg-vscode-textPreformat-background!
|
||||
[&_code:not(pre>code)]:px-[2px]
|
||||
[&_code:not(pre>code)]:whitespace-pre-line
|
||||
[&_code:not(pre>code)]:break-words
|
||||
[&_code:not(pre>code)]:overflow-wrap-anywhere
|
||||
">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
const { theme } = useExtensionState()
|
||||
|
|
@ -228,7 +201,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
|||
}, [markdown, setMarkdown, theme])
|
||||
|
||||
return (
|
||||
<div style={{}}>
|
||||
<div>
|
||||
<StyledMarkdown>{reactContent}</StyledMarkdown>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useEffect, useRef, useState } from "react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import mermaid from "mermaid"
|
||||
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
|
||||
import styled from "styled-components"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
import CodeBlock from "./CodeBlock"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const MERMAID_THEME = {
|
||||
background: "#1e1e1e", // VS Code dark theme background
|
||||
|
|
@ -152,37 +152,18 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
|
|||
{isLoading && <LoadingMessage>{t("common:mermaid.loading")}</LoadingMessage>}
|
||||
|
||||
{error ? (
|
||||
<div style={{ marginTop: "0px", overflow: "hidden", marginBottom: "8px" }}>
|
||||
<div className="mt-0 overflow-hidden mb-2">
|
||||
<div
|
||||
style={{
|
||||
borderBottom: isErrorExpanded ? "1px solid var(--vscode-editorGroup-border)" : "none",
|
||||
fontWeight: "normal",
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
color: "var(--vscode-editor-foreground)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className={cn(
|
||||
"font-normal text-vscode-font-size text-vscode-editor-foreground flex items-center justify-between cursor-pointer",
|
||||
isErrorExpanded ? "border-b border-vscode-editorGroup-border" : "border-b-0",
|
||||
)}
|
||||
onClick={() => setIsErrorExpanded(!isErrorExpanded)}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
<span
|
||||
className="codicon codicon-warning"
|
||||
style={{
|
||||
color: "var(--vscode-editorWarning-foreground)",
|
||||
opacity: 0.8,
|
||||
fontSize: 16,
|
||||
marginBottom: "-1.5px",
|
||||
}}></span>
|
||||
<span style={{ fontWeight: "bold" }}>{t("common:mermaid.render_error")}</span>
|
||||
<div className="flex items-center gap-2.5 flex-grow">
|
||||
<span className="codicon codicon-warning text-vscode-editorWarning-foreground opacity-80 text-base mb-[-1.5px]"></span>
|
||||
<span className="font-bold">{t("common:mermaid.render_error")}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div className="flex items-center">
|
||||
<CopyButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
|
@ -195,21 +176,14 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
|
|||
</div>
|
||||
</div>
|
||||
{isErrorExpanded && (
|
||||
<div
|
||||
style={{
|
||||
padding: "8px",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
borderTop: "none",
|
||||
}}>
|
||||
<div style={{ marginBottom: "8px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
{error}
|
||||
</div>
|
||||
<div className="p-2 bg-vscode-editor-background border-t-0">
|
||||
<div className="mb-2 text-vscode-descriptionForeground">{error}</div>
|
||||
<CodeBlock language="mermaid" source={code} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<SvgContainer onClick={handleClick} ref={containerRef} $isLoading={isLoading} />
|
||||
<SvgContainer onClick={handleClick} ref={containerRef} isLoading={isLoading} />
|
||||
)}
|
||||
</MermaidBlockContainer>
|
||||
)
|
||||
|
|
@ -266,44 +240,40 @@ async function svgToPng(svgEl: SVGElement): Promise<string> {
|
|||
})
|
||||
}
|
||||
|
||||
const MermaidBlockContainer = styled.div`
|
||||
position: relative;
|
||||
margin: 8px 0;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div`
|
||||
padding: 8px 0;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-style: italic;
|
||||
font-size: 0.9em;
|
||||
`
|
||||
|
||||
const CopyButton = styled.button`
|
||||
padding: 3px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
color: var(--vscode-editor-foreground);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
interface SvgContainerProps {
|
||||
$isLoading: boolean
|
||||
const MermaidBlockContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return <div className="relative my-2">{children}</div>
|
||||
}
|
||||
|
||||
const SvgContainer = styled.div<SvgContainerProps>`
|
||||
opacity: ${(props) => (props.$isLoading ? 0.3 : 1)};
|
||||
min-height: 20px;
|
||||
transition: opacity 0.2s ease;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
const LoadingMessage: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return <div className="py-2 text-vscode-descriptionForeground italic text-[0.9em]">{children}</div>
|
||||
}
|
||||
|
||||
const CopyButton: React.FC<React.ButtonHTMLAttributes<HTMLButtonElement>> = ({ children, ...props }) => {
|
||||
return (
|
||||
<button
|
||||
className="p-[3px] h-6 mr-1 text-vscode-editor-foreground flex items-center justify-center bg-transparent border-none cursor-pointer hover:opacity-80"
|
||||
{...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
interface SvgContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
isLoading: boolean
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const SvgContainer = React.forwardRef<HTMLDivElement, SvgContainerProps>(({ isLoading, children, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"min-h-[20px] transition-opacity duration-200 ease-in-out cursor-pointer flex justify-center",
|
||||
isLoading ? "opacity-30" : "opacity-100",
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
SvgContainer.displayName = "SvgContainer"
|
||||
|
|
|
|||
|
|
@ -1,29 +1,10 @@
|
|||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { TelemetrySetting } from "@roo/shared/TelemetrySetting"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Trans } from "react-i18next"
|
||||
|
||||
const BannerContainer = styled.div`
|
||||
background-color: var(--vscode-banner-background);
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 6px;
|
||||
`
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
& > vscode-button {
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const TelemetryBanner = () => {
|
||||
const { t } = useAppTranslation()
|
||||
|
|
@ -48,7 +29,7 @@ const TelemetryBanner = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<BannerContainer>
|
||||
<div className={`bg-vscode-banner-background p-[12px_20px] flex flex-col gap-[10px] shrink-0 mb-[6px]`}>
|
||||
<div>
|
||||
<strong>{t("welcome:telemetry.title")}</strong>
|
||||
<div className="mt-1">
|
||||
|
|
@ -64,15 +45,15 @@ const TelemetryBanner = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonContainer>
|
||||
<VSCodeButton appearance="primary" onClick={handleAllow} disabled={hasChosen}>
|
||||
<div className={cn("flex gap-[8px] w-full")}>
|
||||
<VSCodeButton appearance="primary" onClick={handleAllow} disabled={hasChosen} className="flex-1">
|
||||
{t("welcome:telemetry.allow")}
|
||||
</VSCodeButton>
|
||||
<VSCodeButton appearance="secondary" onClick={handleDeny} disabled={hasChosen}>
|
||||
<VSCodeButton appearance="secondary" onClick={handleDeny} disabled={hasChosen} className="flex-1">
|
||||
{t("welcome:telemetry.deny")}
|
||||
</VSCodeButton>
|
||||
</ButtonContainer>
|
||||
</BannerContainer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,56 +37,24 @@ const Thumbnails = ({ images, style, setImages, onHeightChange }: ThumbnailsProp
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 5,
|
||||
rowGap: 3,
|
||||
...style,
|
||||
}}>
|
||||
<div ref={containerRef} className="flex flex-wrap gap-[5px] gap-y-[3px]" style={style}>
|
||||
{images.map((image, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{ position: "relative" }}
|
||||
className="relative"
|
||||
onMouseEnter={() => setHoveredIndex(index)}
|
||||
onMouseLeave={() => setHoveredIndex(null)}>
|
||||
<img
|
||||
src={image}
|
||||
alt={`Thumbnail ${index + 1}`}
|
||||
style={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
objectFit: "cover",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className="w-[34px] h-[34px] object-cover rounded cursor-pointer"
|
||||
onClick={() => handleImageClick(image)}
|
||||
/>
|
||||
{isDeletable && hoveredIndex === index && (
|
||||
<div
|
||||
onClick={() => handleDelete(index)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: -4,
|
||||
right: -4,
|
||||
width: 13,
|
||||
height: 13,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: "var(--vscode-badge-background)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
<span
|
||||
className="codicon codicon-close"
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
fontSize: 10,
|
||||
fontWeight: "bold",
|
||||
}}></span>
|
||||
className="absolute -top-1 -right-1 w-[13px] h-[13px] rounded-full bg-vscode-badge-background flex justify-center items-center cursor-pointer">
|
||||
<span className="codicon codicon-close text-vscode-foreground text-[10px] font-bold"></span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,12 +8,7 @@ interface VSCodeButtonLinkProps {
|
|||
}
|
||||
|
||||
export const VSCodeButtonLink = ({ href, children, ...props }: VSCodeButtonLinkProps) => (
|
||||
<a
|
||||
href={href}
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
}}>
|
||||
<a href={href} className="no-underline text-inherit">
|
||||
<VSCodeButton {...props}>{children}</VSCodeButton>
|
||||
</a>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,13 +29,11 @@ const HistoryPreview = () => {
|
|||
<CopyButton itemTask={item.task} />
|
||||
</div>
|
||||
<div
|
||||
className="text-vscode-foreground overflow-hidden whitespace-pre-wrap"
|
||||
className="text-vscode-foreground overflow-hidden whitespace-pre-wrap break-words overflow-wrap-anywhere"
|
||||
style={{
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: "vertical",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
{item.task}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<VSCodeTextField
|
||||
style={{ width: "100%" }}
|
||||
className="w-full"
|
||||
placeholder={t("history:searchPlaceholder")}
|
||||
value={searchQuery}
|
||||
data-testid="history-search-input"
|
||||
|
|
@ -111,28 +111,18 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
setSortOption("mostRelevant")
|
||||
}
|
||||
}}>
|
||||
<div
|
||||
slot="start"
|
||||
className="codicon codicon-search"
|
||||
style={{ fontSize: 13, marginTop: 2.5, opacity: 0.8 }}
|
||||
/>
|
||||
<div slot="start" className="codicon codicon-search text-[13px] mt-[2.5px] opacity-80" />
|
||||
{searchQuery && (
|
||||
<div
|
||||
className="input-icon-button codicon codicon-close"
|
||||
className="input-icon-button codicon codicon-close flex justify-center items-center h-full"
|
||||
aria-label="Clear search"
|
||||
onClick={() => setSearchQuery("")}
|
||||
slot="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VSCodeTextField>
|
||||
<VSCodeRadioGroup
|
||||
style={{ display: "flex", flexWrap: "wrap" }}
|
||||
className="flex flex-wrap"
|
||||
value={sortOption}
|
||||
role="radiogroup"
|
||||
onChange={(e) => setSortOption((e.target as HTMLInputElement).value as SortOption)}>
|
||||
|
|
@ -152,7 +142,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
value="mostRelevant"
|
||||
disabled={!searchQuery}
|
||||
data-testid="radio-most-relevant"
|
||||
style={{ opacity: searchQuery ? 1 : 0.5 }}>
|
||||
className={searchQuery ? "opacity-100" : "opacity-50"}>
|
||||
{t("history:mostRelevant")}
|
||||
</VSCodeRadio>
|
||||
</VSCodeRadioGroup>
|
||||
|
|
@ -197,10 +187,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
|
||||
<TabContent className="p-0">
|
||||
<Virtuoso
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
overflowY: "scroll",
|
||||
}}
|
||||
className="flex-grow overflow-y-scroll"
|
||||
data={tasks}
|
||||
data-testid="virtuoso-container"
|
||||
initialTopMostItemIndex={0}
|
||||
|
|
@ -274,76 +261,33 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="text-vscode-font-size text-vscode-foreground overflow-hidden whitespace-pre-wrap break-words overflow-wrap-anywhere"
|
||||
style={{
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
color: "var(--vscode-foreground)",
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
data-testid="task-content"
|
||||
dangerouslySetInnerHTML={{ __html: item.task }}
|
||||
/>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div
|
||||
data-testid="tokens-container"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
flexWrap: "wrap",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
<span className="font-medium text-vscode-descriptionForeground">
|
||||
{t("history:tokensLabel")}
|
||||
</span>
|
||||
<span
|
||||
data-testid="tokens-in"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-up"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-2px",
|
||||
}}
|
||||
/>
|
||||
className="flex items-center gap-[3px] text-vscode-descriptionForeground">
|
||||
<i className="codicon codicon-arrow-up text-xs font-bold mb-[-2px]" />
|
||||
{formatLargeNumber(item.tokensIn || 0)}
|
||||
</span>
|
||||
<span
|
||||
data-testid="tokens-out"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-down"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-2px",
|
||||
}}
|
||||
/>
|
||||
className="flex items-center gap-[3px] text-vscode-descriptionForeground">
|
||||
<i className="codicon codicon-arrow-down text-xs font-bold mb-[-2px]" />
|
||||
{formatLargeNumber(item.tokensOut || 0)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -358,75 +302,32 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
|||
{!!item.cacheWrites && (
|
||||
<div
|
||||
data-testid="cache-container"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
flexWrap: "wrap",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
className="flex items-center gap-1 flex-wrap">
|
||||
<span className="font-medium text-vscode-descriptionForeground">
|
||||
{t("history:cacheLabel")}
|
||||
</span>
|
||||
<span
|
||||
data-testid="cache-writes"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-database"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "-1px",
|
||||
}}
|
||||
/>
|
||||
className="flex items-center gap-[3px] text-vscode-descriptionForeground">
|
||||
<i className="codicon codicon-database text-xs font-bold mb-[-1px]" />
|
||||
+{formatLargeNumber(item.cacheWrites || 0)}
|
||||
</span>
|
||||
<span
|
||||
data-testid="cache-reads"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "3px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<i
|
||||
className="codicon codicon-arrow-right"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: 0,
|
||||
}}
|
||||
/>
|
||||
className="flex items-center gap-[3px] text-vscode-descriptionForeground">
|
||||
<i className="codicon codicon-arrow-right text-xs font-bold mb-0" />
|
||||
{formatLargeNumber(item.cacheReads || 0)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!item.totalCost && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginTop: -2,
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<div className="flex justify-between items-center mt-[-2px]">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-medium text-vscode-descriptionForeground">
|
||||
{t("history:apiCostLabel")}
|
||||
</span>
|
||||
<span style={{ color: "var(--vscode-descriptionForeground)" }}>
|
||||
<span className="text-vscode-descriptionForeground">
|
||||
${item.totalCost?.toFixed(4)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,18 +16,11 @@ const McpEnabledToggle = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<div className="mb-5">
|
||||
<VSCodeCheckbox checked={mcpEnabled} onChange={handleChange}>
|
||||
<span style={{ fontWeight: "500" }}>{t("mcp:enableToggle.title")}</span>
|
||||
<span className="font-medium">{t("mcp:enableToggle.title")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{t("mcp:enableToggle.description")}
|
||||
</p>
|
||||
<p className="text-xs mt-[5px] text-vscode-descriptionForeground">{t("mcp:enableToggle.description")}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,26 +9,12 @@ const McpResourceRow = ({ item }: McpResourceRowProps) => {
|
|||
const uri = hasUri ? item.uri : item.uriTemplate
|
||||
|
||||
return (
|
||||
<div
|
||||
key={uri}
|
||||
style={{
|
||||
padding: "3px 0",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "4px",
|
||||
}}>
|
||||
<span className={`codicon codicon-symbol-file`} style={{ marginRight: "6px" }} />
|
||||
<span style={{ fontWeight: 500, wordBreak: "break-all" }}>{uri}</span>
|
||||
<div key={uri} className="py-[3px]">
|
||||
<div className="flex items-center mb-1">
|
||||
<span className={`codicon codicon-symbol-file mr-[6px]`} />
|
||||
<span className="font-medium break-all">{uri}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
opacity: 0.8,
|
||||
margin: "4px 0",
|
||||
}}>
|
||||
<div className="text-xs opacity-80 my-1">
|
||||
{item.name && item.description
|
||||
? `${item.name}: ${item.description}`
|
||||
: !item.name && item.description
|
||||
|
|
@ -37,18 +23,9 @@ const McpResourceRow = ({ item }: McpResourceRowProps) => {
|
|||
? item.name
|
||||
: "No description"}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
<span style={{ opacity: 0.8 }}>Returns </span>
|
||||
<code
|
||||
style={{
|
||||
color: "var(--vscode-textPreformat-foreground)",
|
||||
background: "var(--vscode-textPreformat-background)",
|
||||
padding: "1px 4px",
|
||||
borderRadius: "3px",
|
||||
}}>
|
||||
<div className="text-xs">
|
||||
<span className="opacity-80">Returns </span>
|
||||
<code className="text-vscode-textPreformat-foreground bg-vscode-textPreformat-background px-1 py-[1px] rounded-[3px]">
|
||||
{item.mimeType || "Unknown"}
|
||||
</code>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,18 +24,14 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tool.name}
|
||||
style={{
|
||||
padding: "3px 0",
|
||||
}}>
|
||||
<div key={tool.name} className="py-[3px]">
|
||||
<div
|
||||
data-testid="tool-row-container"
|
||||
style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}
|
||||
className="flex items-center justify-between"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<span className="codicon codicon-symbol-method" style={{ marginRight: "6px" }}></span>
|
||||
<span style={{ fontWeight: 500 }}>{tool.name}</span>
|
||||
<div className="flex items-center">
|
||||
<span className="codicon codicon-symbol-method mr-[6px]"></span>
|
||||
<span className="font-medium">{tool.name}</span>
|
||||
</div>
|
||||
{serverName && alwaysAllowMcp && (
|
||||
<VSCodeCheckbox checked={tool.alwaysAllow} onChange={handleAlwaysAllowChange} data-tool={tool.name}>
|
||||
|
|
@ -43,32 +39,12 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
|
|||
</VSCodeCheckbox>
|
||||
)}
|
||||
</div>
|
||||
{tool.description && (
|
||||
<div
|
||||
style={{
|
||||
marginLeft: "0px",
|
||||
marginTop: "4px",
|
||||
opacity: 0.8,
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
{tool.description}
|
||||
</div>
|
||||
)}
|
||||
{tool.description && <div className="ml-0 mt-1 opacity-80 text-xs">{tool.description}</div>}
|
||||
{tool.inputSchema &&
|
||||
"properties" in tool.inputSchema &&
|
||||
Object.keys(tool.inputSchema.properties as Record<string, any>).length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "8px",
|
||||
fontSize: "12px",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 30%, transparent)",
|
||||
borderRadius: "3px",
|
||||
padding: "8px",
|
||||
}}>
|
||||
<div
|
||||
style={{ marginBottom: "4px", opacity: 0.8, fontSize: "11px", textTransform: "uppercase" }}>
|
||||
{t("mcp:tool.parameters")}
|
||||
</div>
|
||||
<div className="mt-2 text-xs rounded-[3px] p-2 border border-vscode-descriptionForeground-transparent-30">
|
||||
<div className="mb-1 opacity-80 text-[11px] uppercase">{t("mcp:tool.parameters")}</div>
|
||||
{Object.entries(tool.inputSchema.properties as Record<string, any>).map(
|
||||
([paramName, schema]) => {
|
||||
const isRequired =
|
||||
|
|
@ -78,29 +54,12 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
|
|||
tool.inputSchema.required.includes(paramName)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={paramName}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
marginTop: "4px",
|
||||
}}>
|
||||
<code
|
||||
style={{
|
||||
color: "var(--vscode-textPreformat-foreground)",
|
||||
marginRight: "8px",
|
||||
}}>
|
||||
<div key={paramName} className="flex items-baseline mt-1">
|
||||
<code className="text-vscode-textPreformat-foreground mr-2">
|
||||
{paramName}
|
||||
{isRequired && (
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>*</span>
|
||||
)}
|
||||
{isRequired && <span className="text-vscode-errorForeground">*</span>}
|
||||
</code>
|
||||
<span
|
||||
style={{
|
||||
opacity: 0.8,
|
||||
overflowWrap: "break-word",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<span className="opacity-80 break-words">
|
||||
{schema.description || t("mcp:tool.noDescription")}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -112,5 +71,4 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpToolRow
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@src/components/ui"
|
||||
import { cn } from "@/lib/utils" // Import cn utility
|
||||
|
||||
import { Tab, TabContent, TabHeader } from "../common/Tab"
|
||||
|
||||
|
|
@ -54,20 +55,12 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|||
</TabHeader>
|
||||
|
||||
<TabContent>
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
fontSize: "13px",
|
||||
marginBottom: "10px",
|
||||
marginTop: "5px",
|
||||
}}>
|
||||
<div className="text-vscode-foreground text-[13px] mb-[10px] mt-[5px]">
|
||||
<Trans i18nKey="mcp:description">
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol" style={{ display: "inline" }}>
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol" className="inline">
|
||||
Model Context Protocol
|
||||
</VSCodeLink>
|
||||
<VSCodeLink
|
||||
href="https://github.com/modelcontextprotocol/servers"
|
||||
style={{ display: "inline" }}>
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" className="inline">
|
||||
community-made servers
|
||||
</VSCodeLink>
|
||||
</Trans>
|
||||
|
|
@ -77,28 +70,23 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|||
|
||||
{mcpEnabled && (
|
||||
<>
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<div className="mb-[15px]">
|
||||
<VSCodeCheckbox
|
||||
checked={enableMcpServerCreation}
|
||||
onChange={(e: any) => {
|
||||
setEnableMcpServerCreation(e.target.checked)
|
||||
vscode.postMessage({ type: "enableMcpServerCreation", bool: e.target.checked })
|
||||
}}>
|
||||
<span style={{ fontWeight: "500" }}>{t("mcp:enableServerCreation.title")}</span>
|
||||
<span className="font-medium">{t("mcp:enableServerCreation.title")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<p className="text-xs mt-[5px] text-vscode-descriptionForeground">
|
||||
{t("mcp:enableServerCreation.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Server List */}
|
||||
{servers.length > 0 && (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||
<div className="flex flex-col gap-[10px]">
|
||||
{servers.map((server) => (
|
||||
<ServerRow
|
||||
key={`${server.name}-${server.source || "global"}`}
|
||||
|
|
@ -110,23 +98,23 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|||
)}
|
||||
|
||||
{/* Edit Settings Buttons */}
|
||||
<div style={{ marginTop: "10px", width: "100%", display: "flex", gap: "10px" }}>
|
||||
<div className="mt-[10px] w-full flex gap-[10px]">
|
||||
<Button
|
||||
variant="secondary"
|
||||
style={{ flex: 1 }}
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openMcpSettings" })
|
||||
}}>
|
||||
<span className="codicon codicon-edit" style={{ marginRight: "6px" }}></span>
|
||||
<span className="codicon codicon-edit mr-[6px]"></span>
|
||||
{t("mcp:editGlobalMCP")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
style={{ flex: 1 }}
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openProjectMcpSettings" })
|
||||
}}>
|
||||
<span className="codicon codicon-edit" style={{ marginRight: "6px" }}></span>
|
||||
<span className="codicon codicon-edit mr-[6px]"></span>
|
||||
{t("mcp:editProjectMCP")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -157,17 +145,6 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
{ value: 3600, label: t("mcp:networkTimeout.options.60minutes") },
|
||||
]
|
||||
|
||||
const getStatusColor = () => {
|
||||
switch (server.status) {
|
||||
case "connected":
|
||||
return "var(--vscode-testing-iconPassed)"
|
||||
case "connecting":
|
||||
return "var(--vscode-charts-yellow)"
|
||||
case "disconnected":
|
||||
return "var(--vscode-testing-iconFailed)"
|
||||
}
|
||||
}
|
||||
|
||||
const handleRowClick = () => {
|
||||
if (server.status === "connected") {
|
||||
setIsExpanded(!isExpanded)
|
||||
|
|
@ -203,74 +180,48 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<div className="mb-[10px]">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "8px",
|
||||
background: "var(--vscode-textCodeBlock-background)",
|
||||
cursor: server.status === "connected" ? "pointer" : "default",
|
||||
borderRadius: isExpanded || server.status === "connected" ? "4px" : "4px 4px 0 0",
|
||||
opacity: server.disabled ? 0.6 : 1,
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center p-[8px] bg-vscode-textCodeBlock-background",
|
||||
server.status === "connected" ? "cursor-pointer" : "cursor-default",
|
||||
isExpanded || server.status === "connected" ? "rounded-[4px]" : "rounded-t-[4px]",
|
||||
server.disabled ? "opacity-60" : "opacity-100",
|
||||
)}
|
||||
onClick={handleRowClick}>
|
||||
{server.status === "connected" && (
|
||||
<span
|
||||
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
|
||||
style={{ marginRight: "8px" }}
|
||||
/>
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"} mr-[8px]`} />
|
||||
)}
|
||||
<span style={{ flex: 1 }}>
|
||||
<span className="flex-1">
|
||||
{server.name}
|
||||
{server.source && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: "8px",
|
||||
padding: "1px 6px",
|
||||
fontSize: "11px",
|
||||
borderRadius: "4px",
|
||||
background: "var(--vscode-badge-background)",
|
||||
color: "var(--vscode-badge-foreground)",
|
||||
}}>
|
||||
<span className="ml-[8px] px-[6px] py-[1px] text-[11px] rounded-[4px] bg-vscode-badge-background text-vscode-badge-foreground">
|
||||
{server.source}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", marginRight: "8px" }}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
style={{ marginRight: "8px" }}>
|
||||
<span className="codicon codicon-trash" style={{ fontSize: "14px" }}></span>
|
||||
<div className="flex items-center mr-[8px]" onClick={(e) => e.stopPropagation()}>
|
||||
<Button variant="ghost" size="icon" onClick={() => setShowDeleteConfirm(true)} className="mr-[8px]">
|
||||
<span className="codicon codicon-trash text-[14px]"></span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleRestart}
|
||||
disabled={server.status === "connecting"}
|
||||
style={{ marginRight: "8px" }}>
|
||||
<span className="codicon codicon-refresh" style={{ fontSize: "14px" }}></span>
|
||||
className="mr-[8px]">
|
||||
<span className="codicon codicon-refresh text-[14px]"></span>
|
||||
</Button>
|
||||
<div
|
||||
role="switch"
|
||||
aria-checked={!server.disabled}
|
||||
tabIndex={0}
|
||||
style={{
|
||||
width: "20px",
|
||||
height: "10px",
|
||||
backgroundColor: server.disabled
|
||||
? "var(--vscode-titleBar-inactiveForeground)"
|
||||
: "var(--vscode-button-background)",
|
||||
borderRadius: "5px",
|
||||
position: "relative",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 0.2s",
|
||||
opacity: server.disabled ? 0.4 : 0.8,
|
||||
}}
|
||||
className={cn(
|
||||
"w-[20px] h-[10px] rounded-[5px] relative cursor-pointer transition-colors duration-200",
|
||||
server.disabled
|
||||
? "bg-vscode-titleBar-inactiveForeground opacity-40"
|
||||
: "bg-vscode-button-background opacity-80",
|
||||
)}
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "toggleMcpServer",
|
||||
|
|
@ -291,40 +242,26 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
}
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: "6px",
|
||||
height: "6px",
|
||||
backgroundColor: "var(--vscode-titleBar-activeForeground)",
|
||||
borderRadius: "50%",
|
||||
position: "absolute",
|
||||
top: "2px",
|
||||
left: server.disabled ? "2px" : "12px",
|
||||
transition: "left 0.2s",
|
||||
}}
|
||||
className={cn(
|
||||
"w-[6px] h-[6px] bg-vscode-titleBar-activeForeground rounded-full absolute top-[2px] transition-all duration-200",
|
||||
server.disabled ? "left-[2px]" : "left-[12px]",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
borderRadius: "50%",
|
||||
background: getStatusColor(),
|
||||
marginLeft: "8px",
|
||||
}}
|
||||
className={cn("w-[8px] h-[8px] rounded-full ml-[8px]", {
|
||||
"bg-vscode-testing-iconPassed": server.status === "connected",
|
||||
"bg-vscode-charts-yellow": server.status === "connecting",
|
||||
"bg-vscode-testing-iconFailed": server.status === "disconnected",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{server.status === "connected" ? (
|
||||
isExpanded && (
|
||||
<div
|
||||
style={{
|
||||
background: "var(--vscode-textCodeBlock-background)",
|
||||
padding: "0 10px 10px 10px",
|
||||
fontSize: "13px",
|
||||
borderRadius: "0 0 4px 4px",
|
||||
}}>
|
||||
<VSCodePanels style={{ marginBottom: "10px" }}>
|
||||
<div className="bg-vscode-textCodeBlock-background px-[10px] pb-[10px] text-[13px] rounded-b-[4px]">
|
||||
<VSCodePanels className="mb-[10px]">
|
||||
<VSCodePanelTab id="tools">
|
||||
{t("mcp:tabs.tools")} ({server.tools?.length || 0})
|
||||
</VSCodePanelTab>
|
||||
|
|
@ -338,8 +275,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
|
||||
<VSCodePanelView id="tools-view">
|
||||
{server.tools && server.tools.length > 0 ? (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
|
||||
<div className="flex flex-col gap-[8px] w-full">
|
||||
{server.tools.map((tool) => (
|
||||
<McpToolRow
|
||||
key={`${tool.name}-${server.name}-${server.source || "global"}`}
|
||||
|
|
@ -351,7 +287,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>
|
||||
<div className="py-[10px] text-vscode-descriptionForeground">
|
||||
{t("mcp:emptyState.noTools")}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -360,8 +296,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
<VSCodePanelView id="resources-view">
|
||||
{(server.resources && server.resources.length > 0) ||
|
||||
(server.resourceTemplates && server.resourceTemplates.length > 0) ? (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
|
||||
<div className="flex flex-col gap-[8px] w-full">
|
||||
{[...(server.resourceTemplates || []), ...(server.resources || [])].map(
|
||||
(item) => (
|
||||
<McpResourceRow
|
||||
|
|
@ -372,7 +307,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>
|
||||
<div className="py-[10px] text-vscode-descriptionForeground">
|
||||
{t("mcp:emptyState.noResources")}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -380,8 +315,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
|
||||
<VSCodePanelView id="errors-view">
|
||||
{server.errorHistory && server.errorHistory.length > 0 ? (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: "8px", width: "100%" }}>
|
||||
<div className="flex flex-col gap-[8px] w-full">
|
||||
{[...server.errorHistory]
|
||||
.sort((a, b) => b.timestamp - a.timestamp)
|
||||
.map((error, index) => (
|
||||
|
|
@ -389,7 +323,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: "10px 0", color: "var(--vscode-descriptionForeground)" }}>
|
||||
<div className="py-[10px] text-vscode-descriptionForeground">
|
||||
{t("mcp:emptyState.noErrors")}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -397,28 +331,13 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
</VSCodePanels>
|
||||
|
||||
{/* Network Timeout */}
|
||||
<div style={{ padding: "10px 7px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<div className="px-[7px] py-[10px]">
|
||||
<div className="flex items-center gap-[10px] mb-[8px]">
|
||||
<span>{t("mcp:networkTimeout.label")}</span>
|
||||
<select
|
||||
value={timeoutValue}
|
||||
onChange={handleTimeoutChange}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "4px",
|
||||
background: "var(--vscode-dropdown-background)",
|
||||
color: "var(--vscode-dropdown-foreground)",
|
||||
border: "1px solid var(--vscode-dropdown-border)",
|
||||
borderRadius: "2px",
|
||||
outline: "none",
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
className="flex-1 p-[4px] bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded-[2px] outline-none cursor-pointer">
|
||||
{timeoutOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
|
|
@ -426,33 +345,15 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
))}
|
||||
</select>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
display: "block",
|
||||
}}>
|
||||
<span className="text-xs text-vscode-descriptionForeground block">
|
||||
{t("mcp:networkTimeout.description")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
background: "var(--vscode-textCodeBlock-background)",
|
||||
borderRadius: "0 0 4px 4px",
|
||||
width: "100%",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-testing-iconFailed)",
|
||||
marginBottom: "8px",
|
||||
padding: "0 10px",
|
||||
overflowWrap: "break-word",
|
||||
wordBreak: "break-word",
|
||||
}}>
|
||||
<div className="text-[13px] bg-vscode-textCodeBlock-background rounded-b-[4px] w-full">
|
||||
<div className="text-vscode-testing-iconFailed mb-[8px] px-[10px] overflow-wrap-break-word break-words">
|
||||
{server.error &&
|
||||
server.error.split("\n").map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
|
|
@ -465,7 +366,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
|
|||
appearance="secondary"
|
||||
onClick={handleRestart}
|
||||
disabled={server.status === "connecting"}
|
||||
style={{ width: "calc(100% - 20px)", margin: "0 10px 10px 10px" }}>
|
||||
className="w-[calc(100%-20px)] mx-[10px] mb-[10px]">
|
||||
{server.status === "connecting" ? "Retrying..." : "Retry Connection"}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -434,14 +434,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
return (
|
||||
<Tab>
|
||||
<TabHeader className="flex justify-between items-center">
|
||||
<h3 className="text-vscode-foreground m-0">{t("prompts:title")}</h3>
|
||||
<h3 className="text-foreground m-0">{t("prompts:title")}</h3>
|
||||
<Button onClick={onDone}>{t("prompts:done")}</Button>
|
||||
</TabHeader>
|
||||
|
||||
<TabContent>
|
||||
<div>
|
||||
<div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
|
||||
<h3 className="text-vscode-foreground m-0">{t("prompts:modes.title")}</h3>
|
||||
<h3 className="text-foreground m-0">{t("prompts:modes.title")}</h3>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -471,9 +471,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
className="absolute top-full right-0 w-[200px] mt-1 bg-vscode-editor-background border border-vscode-input-border rounded shadow-md z-[1000]">
|
||||
className="absolute top-full right-0 w-[200px] mt-1 bg-background border border-border rounded shadow-md z-[1000]">
|
||||
<div
|
||||
className="p-2 cursor-pointer text-vscode-foreground text-sm"
|
||||
className="p-2 cursor-pointer text-foreground text-sm"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault() // Prevent blur
|
||||
vscode.postMessage({
|
||||
|
|
@ -485,7 +485,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
{t("prompts:modes.editGlobalModes")}
|
||||
</div>
|
||||
<div
|
||||
className="p-2 cursor-pointer text-vscode-foreground text-sm border-t border-vscode-input-border"
|
||||
className="p-2 cursor-pointer text-foreground text-sm border-t border-border"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault() // Prevent blur
|
||||
vscode.postMessage({
|
||||
|
|
@ -507,9 +507,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-3">
|
||||
{t("prompts:modes.createModeHelpText")}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground mb-3">{t("prompts:modes.createModeHelpText")}</div>
|
||||
|
||||
<div className="flex items-center gap-1 mb-3">
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
|
|
@ -641,7 +639,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
{t("prompts:roleDefinition.description")}
|
||||
</div>
|
||||
<Textarea
|
||||
|
|
@ -702,7 +700,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-xs mt-1.5 text-vscode-descriptionForeground">
|
||||
<div className="text-xs mt-1.5 text-muted-foreground">
|
||||
{t("prompts:apiConfiguration.select")}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -728,7 +726,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
)}
|
||||
</div>
|
||||
{!findModeBySlug(visualMode, customModes) && (
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
{t("prompts:tools.builtInModesText")}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -750,7 +748,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
disabled={!isCustomMode}>
|
||||
{t(`prompts:tools.toolNames.${group}`)}
|
||||
{group === "edit" && (
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-0.5">
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
{t("prompts:tools.allowedFiles")}{" "}
|
||||
{(() => {
|
||||
const currentMode = getCurrentMode()
|
||||
|
|
@ -773,7 +771,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-vscode-foreground mb-2 leading-relaxed">
|
||||
<div className="text-sm text-foreground mb-2 leading-relaxed">
|
||||
{(() => {
|
||||
const currentMode = getCurrentMode()
|
||||
const enabledGroups = currentMode?.groups || []
|
||||
|
|
@ -821,7 +819,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-[13px] text-muted-foreground mb-2">
|
||||
{t("prompts:customInstructions.description", {
|
||||
modeName: getCurrentMode()?.name || "Code",
|
||||
})}
|
||||
|
|
@ -861,7 +859,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
className="w-full resize-y"
|
||||
data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`}
|
||||
/>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5">
|
||||
<div className="text-xs text-muted-foreground mt-1.5">
|
||||
<Trans
|
||||
i18nKey="prompts:customInstructions.loadFromFile"
|
||||
values={{
|
||||
|
|
@ -894,7 +892,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pb-4 border-b border-vscode-input-border">
|
||||
<div className="pb-4 border-b border-border">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="default"
|
||||
|
|
@ -932,7 +930,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => setIsSystemPromptDisclosureOpen(!isSystemPromptDisclosureOpen)}
|
||||
className="flex items-center text-xs text-vscode-foreground hover:text-vscode-textLink-foreground focus:outline-none"
|
||||
className="flex items-center text-xs text-foreground hover:text-vscode-textLink-foreground focus:outline-none"
|
||||
aria-expanded={isSystemPromptDisclosureOpen}>
|
||||
<span
|
||||
className={`codicon codicon-${isSystemPromptDisclosureOpen ? "chevron-down" : "chevron-right"} mr-1`}></span>
|
||||
|
|
@ -940,7 +938,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</button>
|
||||
|
||||
{isSystemPromptDisclosureOpen && (
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-2 ml-5">
|
||||
<div className="text-xs text-muted-foreground mt-2 ml-5">
|
||||
<Trans
|
||||
i18nKey="prompts:advancedSystemPrompt.description"
|
||||
values={{
|
||||
|
|
@ -972,10 +970,10 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pb-5 border-b border-vscode-input-border">
|
||||
<h3 className="text-vscode-foreground mb-3">{t("prompts:globalCustomInstructions.title")}</h3>
|
||||
<div className="pb-5 border-b border-border">
|
||||
<h3 className="text-foreground mb-3">{t("prompts:globalCustomInstructions.title")}</h3>
|
||||
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
{t("prompts:globalCustomInstructions.description", {
|
||||
language: i18next.language,
|
||||
})}
|
||||
|
|
@ -996,7 +994,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
className="w-full resize-y"
|
||||
data-testid="global-custom-instructions-textarea"
|
||||
/>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5">
|
||||
<div className="text-xs text-muted-foreground mt-1.5">
|
||||
<Trans
|
||||
i18nKey="prompts:globalCustomInstructions.loadFromFile"
|
||||
components={{
|
||||
|
|
@ -1020,8 +1018,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 pb-15 border-b border-vscode-input-border">
|
||||
<h3 className="text-vscode-foreground mb-3">{t("prompts:supportPrompts.title")}</h3>
|
||||
<div className="mt-5 pb-[3.75rem] border-b border-border">
|
||||
<h3 className="text-foreground mb-3">{t("prompts:supportPrompts.title")}</h3>
|
||||
<div className="flex gap-4 items-center flex-wrap py-1">
|
||||
<Select
|
||||
value={activeSupportOption}
|
||||
|
|
@ -1040,7 +1038,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
|
||||
{/* Support prompt description */}
|
||||
<div className="text-[13px] text-vscode-descriptionForeground my-2 mb-4">
|
||||
<div className="text-[13px] text-muted-foreground my-2 mb-4">
|
||||
{t(`prompts:supportPrompts.types.${activeSupportOption}.description`)}
|
||||
</div>
|
||||
|
||||
|
|
@ -1074,13 +1072,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
{activeSupportOption === "ENHANCE" && (
|
||||
<>
|
||||
<div>
|
||||
<div className="text-vscode-foreground text-[13px] mb-5 mt-1.5"></div>
|
||||
<div className="text-foreground text-[13px] mb-5 mt-1.5"></div>
|
||||
<div className="mb-3">
|
||||
<div className="mb-2">
|
||||
<div className="font-bold mb-1">
|
||||
{t("prompts:supportPrompts.enhance.apiConfiguration")}
|
||||
</div>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground">
|
||||
<div className="text-[13px] text-muted-foreground">
|
||||
{t("prompts:supportPrompts.enhance.apiConfigDescription")}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1143,7 +1141,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
|
||||
{isCreateModeDialogOpen && (
|
||||
<div className="fixed inset-0 flex justify-end bg-black/50 z-[1000]">
|
||||
<div className="w-[calc(100vw-100px)] h-full bg-vscode-editor-background shadow-md flex flex-col relative">
|
||||
<div className="w-[calc(100vw-100px)] h-full bg-background shadow-md flex flex-col relative">
|
||||
<div className="flex-1 p-5 overflow-y-auto min-h-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -1163,9 +1161,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
{nameError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">{nameError}</div>
|
||||
)}
|
||||
{nameError && <div className="text-xs text-destructive mt-1">{nameError}</div>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.slug.label")}</div>
|
||||
|
|
@ -1177,16 +1173,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
}}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1">
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{t("prompts:createModeDialog.slug.description")}
|
||||
</div>
|
||||
{slugError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">{slugError}</div>
|
||||
)}
|
||||
{slugError && <div className="text-xs text-destructive mt-1">{slugError}</div>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.saveLocation.label")}</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
{t("prompts:createModeDialog.saveLocation.description")}
|
||||
</div>
|
||||
<VSCodeRadioGroup
|
||||
|
|
@ -1198,29 +1192,24 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
}}>
|
||||
<VSCodeRadio value="global">
|
||||
{t("prompts:createModeDialog.saveLocation.global.label")}
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-0.5">
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
{t("prompts:createModeDialog.saveLocation.global.description")}
|
||||
</div>
|
||||
</VSCodeRadio>
|
||||
<VSCodeRadio value="project">
|
||||
{t("prompts:createModeDialog.saveLocation.project.label")}
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-0.5">
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
{t("prompts:createModeDialog.saveLocation.project.description")}
|
||||
</div>
|
||||
</VSCodeRadio>
|
||||
</VSCodeRadioGroup>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">
|
||||
{t("prompts:createModeDialog.roleDefinition.label")}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<div className="text-[13px] text-muted-foreground mb-2">
|
||||
{t("prompts:createModeDialog.roleDefinition.description")}
|
||||
</div>
|
||||
<Textarea
|
||||
|
|
@ -1232,14 +1221,12 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
className="w-full resize-y"
|
||||
/>
|
||||
{roleDefinitionError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">
|
||||
{roleDefinitionError}
|
||||
</div>
|
||||
<div className="text-xs text-destructive mt-1">{roleDefinitionError}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.tools.label")}</div>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-[13px] text-muted-foreground mb-2">
|
||||
{t("prompts:createModeDialog.tools.description")}
|
||||
</div>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2">
|
||||
|
|
@ -1263,15 +1250,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</VSCodeCheckbox>
|
||||
))}
|
||||
</div>
|
||||
{groupsError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">{groupsError}</div>
|
||||
)}
|
||||
{groupsError && <div className="text-xs text-destructive mt-1">{groupsError}</div>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">
|
||||
{t("prompts:createModeDialog.customInstructions.label")}
|
||||
</div>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
|
||||
<div className="text-[13px] text-muted-foreground mb-2">
|
||||
{t("prompts:createModeDialog.customInstructions.description")}
|
||||
</div>
|
||||
<Textarea
|
||||
|
|
@ -1284,7 +1269,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end p-3 px-5 gap-2 border-t border-vscode-editor-lineHighlightBorder bg-vscode-editor-background">
|
||||
<div className="flex justify-end p-3 px-5 gap-2 border-t border-vscode-editor-lineHighlightBorder bg-background">
|
||||
<Button variant="secondary" onClick={() => setIsCreateModeDialogOpen(false)}>
|
||||
{t("prompts:createModeDialog.buttons.cancel")}
|
||||
</Button>
|
||||
|
|
@ -1298,7 +1283,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
|
||||
{isDialogOpen && (
|
||||
<div className="fixed inset-0 flex justify-end bg-black/50 z-[1000]">
|
||||
<div className="w-[calc(100vw-100px)] h-full bg-vscode-editor-background shadow-md flex flex-col relative">
|
||||
<div className="w-[calc(100vw-100px)] h-full bg-background shadow-md flex flex-col relative">
|
||||
<div className="flex-1 p-5 overflow-y-auto min-h-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -1313,11 +1298,11 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
modeName: getCurrentMode()?.name || "Code",
|
||||
})}
|
||||
</h2>
|
||||
<pre className="p-2 whitespace-pre-wrap break-words font-mono text-vscode-editor-font-size text-vscode-editor-foreground bg-vscode-editor-background border border-vscode-editor-lineHighlightBorder rounded overflow-y-auto">
|
||||
<pre className="p-2 whitespace-pre-wrap break-words font-mono text-base text-foreground bg-background border border-vscode-editor-lineHighlightBorder rounded overflow-y-auto">
|
||||
{selectedPromptContent}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="flex justify-end p-3 px-5 border-t border-vscode-editor-lineHighlightBorder bg-vscode-editor-background">
|
||||
<div className="flex justify-end p-3 px-5 border-t border-vscode-editor-lineHighlightBorder bg-background">
|
||||
<Button variant="secondary" onClick={() => setIsDialogOpen(false)}>
|
||||
{t("prompts:createModeDialog.close")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ const ApiConfigManager = ({
|
|||
}}
|
||||
placeholder={t("settings:providers.enterProfileName")}
|
||||
data-testid="new-profile-input"
|
||||
style={{ width: "100%" }}
|
||||
className="w-full"
|
||||
onKeyDown={(e: unknown) => {
|
||||
const event = e as { key: string }
|
||||
if (event.key === "Enter" && newProfileName.trim()) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { VSCodeButton, VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-u
|
|||
import { SquareMousePointer } from "lucide-react"
|
||||
import { HTMLAttributes, useEffect, useMemo, useState } from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
|
|
@ -188,7 +189,7 @@ export const BrowserSettings = ({
|
|||
setCachedStateField("remoteBrowserHost", e.target.value || undefined)
|
||||
}
|
||||
placeholder={t("settings:browser.remote.urlPlaceholder")}
|
||||
style={{ flexGrow: 1 }}
|
||||
className="grow"
|
||||
/>
|
||||
<VSCodeButton disabled={testingConnection} onClick={testConnection}>
|
||||
{testingConnection || discovering
|
||||
|
|
@ -198,11 +199,12 @@ export const BrowserSettings = ({
|
|||
</div>
|
||||
{testResult && (
|
||||
<div
|
||||
className={`p-2 rounded-xs text-sm ${
|
||||
className={cn(
|
||||
"p-2 rounded-xs text-sm",
|
||||
testResult.success
|
||||
? "bg-green-800/20 text-green-400"
|
||||
: "bg-red-800/20 text-red-400"
|
||||
}`}>
|
||||
: "bg-red-800/20 text-red-400",
|
||||
)}>
|
||||
{testResult.text}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,32 @@
|
|||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useEffect, useRef, useState } from "react"
|
||||
import React, { memo, useEffect, useRef, useState, FC, PropsWithChildren } from "react" // Added React, FC, PropsWithChildren
|
||||
import { useRemark } from "react-remark"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Collapsible, CollapsibleTrigger } from "@/components/ui"
|
||||
|
||||
import { StyledMarkdown } from "./styles"
|
||||
// Removed import { StyledMarkdown } from "./styles"
|
||||
|
||||
// Moved StyledMarkdown component definition here
|
||||
interface StyledMarkdownProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
const StyledMarkdown: FC<PropsWithChildren<StyledMarkdownProps>> = ({ className, children, ...props }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"font-vscode-font-family text-xs text-vscode-descriptionForeground",
|
||||
"[&_p]:leading-tight [&_p]:m-0 [&_p]:whitespace-pre-wrap",
|
||||
"[&_li]:leading-tight [&_li]:m-0",
|
||||
"[&_ol]:leading-tight [&_ol]:m-0 [&_ol]:pl-[1.5em] [&_ol]:ml-0",
|
||||
"[&_ul]:leading-tight [&_ul]:m-0 [&_ul]:pl-[1.5em] [&_ul]:ml-0",
|
||||
"[&_a]:no-underline hover:[&_a]:underline",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ModelDescriptionMarkdown = memo(
|
||||
({
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={isSettingValid ? "default" : "secondary"}
|
||||
className={!isSettingValid ? "!border-vscode-errorForeground" : ""}
|
||||
className={cn(!isSettingValid && "!border-vscode-errorForeground")}
|
||||
title={
|
||||
!isSettingValid
|
||||
? errorMessage
|
||||
|
|
|
|||
|
|
@ -109,9 +109,8 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
|
|||
<div className="flex items-center gap-1">
|
||||
<span>{t("settings:providers.enablePromptCaching")}</span>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.enablePromptCachingTitle")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
</Checkbox>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const Glama = ({ apiConfiguration, setApiConfigurationField, routerModels
|
|||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.glamaApiKey && (
|
||||
<VSCodeButtonLink href={getGlamaAuthUrl(uriScheme)} style={{ width: "100%" }} appearance="primary">
|
||||
<VSCodeButtonLink href={getGlamaAuthUrl(uriScheme)} className="w-full" appearance="primary">
|
||||
{t("settings:providers.getGlamaApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -119,13 +119,7 @@ export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudi
|
|||
))}
|
||||
</VSCodeRadioGroup>
|
||||
{lmStudioModels.length === 0 && (
|
||||
<div
|
||||
className="text-sm rounded-xs p-2"
|
||||
style={{
|
||||
backgroundColor: "var(--vscode-inputValidation-infoBackground)",
|
||||
border: "1px solid var(--vscode-inputValidation-infoBorder)",
|
||||
color: "var(--vscode-inputValidation-infoForeground)",
|
||||
}}>
|
||||
<div className="text-sm rounded-xs p-2 bg-vscode-inputValidation-infoBackground border border-vscode-inputValidation-infoBorder text-vscode-inputValidation-infoForeground">
|
||||
{t("settings:providers.lmStudio.noModelsFound")}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { ModelInfo, ReasoningEffort as ReasoningEffortType } from "@roo/schemas"
|
|||
import { ApiConfiguration, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults } from "@roo/shared/api"
|
||||
import { ExtensionMessage } from "@roo/shared/ExtensionMessage"
|
||||
|
||||
import { cn } from "@/lib/utils" // Added cn import
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
|
|
@ -251,17 +252,16 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
""
|
||||
}
|
||||
type="text"
|
||||
style={{
|
||||
borderColor: (() => {
|
||||
className={cn(
|
||||
"w-full",
|
||||
"border", // Added base border class
|
||||
(() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.maxTokens
|
||||
|
||||
if (!value) {
|
||||
return "var(--vscode-input-border)"
|
||||
}
|
||||
|
||||
return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)"
|
||||
// Use `value === undefined || value === null` for a more robust check if 0 is a valid input but should not trigger default border
|
||||
if (value === undefined || value === null) return "border-vscode-input-border"
|
||||
return value > 0 ? "border-vscode-charts-green" : "border-vscode-errorForeground"
|
||||
})(),
|
||||
}}
|
||||
)}
|
||||
title={t("settings:providers.customModel.maxTokens.description")}
|
||||
onInput={handleInputChange("openAiCustomModelInfo", (e) => {
|
||||
const value = parseInt((e.target as HTMLInputElement).value)
|
||||
|
|
@ -272,7 +272,8 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
}
|
||||
})}
|
||||
placeholder={t("settings:placeholders.numbers.maxTokens")}
|
||||
className="w-full">
|
||||
/* className="w-full" */
|
||||
>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.customModel.maxTokens.label")}
|
||||
</label>
|
||||
|
|
@ -290,17 +291,6 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
""
|
||||
}
|
||||
type="text"
|
||||
style={{
|
||||
borderColor: (() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow
|
||||
|
||||
if (!value) {
|
||||
return "var(--vscode-input-border)"
|
||||
}
|
||||
|
||||
return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)"
|
||||
})(),
|
||||
}}
|
||||
title={t("settings:providers.customModel.contextWindow.description")}
|
||||
onInput={handleInputChange("openAiCustomModelInfo", (e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
|
|
@ -312,7 +302,14 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
}
|
||||
})}
|
||||
placeholder={t("settings:placeholders.numbers.contextWindow")}
|
||||
className="w-full">
|
||||
className={cn(
|
||||
"w-full border", // Added base 'border' class
|
||||
(() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow
|
||||
if (value === undefined || value === null) return "border-vscode-input-border" // Default border if no value
|
||||
return value > 0 ? "border-vscode-charts-green" : "border-vscode-errorForeground"
|
||||
})(),
|
||||
)}>
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.customModel.contextWindow.label")}
|
||||
</label>
|
||||
|
|
@ -340,9 +337,8 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
</span>
|
||||
</Checkbox>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.imageSupport.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground pt-1">
|
||||
|
|
@ -363,9 +359,8 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
<span className="font-medium">{t("settings:providers.customModel.computerUse.label")}</span>
|
||||
</Checkbox>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.computerUse.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground pt-1">
|
||||
|
|
@ -386,9 +381,8 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
<span className="font-medium">{t("settings:providers.customModel.promptCache.label")}</span>
|
||||
</Checkbox>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.promptCache.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground pt-1">
|
||||
|
|
@ -404,17 +398,6 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
""
|
||||
}
|
||||
type="text"
|
||||
style={{
|
||||
borderColor: (() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice
|
||||
|
||||
if (!value && value !== 0) {
|
||||
return "var(--vscode-input-border)"
|
||||
}
|
||||
|
||||
return value >= 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)"
|
||||
})(),
|
||||
}}
|
||||
onChange={handleInputChange("openAiCustomModelInfo", (e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
const parsed = parseFloat(value)
|
||||
|
|
@ -425,15 +408,21 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
}
|
||||
})}
|
||||
placeholder={t("settings:placeholders.numbers.inputPrice")}
|
||||
className="w-full">
|
||||
className={cn(
|
||||
"w-full border", // Added base 'border' class
|
||||
(() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice
|
||||
if (value === undefined || value === null) return "border-vscode-input-border"
|
||||
return value >= 0 ? "border-vscode-charts-green" : "border-vscode-errorForeground"
|
||||
})(),
|
||||
)}>
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.customModel.pricing.input.label")}
|
||||
</label>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.pricing.input.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
</VSCodeTextField>
|
||||
|
|
@ -447,17 +436,6 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
""
|
||||
}
|
||||
type="text"
|
||||
style={{
|
||||
borderColor: (() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice
|
||||
|
||||
if (!value && value !== 0) {
|
||||
return "var(--vscode-input-border)"
|
||||
}
|
||||
|
||||
return value >= 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)"
|
||||
})(),
|
||||
}}
|
||||
onChange={handleInputChange("openAiCustomModelInfo", (e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
const parsed = parseFloat(value)
|
||||
|
|
@ -468,15 +446,21 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
}
|
||||
})}
|
||||
placeholder={t("settings:placeholders.numbers.outputPrice")}
|
||||
className="w-full">
|
||||
className={cn(
|
||||
"w-full border", // Added base 'border' class
|
||||
(() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice
|
||||
if (value === undefined || value === null) return "border-vscode-input-border"
|
||||
return value >= 0 ? "border-vscode-charts-green" : "border-vscode-errorForeground"
|
||||
})(),
|
||||
)}>
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.customModel.pricing.output.label")}
|
||||
</label>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.pricing.output.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
</VSCodeTextField>
|
||||
|
|
@ -488,19 +472,16 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiCustomModelInfo?.cacheReadsPrice?.toString() ?? "0"}
|
||||
type="text"
|
||||
style={{
|
||||
borderColor: (() => {
|
||||
className={cn(
|
||||
"w-full border", // Added base 'border' class
|
||||
(() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.cacheReadsPrice
|
||||
|
||||
if (!value && value !== 0) {
|
||||
return "var(--vscode-input-border)"
|
||||
}
|
||||
|
||||
if (value === undefined || value === null) return "border-vscode-input-border"
|
||||
return value >= 0
|
||||
? "var(--vscode-charts-green)"
|
||||
: "var(--vscode-errorForeground)"
|
||||
? "border-vscode-charts-green"
|
||||
: "border-vscode-errorForeground"
|
||||
})(),
|
||||
}}
|
||||
)}
|
||||
onChange={handleInputChange("openAiCustomModelInfo", (e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
const parsed = parseFloat(value)
|
||||
|
|
@ -510,16 +491,14 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
cacheReadsPrice: isNaN(parsed) ? 0 : parsed,
|
||||
}
|
||||
})}
|
||||
placeholder={t("settings:placeholders.numbers.inputPrice")}
|
||||
className="w-full">
|
||||
placeholder={t("settings:placeholders.numbers.inputPrice")}>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-medium">
|
||||
{t("settings:providers.customModel.pricing.cacheReads.label")}
|
||||
</span>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.pricing.cacheReads.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
</VSCodeTextField>
|
||||
|
|
@ -528,19 +507,6 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
<VSCodeTextField
|
||||
value={apiConfiguration?.openAiCustomModelInfo?.cacheWritesPrice?.toString() ?? "0"}
|
||||
type="text"
|
||||
style={{
|
||||
borderColor: (() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.cacheWritesPrice
|
||||
|
||||
if (!value && value !== 0) {
|
||||
return "var(--vscode-input-border)"
|
||||
}
|
||||
|
||||
return value >= 0
|
||||
? "var(--vscode-charts-green)"
|
||||
: "var(--vscode-errorForeground)"
|
||||
})(),
|
||||
}}
|
||||
onChange={handleInputChange("openAiCustomModelInfo", (e) => {
|
||||
const value = (e.target as HTMLInputElement).value
|
||||
const parsed = parseFloat(value)
|
||||
|
|
@ -551,15 +517,23 @@ export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }:
|
|||
}
|
||||
})}
|
||||
placeholder={t("settings:placeholders.numbers.cacheWritePrice")}
|
||||
className="w-full">
|
||||
className={cn(
|
||||
"w-full border",
|
||||
(() => {
|
||||
const value = apiConfiguration?.openAiCustomModelInfo?.cacheWritesPrice
|
||||
if (value === undefined || value === null) return "border-vscode-input-border"
|
||||
return value >= 0
|
||||
? "border-vscode-charts-green"
|
||||
: "border-vscode-errorForeground"
|
||||
})(),
|
||||
)}>
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.customModel.pricing.cacheWrites.label")}
|
||||
</label>
|
||||
<i
|
||||
className="codicon codicon-info text-vscode-descriptionForeground"
|
||||
className="codicon codicon-info text-vscode-descriptionForeground text-xs"
|
||||
title={t("settings:providers.customModel.pricing.cacheWrites.description")}
|
||||
style={{ fontSize: "12px" }}
|
||||
/>
|
||||
</div>
|
||||
</VSCodeTextField>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export const OpenRouter = ({
|
|||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.openRouterApiKey && (
|
||||
<VSCodeButtonLink href={getOpenRouterAuthUrl(uriScheme)} style={{ width: "100%" }} appearance="primary">
|
||||
<VSCodeButtonLink href={getOpenRouterAuthUrl(uriScheme)} className="w-full" appearance="primary">
|
||||
{t("settings:providers.getOpenRouterApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -59,10 +59,7 @@ export const Requesty = ({
|
|||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.requestyApiKey && (
|
||||
<VSCodeButtonLink
|
||||
href="https://app.requesty.ai/api-keys"
|
||||
style={{ width: "100%" }}
|
||||
appearance="primary">
|
||||
<VSCodeButtonLink href="https://app.requesty.ai/api-keys" className="w-full" appearance="primary">
|
||||
{t("settings:providers.getRequestyApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
import styled from "styled-components"
|
||||
|
||||
// Keep StyledMarkdown as it's used by ModelDescriptionMarkdown.tsx
|
||||
export const StyledMarkdown = styled.div`
|
||||
font-family:
|
||||
var(--vscode-font-family),
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
|
||||
p,
|
||||
li,
|
||||
ol,
|
||||
ul {
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
@ -40,7 +40,7 @@ function DialogContent({ className, children, ...props }: React.ComponentProps<t
|
|||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
"bg-vscode-editor-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
|
|
@ -78,7 +78,7 @@ function DialogTitle({ className, ...props }: React.ComponentProps<typeof Dialog
|
|||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
className={cn("text-lg leading-none font-semibold my-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
|
@ -88,7 +88,7 @@ function DialogDescription({ className, ...props }: React.ComponentProps<typeof
|
|||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
className={cn("text-muted-foreground text-sm my-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export const Blockquote = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className="border-l-3 border-accent italic pl-2 py-2 mb-2">{children}</div>
|
||||
return <div className="border-l-[3px] border-accent italic pl-2 py-2 mb-2">{children}</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,11 +60,7 @@ export const CodeBlock: FC<CodeBlockProps> = memo(({ language, value, className,
|
|||
size="icon"
|
||||
className="absolute top-1 right-1 cursor-pointer bg-black/10"
|
||||
onClick={onCopy}>
|
||||
{isCopied ? (
|
||||
<CheckIcon style={{ width: 12, height: 12 }} />
|
||||
) : (
|
||||
<CopyIcon style={{ width: 12, height: 12 }} />
|
||||
)}
|
||||
{isCopied ? <CheckIcon className="w-3 h-3" /> : <CopyIcon className="w-3 h-3" />}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,51 @@
|
|||
--color-vscode-inputValidation-infoForeground: var(--vscode-inputValidation-infoForeground);
|
||||
--color-vscode-inputValidation-infoBackground: var(--vscode-inputValidation-infoBackground);
|
||||
--color-vscode-inputValidation-infoBorder: var(--vscode-inputValidation-infoBorder);
|
||||
--color-vscode-descriptionForeground-transparent-30: color-mix(
|
||||
in srgb,
|
||||
var(--vscode-descriptionForeground) 30%,
|
||||
transparent
|
||||
);
|
||||
--color-vscode-testing-iconPassed: var(--vscode-testing-iconPassed);
|
||||
--color-vscode-testing-iconFailed: var(--vscode-testing-iconFailed);
|
||||
--color-vscode-titleBar-inactiveForeground: var(--vscode-titleBar-inactiveForeground);
|
||||
--color-vscode-titleBar-activeForeground: var(--vscode-titleBar-activeForeground);
|
||||
--color-vscode-editor-lineHighlightBorder: var(--vscode-editor-lineHighlightBorder);
|
||||
--color-vscode-titleBar-inactiveForeground-20: color-mix(
|
||||
in srgb,
|
||||
var(--vscode-titleBar-inactiveForeground) 20%,
|
||||
transparent
|
||||
);
|
||||
|
||||
/* Custom color for CodeBlock background with fallbacks */
|
||||
--color-vscode-code-block-background: var(
|
||||
--vscode-editor-background,
|
||||
var(--vscode-sideBar-background, rgb(30 30 30))
|
||||
);
|
||||
|
||||
/* Custom colors for Slider border */
|
||||
--color-vscode-slider-border-light: #767676;
|
||||
--color-vscode-slider-border-dark: #858585;
|
||||
|
||||
/* Fallback colors for Highlight.js (Light theme) */
|
||||
--color-vscode-fallback-light-comment: #008000;
|
||||
--color-vscode-fallback-light-doctag: #0000ff;
|
||||
--color-vscode-fallback-light-keyword: #0000ff;
|
||||
--color-vscode-fallback-light-title-class: #001080;
|
||||
--color-vscode-fallback-light-title-function: #795e26;
|
||||
--color-vscode-fallback-light-number: #098658;
|
||||
--color-vscode-fallback-light-regexp: #a31515;
|
||||
--color-vscode-fallback-light-string: #a31515;
|
||||
|
||||
/* Fallback colors for Highlight.js (Dark theme) */
|
||||
--color-vscode-fallback-dark-comment: #6a9955;
|
||||
--color-vscode-fallback-dark-doctag: #569cd6;
|
||||
--color-vscode-fallback-dark-keyword: #569cd6;
|
||||
--color-vscode-fallback-dark-title-class: #9cdcfe;
|
||||
--color-vscode-fallback-dark-title-function: #dcdcaa;
|
||||
--color-vscode-fallback-dark-number: #b5cea8;
|
||||
--color-vscode-fallback-dark-regexp: #ce9178;
|
||||
--color-vscode-fallback-dark-string: #ce9178;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
|
@ -184,6 +229,11 @@
|
|||
.history-item-highlight {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
/* Shiki code block inner code transparency */
|
||||
.shiki > code {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Element Focus States */
|
||||
|
|
@ -413,3 +463,16 @@ input[cmdk-input]:focus {
|
|||
.codicon[class*="codicon-"] {
|
||||
text-rendering: geometricPrecision !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue