1- import { useState , useEffect } from 'react'
1+ import { useState , useEffect , useRef } from 'react'
22import { Loader2 , Plus , Trash2 , Edit , Star , StarOff , Download , RotateCcw } from 'lucide-react'
33import { Button } from '@/components/ui/button'
44import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card'
@@ -16,6 +16,35 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
1616import { parseJsonc , hasJsoncComments } from '@/lib/jsonc'
1717import type { OpenCodeConfig } from '@/api/types/settings'
1818
19+ interface Command {
20+ template : string
21+ description ?: string
22+ agent ?: string
23+ model ?: string
24+ subtask ?: boolean
25+ topP ?: number
26+ }
27+
28+ interface Agent {
29+ prompt ?: string
30+ description ?: string
31+ mode ?: 'subagent' | 'primary' | 'all'
32+ temperature ?: number
33+ topP ?: number
34+ model ?: {
35+ modelID : string
36+ providerID : string
37+ }
38+ tools ?: Record < string , boolean >
39+ permission ?: {
40+ edit ?: 'ask' | 'allow' | 'deny'
41+ bash ?: 'ask' | 'allow' | 'deny' | Record < string , 'ask' | 'allow' | 'deny' >
42+ webfetch ?: 'ask' | 'allow' | 'deny'
43+ }
44+ disable ?: boolean
45+ [ key : string ] : unknown
46+ }
47+
1948export function OpenCodeConfigManager ( ) {
2049 const queryClient = useQueryClient ( )
2150 const [ configs , setConfigs ] = useState < OpenCodeConfig [ ] > ( [ ] )
@@ -32,6 +61,31 @@ export function OpenCodeConfigManager() {
3261 const [ isEditDialogOpen , setIsEditDialogOpen ] = useState ( false )
3362 const [ deleteConfirmConfig , setDeleteConfirmConfig ] = useState < OpenCodeConfig | null > ( null )
3463
64+ const commandsRef = useRef < HTMLButtonElement > ( null )
65+ const agentsRef = useRef < HTMLButtonElement > ( null )
66+ const mcpRef = useRef < HTMLButtonElement > ( null )
67+
68+ const scrollToSection = ( ref : React . RefObject < HTMLButtonElement | null > ) => {
69+ if ( ref . current ) {
70+ const rect = ref . current . getBoundingClientRect ( )
71+
72+ console . log ( 'Scroll check:' , {
73+ section : ref . current . textContent ?. trim ( ) ,
74+ rectTop : rect . top ,
75+ rectBottom : rect . bottom ,
76+ viewportHeight : window . innerHeight
77+ } )
78+
79+ // Always scroll to ensure the section is at the top when expanded
80+ console . log ( 'Scrolling to section:' , ref . current . textContent ?. trim ( ) )
81+ ref . current . scrollIntoView ( {
82+ behavior : 'smooth' ,
83+ block : 'start' ,
84+ inline : 'nearest'
85+ } )
86+ }
87+ }
88+
3589 const restartServerMutation = useMutation ( {
3690 mutationFn : async ( ) => {
3791 return await settingsApi . restartOpenCodeServer ( )
@@ -343,21 +397,31 @@ export function OpenCodeConfigManager() {
343397 < >
344398 < div className = "bg-card border border-border rounded-lg overflow-hidden min-w-0" >
345399 < button
400+ ref = { commandsRef }
346401 className = "w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
347- onClick = { ( ) => setExpandedSections ( prev => ( { ...prev , commands : ! prev . commands } ) ) }
402+ onClick = { ( ) => {
403+ const isExpanding = ! expandedSections . commands
404+ console . log ( 'Commands clicked, expanding:' , isExpanding )
405+ setExpandedSections ( prev => ( { ...prev , commands : isExpanding } ) )
406+
407+ // Only scroll when expanding, not collapsing
408+ if ( isExpanding ) {
409+ setTimeout ( ( ) => scrollToSection ( commandsRef ) , 100 )
410+ }
411+ } }
348412 >
349413 < div className = "flex items-center gap-3 min-w-0" >
350414 < h4 className = "text-sm font-medium truncate" > Commands</ h4 >
351415 < span className = "text-xs text-muted-foreground" >
352- { Object . keys ( selectedConfig . content . command as Record < string , any > || { } ) . length } configured
416+ { Object . keys ( selectedConfig . content . command as Record < string , Command > || { } ) . length } configured
353417 </ span >
354418 </ div >
355419 < Edit className = { `h-4 w-4 transition-transform ${ expandedSections . commands ? 'rotate-90' : '' } ` } />
356420 </ button >
357421 < div className = { `${ expandedSections . commands ? 'block' : 'hidden' } border-t border-border` } >
358422 < div className = "p-1 sm:p-4 max-h-[50vh] overflow-y-auto" >
359423 < CommandsEditor
360- commands = { ( selectedConfig . content . command as Record < string , any > ) || { } }
424+ commands = { ( selectedConfig . content . command as Record < string , Command > ) || { } }
361425 onChange = { ( commands ) => {
362426 const updatedContent = {
363427 ...selectedConfig . content ,
@@ -372,21 +436,31 @@ export function OpenCodeConfigManager() {
372436
373437 < div className = "bg-card border border-border rounded-lg overflow-hidden min-w-0" >
374438 < button
439+ ref = { agentsRef }
375440 className = "w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
376- onClick = { ( ) => setExpandedSections ( prev => ( { ...prev , agents : ! prev . agents } ) ) }
441+ onClick = { ( ) => {
442+ const isExpanding = ! expandedSections . agents
443+ console . log ( 'Agents clicked, expanding:' , isExpanding )
444+ setExpandedSections ( prev => ( { ...prev , agents : isExpanding } ) )
445+
446+ // Only scroll when expanding, not collapsing
447+ if ( isExpanding ) {
448+ setTimeout ( ( ) => scrollToSection ( agentsRef ) , 100 )
449+ }
450+ } }
377451 >
378452 < div className = "flex items-center gap-3 min-w-0" >
379453 < h4 className = "text-sm font-medium truncate" > Agents</ h4 >
380454 < span className = "text-xs text-muted-foreground" >
381- { Object . keys ( selectedConfig . content . agent as Record < string , any > || { } ) . length } configured
455+ { Object . keys ( selectedConfig . content . agent as Record < string , Agent > || { } ) . length } configured
382456 </ span >
383457 </ div >
384458 < Edit className = { `h-4 w-4 transition-transform ${ expandedSections . agents ? 'rotate-90' : '' } ` } />
385459 </ button >
386460 < div className = { `${ expandedSections . agents ? 'block' : 'hidden' } border-t border-border` } >
387461 < div className = "p-4 max-h-[50vh] overflow-y-auto" >
388462 < AgentsEditor
389- agents = { ( selectedConfig . content . agent as Record < string , any > ) || { } }
463+ agents = { ( selectedConfig . content . agent as Record < string , Agent > ) || { } }
390464 onChange = { ( agents ) => {
391465 const updatedContent = {
392466 ...selectedConfig . content ,
@@ -401,13 +475,23 @@ export function OpenCodeConfigManager() {
401475
402476 < div className = "bg-card border border-border rounded-lg overflow-hidden min-w-0" >
403477 < button
478+ ref = { mcpRef }
404479 className = "w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
405- onClick = { ( ) => setExpandedSections ( prev => ( { ...prev , mcp : ! prev . mcp } ) ) }
480+ onClick = { ( ) => {
481+ const isExpanding = ! expandedSections . mcp
482+ console . log ( 'MCP clicked, expanding:' , isExpanding )
483+ setExpandedSections ( prev => ( { ...prev , mcp : isExpanding } ) )
484+
485+ // Only scroll when expanding, not collapsing
486+ if ( isExpanding ) {
487+ setTimeout ( ( ) => scrollToSection ( mcpRef ) , 100 )
488+ }
489+ } }
406490 >
407491 < div className = "flex items-center gap-3 min-w-0" >
408492 < h4 className = "text-sm font-medium truncate" > MCP Servers</ h4 >
409493 < span className = "text-xs text-muted-foreground" >
410- { Object . keys ( ( selectedConfig . content . mcp as Record < string , any > ) || { } ) . length } configured
494+ { Object . keys ( ( selectedConfig . content . mcp as Record < string , unknown > ) || { } ) . length } configured
411495 </ span >
412496 </ div >
413497 < Edit className = { `h-4 w-4 transition-transform ${ expandedSections . mcp ? 'rotate-90' : '' } ` } />
0 commit comments