Skip to content

Commit 7deaeb9

Browse files
committed
feat(chat): enhance user info display and file path highlighting
1 parent fbde98e commit 7deaeb9

File tree

14 files changed

+350
-127
lines changed

14 files changed

+350
-127
lines changed

webview-ui/src/components/chat/BatchFilePermission.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ export const BatchFilePermission = memo(({ files = [], onPermissionResponse, ts
2727
return (
2828
<div className="pt-[5px]">
2929
{/* Individual files */}
30-
<div className="flex flex-col gap-0 border border-border rounded-md p-1">
30+
<div className="flex flex-col gap-0 border border-border rounded-md p-1 pb-0">
3131
{files.map((file) => {
3232
return (
33-
<div key={`${file.path}-${ts}`} className="flex items-center gap-2">
33+
<div key={`${file.path}-${ts}`} className="flex items-center gap-2 mb-[4px]">
3434
<ToolUseBlock className="flex-1">
3535
<ToolUseBlockHeader
3636
onClick={() => vscode.postMessage({ type: "openFile", text: file.content })}>

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
} from "lucide-react"
6868
import { cn } from "@/lib/utils"
6969
import { getLine } from "@/utils/path-mentions"
70+
import { useZgsmUserInfo } from "@/hooks/useZgsmUserInfo"
7071

7172
interface ChatRowProps {
7273
message: ClineMessage
@@ -148,8 +149,8 @@ export const ChatRowContent = ({
148149
searchQuery,
149150
}: ChatRowContentProps) => {
150151
const { t } = useTranslation()
151-
152152
const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState()
153+
const { logoPic, userInfo } = useZgsmUserInfo(apiConfiguration)
153154
const { info: model } = useSelectedModel(apiConfiguration)
154155
const [showCopySuccess, setShowCopySuccess] = useState(false)
155156
const [isEditing, setIsEditing] = useState(false)
@@ -1200,15 +1201,29 @@ export const ChatRowContent = ({
12001201
return (
12011202
<div className="group">
12021203
<div style={headerStyle}>
1203-
<User className="w-4" aria-label="User icon" />
1204-
<span style={{ fontWeight: "bold" }}>{t("chat:feedback.youSaid")}</span>
1204+
{logoPic ? (
1205+
<img
1206+
src={logoPic}
1207+
title={userInfo?.name || t("chat:feedback.defaultUserName")}
1208+
alt={userInfo?.name || t("chat:feedback.defaultUserName")}
1209+
className="w-6 h-6 rounded-full object-cover"
1210+
/>
1211+
) : (
1212+
<User className="w-4" aria-label="User icon" />
1213+
)}
1214+
<span style={{ fontWeight: "bold" }}>
1215+
{t("chat:feedback.youSaid", {
1216+
username: userInfo?.name || t("chat:feedback.defaultUserName"),
1217+
})}
1218+
</span>
12051219
</div>
12061220
<div
12071221
className={cn(
1208-
"ml-6 border rounded-sm overflow-hidden whitespace-pre-wrap",
1222+
"ml-6 border rounded-sm whitespace-pre-wrap",
1223+
isEditing ? "" : "overflow-hidden",
12091224
isEditing
12101225
? "bg-vscode-editor-background text-vscode-editor-foreground"
1211-
: "cursor-text p-1 bg-vscode-editor-foreground/70 text-vscode-editor-background",
1226+
: "cursor-text p-1 bg-vscode-editor-foreground/50 text-vscode-editor-background",
12121227
)}>
12131228
{isEditing ? (
12141229
<div className="flex flex-col gap-2">

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1316,7 +1316,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
13161316
<div
13171317
className={cn(
13181318
"flex items-center gap-2 min-w-0 overflow-clip flex-1",
1319-
isEditMode ? "ml-[20px] py-[14px]" : "",
1319+
isEditMode ? "ml-[42px] py-[14px]" : "",
13201320
)}>
13211321
<ModeSelector
13221322
value={mode}

webview-ui/src/components/chat/Mention.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,28 @@ export const Mention = ({ text, withShadow = false }: MentionProps) => {
1111
if (!text) {
1212
return <>{text}</>
1313
}
14-
14+
// Highlight file path with line numbers format: filePath:startLine-endLine
1515
const parts = text.split(mentionRegexGlobal).map((part, index) => {
1616
if (index % 2 === 0) {
1717
// This is regular text.
18-
return part
18+
const textSegments = part.split(/\b([\w/\\.-]+:\d+-\d+)\b/)
19+
return textSegments.map((segment, segmentIndex) => {
20+
if (segmentIndex % 2 === 1) {
21+
// This is a file path match
22+
return (
23+
<mark
24+
key={`${index}-${segmentIndex}`}
25+
className={`mention-context-highlight-with-shadow cursor-pointer`}
26+
style={{
27+
pointerEvents: "auto",
28+
color: "var(--vscode-textPreformat-foreground) !important",
29+
}}>
30+
{segment}
31+
</mark>
32+
)
33+
}
34+
return segment
35+
})
1936
} else {
2037
// This is a mention.
2138
return (

webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const CheckpointSaved = ({ checkpoint, ...props }: CheckpointSavedProps)
3636

3737
return (
3838
<div className="group flex items-center justify-between gap-2 pt-2 pb-3 ">
39-
<div className="flex items-center gap-2 text-blue-400">
39+
<div className="flex items-center gap-2 text-blue-400 whitespace-nowrap">
4040
<GitCommitVertical className="w-4" />
4141
<span className="font-semibold">{t("chat:checkpoint.regular")}</span>
4242
{isCurrent && <span className="text-muted">({t("chat:checkpoint.current")})</span>}

webview-ui/src/components/cloud/ZgsmAccountView.tsx

Lines changed: 4 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { useCallback, useEffect, useRef, useState } from "react"
1+
import { useCallback } from "react"
22
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
33

4-
import type { ProviderSettings, ZgsmUserInfo } from "@roo-code/types"
4+
import type { ProviderSettings } from "@roo-code/types"
55
import { TelemetryEventName } from "@roo-code/types"
66

77
import { useAppTranslation } from "@src/i18n/TranslationContext"
88
import { vscode } from "@src/utils/vscode"
99
import { telemetryClient } from "@src/utils/TelemetryClient"
10-
import axios from "axios"
1110
import { useEvent } from "react-use"
1211
import { ExtensionMessage } from "@roo/ExtensionMessage"
12+
import { useZgsmUserInfo } from "@src/hooks/useZgsmUserInfo"
1313

1414
type AccountViewProps = {
1515
apiConfiguration?: ProviderSettings
@@ -18,52 +18,10 @@ type AccountViewProps = {
1818

1919
export const ZgsmAccountView = ({ apiConfiguration, onDone }: AccountViewProps) => {
2020
const { t } = useAppTranslation()
21-
const [userInfo, setUserInfo] = useState<ZgsmUserInfo | null>(null)
22-
const [hash, setHash] = useState("")
23-
const [logoPic, setLogoPic] = useState("")
24-
25-
const wasAuthenticatedRef = useRef(false)
21+
const { userInfo, logoPic, hash } = useZgsmUserInfo(apiConfiguration)
2622

2723
const rooLogoUri = (window as any).COSTRICT_BASE_URI + "/logo.svg"
2824

29-
// Track authentication state changes to detect successful logout
30-
useEffect(() => {
31-
const token = apiConfiguration?.zgsmAccessToken
32-
33-
if (token) {
34-
wasAuthenticatedRef.current = true
35-
36-
const jwt = parseJwt(token)
37-
38-
const basicInfo: ZgsmUserInfo = {
39-
id: jwt.id,
40-
name: jwt?.properties?.oauth_GitHub_username || jwt.id,
41-
picture: undefined,
42-
email: jwt.email,
43-
phone: jwt.phone,
44-
organizationName: jwt.organizationName,
45-
organizationImageUrl: jwt.organizationImageUrl,
46-
}
47-
setUserInfo(basicInfo)
48-
49-
if (jwt.avatar) {
50-
imageUrlToBase64(jwt.avatar).then((base64) => {
51-
if (!base64) return
52-
// Step 3: Update userInfo.picture, only update the picture field
53-
setLogoPic(base64)
54-
})
55-
}
56-
57-
hashToken(token).then((result) => {
58-
console.log("New Credit hash: ", result)
59-
setHash(result)
60-
})
61-
} else if (wasAuthenticatedRef.current && !token) {
62-
telemetryClient.capture(TelemetryEventName.ACCOUNT_LOGOUT_SUCCESS)
63-
wasAuthenticatedRef.current = false
64-
}
65-
}, [apiConfiguration?.zgsmAccessToken])
66-
6725
const handleConnectClick = () => {
6826
// Send telemetry for account connect action
6927
telemetryClient.capture(TelemetryEventName.ACCOUNT_CONNECT_CLICKED)
@@ -193,42 +151,3 @@ export const ZgsmAccountView = ({ apiConfiguration, onDone }: AccountViewProps)
193151
</div>
194152
)
195153
}
196-
197-
function parseJwt(token: string) {
198-
const parts = token.split(".")
199-
if (parts.length !== 3) {
200-
throw new Error("Invalid JWT")
201-
}
202-
const payload = parts[1]
203-
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/")) // base64url → base64 → decode
204-
return JSON.parse(decoded)
205-
}
206-
207-
async function hashToken(token: string) {
208-
const encoder = new TextEncoder()
209-
const data = encoder.encode(token)
210-
const hashBuffer = await crypto.subtle.digest("SHA-256", data)
211-
return Array.from(new Uint8Array(hashBuffer))
212-
.map((b) => b.toString(16).padStart(2, "0"))
213-
.join("")
214-
}
215-
216-
export async function imageUrlToBase64(url: string): Promise<string | null> {
217-
try {
218-
const response = await axios.get(url, {
219-
responseType: "blob", // Key! Ensure axios returns Blob
220-
})
221-
222-
const blob = response.data as Blob
223-
224-
return await new Promise<string>((resolve, reject) => {
225-
const reader = new FileReader()
226-
reader.onloadend = () => resolve(reader.result as string)
227-
reader.onerror = () => reject("Failed to convert blob to base64")
228-
reader.readAsDataURL(blob) // Automatically adds data:image/png;base64,...
229-
})
230-
} catch (error) {
231-
console.error("Failed to convert image to base64", error)
232-
return null
233-
}
234-
}

webview-ui/src/components/common/CodeAccordian.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ const CodeAccordian = ({
8989
)}
9090
{(!hasHeader || isExpanded) && (
9191
<div className="overflow-x-auto overflow-y-hidden max-w-full">
92-
<CodeBlock source={source} language={inferredLanguage} />
92+
<CodeBlock source={source} language={inferredLanguage} onDoubleClick={onToggleExpand} />
9393
</div>
9494
)}
9595
</ToolUseBlock>

webview-ui/src/components/common/CodeBlock.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ interface CodeBlockProps {
3939
collapsedHeight?: number
4040
initialWindowShade?: boolean
4141
onLanguageChange?: (language: string) => void
42+
onDoubleClick?: (e: React.MouseEvent) => void
4243
}
4344

4445
const CodeBlockButton = styled.button`
@@ -222,6 +223,7 @@ const CodeBlock = memo(
222223
initialWordWrap = false,
223224
initialWindowShade = true,
224225
collapsedHeight,
226+
onDoubleClick,
225227
onLanguageChange,
226228
}: CodeBlockProps) => {
227229
const [wordWrap, setWordWrap] = useState(initialWordWrap)
@@ -692,7 +694,7 @@ const CodeBlock = memo(
692694
}
693695

694696
return (
695-
<CodeBlockContainer ref={codeBlockRef}>
697+
<CodeBlockContainer ref={codeBlockRef} onDoubleClick={onDoubleClick}>
696698
<MemoizedStyledPre
697699
preRef={preRef}
698700
preStyle={preStyle}

0 commit comments

Comments
 (0)