Skip to content

Commit a403d05

Browse files
authored
v0.7.34: billing page fixes, chat rendering improvements
2 parents 377f963 + 7877713 commit a403d05

8 files changed

Lines changed: 135 additions & 5 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export function AgentGroup({
130130
toolName={item.data.toolName}
131131
displayTitle={item.data.displayTitle}
132132
status={item.data.status}
133+
params={item.data.params}
133134
streamingArgs={item.data.streamingArgs}
134135
/>
135136
)
@@ -261,7 +262,10 @@ function BoundedViewport({ children, isStreaming }: BoundedViewportProps) {
261262

262263
return (
263264
<div className='relative'>
264-
<div ref={ref} className={cn('max-h-[110px] overflow-y-auto pr-2', hasOverflow && 'py-1')}>
265+
<div
266+
ref={ref}
267+
className={cn('scrollbar-hide max-h-[110px] overflow-y-auto pr-2', hasOverflow && 'py-1')}
268+
>
265269
{children}
266270
</div>
267271
{hasOverflow && (

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { useMemo } from 'react'
22
import { ShimmerText } from '@/components/ui'
3-
import { WorkspaceFile } from '@/lib/copilot/generated/tool-catalog-v1'
3+
import { Read as ReadTool, WorkspaceFile } from '@/lib/copilot/generated/tool-catalog-v1'
4+
import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
45
import { getToolCompletedTitle } from '@/lib/copilot/tools/tool-display'
6+
import { getBareIconStyle } from '@/blocks/icon-color'
57
import type { ToolCallStatus } from '../../../../types'
68
import { resolveToolDisplayState } from '../../utils'
79

@@ -25,6 +27,7 @@ interface ToolCallItemProps {
2527
toolName: string
2628
displayTitle: string
2729
status: ToolCallStatus
30+
params?: Record<string, unknown>
2831
streamingArgs?: string
2932
}
3033

@@ -33,8 +36,22 @@ interface ToolCallItemProps {
3336
* static label once terminal. For `workspace_file` the title is derived live
3437
* from the streaming args; because that path bypasses the completed-title
3538
* rewrite in `toToolData`, the past-tense flip is applied here on success.
39+
* A `read` of a block or integration schema shows the block's brand icon
40+
* inline next to its display name (e.g. the Gmail logo before "Read Gmail").
3641
*/
37-
export function ToolCallItem({ toolName, displayTitle, status, streamingArgs }: ToolCallItemProps) {
42+
export function ToolCallItem({
43+
toolName,
44+
displayTitle,
45+
status,
46+
params,
47+
streamingArgs,
48+
}: ToolCallItemProps) {
49+
const readBlock = useMemo(() => {
50+
if (toolName !== ReadTool.id) return undefined
51+
const path = params?.path
52+
return typeof path === 'string' ? getReadTargetBlock(path) : undefined
53+
}, [toolName, params])
54+
3855
const liveWorkspaceFileTitle = useMemo(() => {
3956
if (toolName !== WorkspaceFile.id || !streamingArgs) return null
4057
const titleMatch = streamingArgs.match(/"title"\s*:\s*"([^"]+)"/)
@@ -71,8 +88,13 @@ export function ToolCallItem({ toolName, displayTitle, status, streamingArgs }:
7188
? (getToolCompletedTitle(liveTitle) ?? liveTitle)
7289
: liveTitle
7390

91+
const BlockIcon = readBlock?.icon
92+
7493
return (
75-
<div className='flex items-center pl-6'>
94+
<div className='flex items-center gap-[6px] pl-6'>
95+
{BlockIcon && (
96+
<BlockIcon className='size-[14px] flex-shrink-0' style={getBareIconStyle(BlockIcon)} />
97+
)}
7698
{isExecuting ? (
7799
<ShimmerText className='text-[13px] [--shimmer-rest:var(--text-secondary)]'>
78100
{title}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
SettingsHeaderProvider,
3+
SettingsHeaderShell,
4+
} from '@/app/workspace/[workspaceId]/settings/components/settings-header/settings-header'
5+
6+
/**
7+
* Credit usage is a static route outside `[section]`, so it does not inherit
8+
* `SettingsSectionLayout`'s chrome. `CreditUsageView` and its loading fallback
9+
* render through `SettingsPanel`, which only registers header config into the
10+
* `SettingsHeaderProvider` context — without this shell the page has no header
11+
* bar, title, or scroll region.
12+
*/
13+
export default function CreditUsageLayout({ children }: { children: React.ReactNode }) {
14+
return (
15+
<SettingsHeaderProvider>
16+
<SettingsHeaderShell>{children}</SettingsHeaderShell>
17+
</SettingsHeaderProvider>
18+
)
19+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it, vi } from 'vitest'
5+
import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
6+
7+
const gmailBlock = { type: 'gmail_v2', name: 'Gmail', icon: () => null }
8+
9+
vi.mock('@/blocks/registry', () => ({
10+
getBlock: vi.fn((type: string) => (type === 'gmail_v2' ? gmailBlock : undefined)),
11+
getLatestBlock: vi.fn((baseType: string) => (baseType === 'gmail' ? gmailBlock : undefined)),
12+
}))
13+
14+
describe('getReadTargetBlock', () => {
15+
it('resolves a block schema read to its block', () => {
16+
expect(getReadTargetBlock('components/blocks/gmail_v2.json')?.name).toBe('Gmail')
17+
})
18+
19+
it('resolves integration operation and service-directory reads to the latest service block', () => {
20+
expect(getReadTargetBlock('components/integrations/gmail/send.json')?.name).toBe('Gmail')
21+
expect(getReadTargetBlock('components/integrations/gmail')?.name).toBe('Gmail')
22+
})
23+
24+
it('returns undefined for unknown blocks and non-component paths', () => {
25+
expect(getReadTargetBlock('components/blocks/unknown_block.json')).toBeUndefined()
26+
expect(getReadTargetBlock('workflows/My Workflow/meta.json')).toBeUndefined()
27+
expect(getReadTargetBlock('files/gmail_v2.json')).toBeUndefined()
28+
expect(getReadTargetBlock(undefined)).toBeUndefined()
29+
})
30+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getBlock, getLatestBlock } from '@/blocks/registry'
2+
import type { BlockConfig } from '@/blocks/types'
3+
4+
/**
5+
* Resolves the block a copilot `read` call targets when the path is a
6+
* component schema — `components/blocks/{type}.json` or
7+
* `components/integrations/{service}/{operation}.json` — so tool rows can show
8+
* the block's display name and brand icon instead of the raw type id
9+
* (e.g. "Gmail" instead of `gmail_v2`). Returns undefined for every other
10+
* path, leaving the generic read-target labeling untouched.
11+
*/
12+
export function getReadTargetBlock(path: string | undefined): BlockConfig | undefined {
13+
if (!path) return undefined
14+
const segments = path.trim().split('/').filter(Boolean)
15+
if (segments[0] !== 'components' || segments.length < 3) return undefined
16+
if (segments[1] === 'blocks' && segments.length === 3) {
17+
return getBlock(segments[2].replace(/\.json$/, ''))
18+
}
19+
if (segments[1] === 'integrations') {
20+
return getLatestBlock(segments[2])
21+
}
22+
return undefined
23+
}

apps/sim/lib/copilot/tools/client/store-utils.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22
* @vitest-environment node
33
*/
44

5-
import { describe, expect, it } from 'vitest'
5+
import { describe, expect, it, vi } from 'vitest'
66
import { Read as ReadTool } from '@/lib/copilot/generated/tool-catalog-v1'
77
import { resolveToolDisplay } from './store-utils'
88
import { ClientToolCallState } from './tool-call-state'
99

10+
const gmailBlock = { type: 'gmail_v2', name: 'Gmail', icon: () => null }
11+
12+
vi.mock('@/blocks/registry', () => ({
13+
getBlock: vi.fn((type: string) => (type === 'gmail_v2' ? gmailBlock : undefined)),
14+
getLatestBlock: vi.fn((baseType: string) => (baseType === 'gmail' ? gmailBlock : undefined)),
15+
}))
16+
1017
describe('resolveToolDisplay', () => {
1118
it('uses a friendly label for internal respond tools', () => {
1219
expect(resolveToolDisplay('respond', ClientToolCallState.executing)?.text).toBe(
@@ -120,6 +127,26 @@ describe('resolveToolDisplay', () => {
120127
).toBe('Read style details for deck.pptx')
121128
})
122129

130+
it('shows the block display name for block and integration schema reads', () => {
131+
expect(
132+
resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
133+
path: 'components/blocks/gmail_v2.json',
134+
})?.text
135+
).toBe('Read Gmail')
136+
137+
expect(
138+
resolveToolDisplay(ReadTool.id, ClientToolCallState.executing, {
139+
path: 'components/integrations/gmail/send.json',
140+
})?.text
141+
).toBe('Reading Gmail')
142+
143+
expect(
144+
resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
145+
path: 'components/blocks/unknown_block.json',
146+
})?.text
147+
).toBe('Read unknown_block')
148+
})
149+
123150
it('falls back to a humanized tool label for generic tools', () => {
124151
expect(resolveToolDisplay('deploy_api', ClientToolCallState.success)?.text).toBe(
125152
'Executed Deploy Api'

apps/sim/lib/copilot/tools/client/store-utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FileText } from 'lucide-react'
44
import { Read as ReadTool } from '@/lib/copilot/generated/tool-catalog-v1'
55
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resources/types'
66
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
7+
import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
78
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
89
import { decodeVfsSegment } from '@/lib/copilot/vfs/path-utils'
910

@@ -98,6 +99,9 @@ function decodeVfsSegmentSafe(segment: string): string {
9899
function describeReadTarget(path: string | undefined): string | undefined {
99100
if (!path) return undefined
100101

102+
const block = getReadTargetBlock(path)
103+
if (block) return block.name
104+
101105
const segments = path
102106
.split('/')
103107
.map((segment) => segment.trim())

apps/sim/vitest.setup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ vi.mock('@/blocks/registry', () => ({
8888
outputs: {},
8989
})),
9090
getAllBlocks: vi.fn(() => ({})),
91+
getLatestBlock: vi.fn(() => undefined),
9192
}))
9293

9394
vi.mock('@trigger.dev/sdk', () => ({

0 commit comments

Comments
 (0)