Skip to content

Commit ad824a4

Browse files
committed
Merge remote-tracking branch 'origin/mobile-main' into dev
2 parents 0da9f20 + d47ed33 commit ad824a4

File tree

37 files changed

+1902
-131
lines changed

37 files changed

+1902
-131
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import log from "electron-log"
2+
3+
/**
4+
* Logger for updater module with scoped prefix
5+
* All logs are prefixed with [Updater] for easy identification
6+
*/
7+
export const updaterLogger = log.scope("updater")
8+
9+
/**
10+
* Logger specifically for GitHub provider operations
11+
*/
12+
export const githubProviderLogger = log.scope("updater:github")
13+
14+
/**
15+
* Helper to log object properties in a formatted way
16+
*/
17+
export function logObject(logger: typeof updaterLogger, prefix: string, obj: Record<string, any>) {
18+
logger.info(`${prefix}:`)
19+
for (const [key, value] of Object.entries(obj)) {
20+
logger.info(` ${key}: ${value}`)
21+
}
22+
}

apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatHeader.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ const ChatHeaderLayout = ({
7777

7878
const { isScrolledBeyondThreshold } = useAIRootState()
7979
const isScrolledBeyondThresholdValue = useAtomValue(isScrolledBeyondThreshold)
80-
8180
return (
8281
<div
8382
className={cn(

apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/ChatInterface.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ const ChatInterfaceContent = ({ centerInputOnEmpty }: ChatInterfaceProps) => {
304304

305305
interface ChatInterfaceProps {
306306
centerInputOnEmpty?: boolean
307+
visualOffsetY?: string | number
307308
}
308309
export const ChatInterface = (props: ChatInterfaceProps) => (
309310
<ErrorBoundary fallback={AIErrorFallback}>

apps/desktop/layer/renderer/src/modules/ai-chat/components/message/parse-incomplete-markdown.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,157 @@ const isWordChar = (char: string): boolean => {
3434
return letterNumberUnderscorePattern.test(char)
3535
}
3636

37+
// Detect custom inline reference tags
38+
const mentionTagStartPattern = /<\s*mention-(?:entry|feed)\b/gi
39+
const mentionTagCompletePattern = /^<\s*(mention-(?:entry|feed))/i
40+
41+
// Finds the end index of a mention tag (self-closing or paired) starting at `startIndex`.
42+
// Returns the index of the closing `>` when found outside of quotes; otherwise -1.
43+
const findMentionTagEnd = (text: string, startIndex: number): number => {
44+
// Don't process if inside a complete code block
45+
if (hasCompleteCodeBlock(text)) return -1
46+
47+
let inQuote: '"' | "'" | null = null
48+
let openingTagEnd = -1
49+
for (let i = startIndex; i < text.length; i++) {
50+
const char = text[i]
51+
if (inQuote) {
52+
if (char === inQuote && text[i - 1] !== "\\") {
53+
inQuote = null
54+
}
55+
continue
56+
}
57+
if (char === '"' || char === "'") {
58+
inQuote = char
59+
continue
60+
}
61+
if (char === "/" && text[i + 1] === ">") {
62+
return i + 1 // index of '>' in `/>`
63+
}
64+
if (char === ">") {
65+
openingTagEnd = i
66+
break
67+
}
68+
}
69+
70+
if (openingTagEnd === -1) {
71+
return -1
72+
}
73+
74+
const openingTag = text.substring(startIndex, openingTagEnd + 1)
75+
const tagNameMatch = openingTag.match(mentionTagCompletePattern)
76+
if (!tagNameMatch) {
77+
return -1
78+
}
79+
80+
// If the tag is already self-closing (allow whitespace before `/`)
81+
if (/\/\s*>$/.test(openingTag)) {
82+
return openingTagEnd
83+
}
84+
85+
const tagName = (tagNameMatch[1] ?? "").toLowerCase()
86+
if (!tagName) {
87+
return -1
88+
}
89+
const afterOpening = text.substring(openingTagEnd + 1)
90+
const closingTagPattern = new RegExp(`<\\s*/\\s*${tagName}\\s*>`, "i")
91+
const closingMatch = closingTagPattern.exec(afterOpening)
92+
93+
if (!closingMatch) {
94+
return -1
95+
}
96+
97+
return openingTagEnd + 1 + closingMatch.index + closingMatch[0].length - 1
98+
}
99+
100+
// Trims trailing, incomplete `<mention-entry ...>` or `<mention-feed ...>` tags to avoid
101+
// injecting broken raw HTML into markdown while streaming.
102+
const handleIncompleteMentionTags = (text: string): string => {
103+
// Don't process if inside a complete code block
104+
if (hasCompleteCodeBlock(text)) {
105+
return text
106+
}
107+
108+
let cutIndex: number | null = null
109+
let match: RegExpExecArray | null
110+
mentionTagStartPattern.lastIndex = 0
111+
while ((match = mentionTagStartPattern.exec(text))) {
112+
const start = match.index
113+
const end = findMentionTagEnd(text, start)
114+
if (end === -1) {
115+
cutIndex = start
116+
break
117+
} else {
118+
// continue scanning after this complete tag
119+
mentionTagStartPattern.lastIndex = end + 1
120+
}
121+
}
122+
123+
if (cutIndex !== null) {
124+
const nextNewlineIndex = text.indexOf("\n", cutIndex)
125+
if (nextNewlineIndex !== -1) {
126+
// Remove only the incomplete tag segment and preserve following lines
127+
return text.substring(0, cutIndex) + text.substring(nextNewlineIndex)
128+
}
129+
// No newline after the incomplete tag; drop the trailing incomplete segment
130+
return text.substring(0, cutIndex)
131+
}
132+
return text
133+
}
134+
135+
// Handles `<Use: ...>` wrappers that contain mention tags (self-closing or paired) by:
136+
// - Replacing the whole wrapper with only the inner `<mention-...>` when complete
137+
// - Trimming from `<Use:` if the inner mention tag is incomplete while streaming
138+
const handleUseWrapper = (text: string): string => {
139+
// Don't process if inside a complete code block
140+
if (hasCompleteCodeBlock(text)) return text
141+
142+
const usePattern = /<\s*Use:\s*/gi
143+
let result = text
144+
let match: RegExpExecArray | null
145+
usePattern.lastIndex = 0
146+
147+
// We rebuild iteratively in case of multiple occurrences
148+
while ((match = usePattern.exec(result))) {
149+
const useStart = match.index
150+
const mentionStart = result.indexOf("<mention-", useStart)
151+
if (mentionStart === -1) {
152+
// Incomplete `<Use:` without a mention yet → remove only the incomplete segment
153+
const nextNewlineIndex = result.indexOf("\n", useStart)
154+
return nextNewlineIndex !== -1
155+
? result.substring(0, useStart) + result.substring(nextNewlineIndex)
156+
: result.substring(0, useStart)
157+
}
158+
159+
// Ensure mention is the immediate content of the Use wrapper (allow whitespace)
160+
const between = result.substring(useStart + match[0].length, mentionStart)
161+
if (!/^\s*$/.test(between)) {
162+
// Unexpected content between Use and mention → treat as plain text, continue
163+
continue
164+
}
165+
166+
const mentionEnd = findMentionTagEnd(result, mentionStart)
167+
if (mentionEnd === -1) {
168+
// Mention not finished yet → remove only the incomplete wrapper segment
169+
const nextNewlineIndex = result.indexOf("\n", useStart)
170+
return nextNewlineIndex !== -1
171+
? result.substring(0, useStart) + result.substring(nextNewlineIndex)
172+
: result.substring(0, useStart)
173+
}
174+
175+
// Replace `<Use: <mention-...>` with `<mention-...>`
176+
const before = result.substring(0, useStart)
177+
const mentionTag = result.substring(mentionStart, mentionEnd + 1)
178+
const after = result.substring(mentionEnd + 1)
179+
result = before + mentionTag + after
180+
181+
// Reset the regex lastIndex to continue scanning after the replaced tag
182+
usePattern.lastIndex = before.length + mentionTag.length
183+
}
184+
185+
return result
186+
}
187+
37188
// Helper function to check if we have a complete code block
38189
const hasCompleteCodeBlock = (text: string): boolean => {
39190
const tripleBackticks = (text.match(/```/g) || []).length
@@ -611,6 +762,24 @@ const handleIncompleteStrikethrough = (text: string): string => {
611762
return text
612763
}
613764

765+
// Counts single dollar signs that are not part of double dollar signs and not escaped
766+
const _countSingleDollarSigns = (text: string): number => {
767+
return text.split("").reduce((acc, char, index) => {
768+
if (char === "$") {
769+
const prevChar = text[index - 1]
770+
const nextChar = text[index + 1]
771+
// Skip if escaped with backslash
772+
if (prevChar === "\\") {
773+
return acc
774+
}
775+
if (prevChar !== "$" && nextChar !== "$") {
776+
return acc + 1
777+
}
778+
}
779+
return acc
780+
}, 0)
781+
}
782+
614783
// Completes incomplete block KaTeX formatting ($$)
615784
const handleIncompleteBlockKatex = (text: string): string => {
616785
// Count all $$ pairs in the text
@@ -717,6 +886,10 @@ export const parseIncompleteMarkdown = (text: string): string => {
717886
// Handle various formatting completions
718887
// Handle triple asterisks first (most specific)
719888
result = handleIncompleteBoldItalic(result)
889+
// Normalize and guard the `<Use:` wrapper first so inner tags are handled correctly
890+
result = handleUseWrapper(result)
891+
// Handle custom mention tags trimming before other single-character completions
892+
result = handleIncompleteMentionTags(result)
720893
result = handleIncompleteBold(result)
721894
result = handleIncompleteDoubleUnderscoreItalic(result)
722895
result = handleIncompleteSingleAsteriskItalic(result)

0 commit comments

Comments
 (0)