This commit is contained in:
Fredrik Jensen 2025-12-07 15:14:58 +01:00
parent 212bf1c705
commit 9d19cae491
6 changed files with 172 additions and 55 deletions

View File

@ -1,7 +1,9 @@
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
height: 100vh;
max-height: 100vh;
overflow: hidden;
}
/* Header */
@ -104,6 +106,7 @@
.main-layout {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
position: relative;
}
@ -116,12 +119,14 @@
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.content-panels {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
@ -134,8 +139,19 @@
/* Responsive - Mobile */
@media (max-width: 768px) {
.app {
height: auto;
max-height: none;
min-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
max-width: 100vw;
}
.header {
padding: 10px 16px;
flex-shrink: 0;
}
.mobile-menu-toggle {
@ -165,7 +181,22 @@
z-index: 99;
}
.main-layout {
flex: none;
width: 100%;
max-width: 100%;
}
.content {
flex: none;
width: 100%;
max-width: 100%;
}
.content-panels {
flex: none;
flex-direction: column;
width: 100%;
max-width: 100%;
}
}

View File

@ -3,6 +3,7 @@
display: flex;
flex-direction: column;
min-width: 300px;
min-height: 0;
background: var(--bg-primary);
}
@ -133,6 +134,7 @@
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
@ -224,6 +226,7 @@
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
@ -250,6 +253,7 @@
.events-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 12px;
display: flex;
@ -258,6 +262,7 @@
}
.event-item {
flex-shrink: 0;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
@ -322,8 +327,25 @@
/* Mobile styles */
@media (max-width: 768px) {
.results-pane {
flex: none;
min-width: 0;
min-height: 300px;
min-height: 0;
width: 100%;
max-width: 100%;
}
.results-content {
flex: none;
width: 100%;
max-width: 100%;
}
.ui-frame {
flex: none;
height: 500px;
min-height: 500px;
width: 100%;
max-width: 100%;
}
.results-pane .panel-header {
@ -339,6 +361,7 @@
.tab {
padding: 10px 12px;
font-size: 12px;
min-height: auto;
}
.results-actions {
@ -353,6 +376,8 @@
.results-loading,
.results-empty,
.results-error {
flex: none;
min-height: 200px;
padding: 24px 16px;
}
@ -362,6 +387,7 @@
}
.text-output {
flex: none;
padding: 12px;
}
@ -369,9 +395,17 @@
font-size: 12px;
}
.events-panel {
flex: none;
width: 100%;
max-width: 100%;
}
.events-list {
flex: none;
padding: 8px;
gap: 6px;
overflow: visible;
}
.event-header {

View File

@ -100,6 +100,14 @@ export function ResultsPane({ result, isExecuting, onReload }: ResultsPaneProps)
Events
{events.length > 0 && <span className="tab-count">{events.length}</span>}
</button>
{/* {activeTab === 'events' && events.length > 0 && (
<Button
variant="ghost"
onClick={clearEvents}
title="Clear events"
children="Clear events"
/>
)} */}
</div>
<div className="results-actions">
@ -117,14 +125,6 @@ export function ResultsPane({ result, isExecuting, onReload }: ResultsPaneProps)
)}
</div>
)}
{activeTab === 'events' && events.length > 0 && (
<Button
variant="ghost"
onClick={clearEvents}
title="Clear events"
icon={<Trash2 size={14} />}
/>
)}
{onReload && (
<Button
variant="ghost"

View File

@ -289,17 +289,35 @@
/* Mobile styles */
@media (max-width: 768px) {
.tools-panel {
flex: none;
min-width: 0;
max-width: none;
max-width: 100%;
width: 100%;
}
.refresh-button {
min-height: 28px;
width: 36px;
height: 36px;
}
.tools-content {
flex: none;
width: 100%;
max-width: 100%;
}
.tools-layout {
flex: none;
flex-direction: column;
width: 100%;
max-width: 100%;
}
.tools-list {
width: 100%;
min-width: 0;
max-width: 100%;
max-height: 200px;
border-right: none;
border-bottom: 1px solid var(--border-color);
@ -308,10 +326,13 @@
.tool-item {
padding: 12px 16px;
min-height: auto;
}
.tool-detail {
min-height: 300px;
flex: none;
width: 100%;
max-width: 100%;
}
.tool-detail-header {
@ -319,7 +340,9 @@
}
.tool-params {
flex: none;
padding: 12px 16px;
overflow: visible;
}
.tool-actions {

View File

@ -29,47 +29,34 @@ export function ToolsPanel({
const [jsonMode, setJsonMode] = useState(false)
const [jsonInput, setJsonInput] = useState('{}')
// Reset params when tool changes
useEffect(() => {
if (selectedTool) {
const defaultParams: Record<string, string> = {}
const props = selectedTool.inputSchema?.properties || {}
// Build initial params for a tool (string values for form state)
const buildInitialParams = (tool: Tool): Record<string, string> => {
const initialParams: Record<string, string> = {}
const props = tool.inputSchema?.properties || {}
for (const [key, value] of Object.entries(props)) {
if (value.default !== undefined) {
defaultParams[key] = typeof value.default === 'string'
initialParams[key] = typeof value.default === 'string'
? value.default
: JSON.stringify(value.default)
} else {
defaultParams[key] = ''
initialParams[key] = ''
}
}
setParams(defaultParams)
setJsonInput(JSON.stringify(defaultParams, null, 2))
return initialParams
}
}, [selectedTool])
const handleExecute = () => {
if (!selectedTool) return
// Convert string params to typed params for execution
const buildExecuteParams = (tool: Tool, stringParams: Record<string, string>): Record<string, unknown> => {
const finalParams: Record<string, unknown> = {}
const props = tool.inputSchema?.properties || {}
let finalParams: Record<string, unknown> = {}
if (jsonMode) {
try {
finalParams = JSON.parse(jsonInput)
} catch {
alert('Invalid JSON')
return
}
} else {
const props = selectedTool.inputSchema?.properties || {}
for (const [key, value] of Object.entries(params)) {
for (const [key, value] of Object.entries(stringParams)) {
const propType = props[key]?.type
// Skip empty optional fields (but always include if there's a value or it's required)
const isRequired = selectedTool.inputSchema?.required?.includes(key)
const isRequired = tool.inputSchema?.required?.includes(key)
if (!value && !isRequired && !props[key]?.default) continue
// Try to parse as JSON for arrays/objects
@ -91,6 +78,41 @@ export function ToolsPanel({
finalParams[key] = value
}
}
return finalParams
}
// Reset params when tool changes
useEffect(() => {
if (selectedTool) {
const initialParams = buildInitialParams(selectedTool)
setParams(initialParams)
setJsonInput(JSON.stringify(initialParams, null, 2))
}
}, [selectedTool])
// Handle tool click - select and execute with defaults
const handleToolClick = (tool: Tool) => {
onSelectTool(tool)
const initialParams = buildInitialParams(tool)
const executeParams = buildExecuteParams(tool, initialParams)
onExecuteTool(tool.name, executeParams)
}
const handleExecute = () => {
if (!selectedTool) return
let finalParams: Record<string, unknown> = {}
if (jsonMode) {
try {
finalParams = JSON.parse(jsonInput)
} catch {
alert('Invalid JSON')
return
}
} else {
finalParams = buildExecuteParams(selectedTool, params)
}
onExecuteTool(selectedTool.name, finalParams)
@ -162,7 +184,7 @@ export function ToolsPanel({
<button
key={tool.name}
className={`tool-item ${selectedTool?.name === tool.name ? 'selected' : ''}`}
onClick={() => onSelectTool(tool)}
onClick={() => handleToolClick(tool)}
>
{selectedTool?.name === tool.name ? <CircleDot size={14} className="tool-chevron" /> : <Circle size={14} className="tool-chevron" />}
<div className="tool-info">

View File

@ -88,6 +88,12 @@ code,
/* Touch-friendly improvements */
@media (max-width: 768px) {
html,
body {
overflow-x: hidden;
max-width: 100vw;
}
button,
input,
select,
@ -99,6 +105,7 @@ code,
select,
textarea {
font-size: 16px; /* Prevents zoom on iOS */
max-width: 100%;
}
}