@@ -25,19 +25,32 @@ export interface CredentialTagData {
2525 provider ?: string
2626}
2727
28+ export interface MothershipErrorTagData {
29+ message : string
30+ code : string
31+ provider : string
32+ }
33+
2834export type ContentSegment =
2935 | { type : 'text' ; content : string }
3036 | { type : 'thinking' ; content : string }
3137 | { type : 'options' ; data : OptionsTagData }
3238 | { type : 'usage_upgrade' ; data : UsageUpgradeTagData }
3339 | { type : 'credential' ; data : CredentialTagData }
40+ | { type : 'mothership-error' ; data : MothershipErrorTagData }
3441
3542export interface ParsedSpecialContent {
3643 segments : ContentSegment [ ]
3744 hasPendingTag : boolean
3845}
3946
40- const SPECIAL_TAG_NAMES = [ 'thinking' , 'options' , 'usage_upgrade' , 'credential' ] as const
47+ const SPECIAL_TAG_NAMES = [
48+ 'thinking' ,
49+ 'options' ,
50+ 'usage_upgrade' ,
51+ 'credential' ,
52+ 'mothership-error' ,
53+ ] as const
4154
4255/**
4356 * Parses inline special tags (`<options>`, `<usage_upgrade>`) from streamed
@@ -69,7 +82,7 @@ export function parseSpecialTags(content: string, isStreaming: boolean): ParsedS
6982 let remaining = content . slice ( cursor )
7083
7184 if ( isStreaming ) {
72- const partial = remaining . match ( / < [ a - z _ ] * $ / i)
85+ const partial = remaining . match ( / < [ a - z _ - ] * $ / i)
7386 if ( partial ) {
7487 const fragment = partial [ 0 ] . slice ( 1 )
7588 if ( fragment . length > 0 && SPECIAL_TAG_NAMES . some ( ( t ) => t . startsWith ( fragment ) ) ) {
@@ -111,7 +124,10 @@ export function parseSpecialTags(content: string, isStreaming: boolean): ParsedS
111124 } else {
112125 try {
113126 const data = JSON . parse ( body )
114- segments . push ( { type : nearestTagName as 'options' | 'usage_upgrade' | 'credential' , data } )
127+ segments . push ( {
128+ type : nearestTagName as 'options' | 'usage_upgrade' | 'credential' | 'mothership-error' ,
129+ data,
130+ } )
115131 } catch {
116132 /* malformed JSON — drop the tag silently */
117133 }
@@ -152,6 +168,8 @@ export function SpecialTags({ segment, onOptionSelect }: SpecialTagsProps) {
152168 return < UsageUpgradeDisplay data = { segment . data } />
153169 case 'credential' :
154170 return < CredentialDisplay data = { segment . data } />
171+ case 'mothership-error' :
172+ return < MothershipErrorDisplay data = { segment . data } />
155173 default :
156174 return null
157175 }
@@ -276,6 +294,37 @@ function CredentialDisplay({ data }: { data: CredentialTagData }) {
276294 )
277295}
278296
297+ function MothershipErrorDisplay ( { data } : { data : MothershipErrorTagData } ) {
298+ return (
299+ < div className = 'animate-stream-fade-in rounded-xl border border-red-300/40 bg-red-50/50 px-4 py-3 dark:border-red-500/20 dark:bg-red-950/20' >
300+ < div className = 'flex items-center gap-2' >
301+ < svg
302+ className = 'h-4 w-4 shrink-0 text-red-600 dark:text-red-400'
303+ viewBox = '0 0 16 16'
304+ fill = 'none'
305+ xmlns = 'http://www.w3.org/2000/svg'
306+ >
307+ < circle cx = '8' cy = '8' r = '6.5' stroke = 'currentColor' strokeWidth = '1.3' />
308+ < path d = 'M8 4.5v4' stroke = 'currentColor' strokeWidth = '1.3' strokeLinecap = 'round' />
309+ < circle cx = '8' cy = '11' r = '0.75' fill = 'currentColor' />
310+ </ svg >
311+ < span className = 'font-[500] text-[14px] text-red-800 leading-5 dark:text-red-300' >
312+ Something went wrong
313+ </ span >
314+ </ div >
315+ < p className = 'mt-1.5 text-[13px] text-red-700/90 leading-[20px] dark:text-red-400/80' >
316+ { data . message }
317+ </ p >
318+ { data . code && (
319+ < span className = 'mt-1 inline-block text-[11px] text-red-500/70 dark:text-red-500/50' >
320+ { data . provider ? `${ data . provider } :` : '' }
321+ { data . code }
322+ </ span >
323+ ) }
324+ </ div >
325+ )
326+ }
327+
279328function UsageUpgradeDisplay ( { data } : { data : UsageUpgradeTagData } ) {
280329 const { workspaceId } = useParams < { workspaceId : string } > ( )
281330 const settingsPath = `/workspace/${ workspaceId } /settings/subscription`
0 commit comments