Files
crank/apps/ui/html/wizard/step3-grpc.html
T
2026-03-31 02:11:55 +03:00

275 lines
18 KiB
HTML

<div id="step-panel-3-grpc" class="step-pane">
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 3 of 5
</div>
<h1 class="step-panel-title">RPC method</h1>
<p class="step-panel-subtitle">Choose how to describe the gRPC service contract. Upload a <code style="font-family:monospace;font-size:13px;color:var(--accent);">.proto</code> file, discover methods from a live server via reflection, or enter the route manually. Only unary RPCs are supported — streaming connections cannot be held open during MCP tool invocations.</p>
</div>
<!-- ── Source selector ── -->
<div class="grpc-source-tabs">
<button class="grpc-source-btn active" data-source="proto" onclick="selectGrpcSource('proto')">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6z"/><path d="M9 2v4h4"/></svg>
Proto file
</button>
<button class="grpc-source-btn" data-source="reflection" onclick="selectGrpcSource('reflection')">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.1 3.1l1.4 1.4M11.5 11.5l1.4 1.4M3.1 12.9l1.4-1.4M11.5 4.5l1.4-1.4"/></svg>
Descriptor set
</button>
<button class="grpc-source-btn" data-source="manual" onclick="selectGrpcSource('manual')">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M11 2l3 3-8 8H3v-3l8-8z"/></svg>
Manual
</button>
</div>
<!-- ══ Proto source ══ -->
<div id="grpc-src-proto">
<div class="config-card" style="margin-bottom: 20px;">
<div class="config-card-header">
<div class="config-card-header-icon">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6z"/><path d="M9 2v4h4"/></svg>
</div>
<div>
<div class="config-card-title">Proto definition</div>
<div class="config-card-subtitle">Upload or paste your .proto file</div>
</div>
<span class="config-card-optional" style="color:var(--error,#f87171);">Required</span>
</div>
<div class="config-card-body" style="gap: 12px; padding: 16px;">
<div class="proto-dropzone" id="proto-dropzone"
onclick="document.getElementById('proto-file-input').click()"
ondragover="event.preventDefault(); this.classList.add('drag-over')"
ondragleave="this.classList.remove('drag-over')"
ondrop="handleProtoDrop(event)">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
</svg>
<div class="proto-dropzone-title">Drop <code style="font-family:monospace;font-size:12px;">.proto</code> file here</div>
<div class="proto-dropzone-sub">or click to browse</div>
<input type="file" id="proto-file-input" accept=".proto" style="display:none" onchange="handleProtoFile(event)">
</div>
<div class="proto-file-info" id="proto-file-info" style="display:none">
<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--accent)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6z"/><path d="M9 2v4h4"/></svg>
<span class="proto-file-name" id="proto-file-name"></span>
<span class="proto-file-size" id="proto-file-size"></span>
<button class="proto-file-change" onclick="resetProto()">Change file</button>
</div>
<div style="display:flex; align-items:center; gap:10px;">
<div style="flex:1; height:1px; background:var(--border-subtle);"></div>
<button class="btn-ghost-sm" onclick="toggleProtoPaste()" id="proto-paste-btn">Paste content</button>
<div style="flex:1; height:1px; background:var(--border-subtle);"></div>
</div>
<div id="proto-paste-area" style="display:none">
<div class="code-block" style="border-radius: 6px;">
<div class="code-toolbar">
<div class="code-dots"><span></span><span></span><span></span></div>
<span class="code-toolbar-label">protobuf / definition</span>
</div>
<textarea class="form-textarea code-textarea" id="proto-paste-input" rows="14"
placeholder='syntax = "proto3";&#10;&#10;package acme;&#10;&#10;service ContactService {&#10; rpc GetContact (GetContactRequest) returns (Contact) {}&#10;}' spellcheck="false"></textarea>
</div>
<div style="display:flex; gap: 8px; margin-top: 8px;">
<button class="btn-primary-sm" onclick="parseProtoPasted()">Parse proto</button>
<button class="btn-ghost-sm" onclick="cancelProtoPaste()">Cancel</button>
</div>
</div>
</div>
</div>
<div id="proto-parsed-view" style="display:none">
<div class="info-callout" id="proto-streaming-notice" style="margin-bottom:20px;display:none">
<svg class="info-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><polygon points="8,1 15,14 1,14" fill="none"/><path d="M8 6v4M8 11.5v.5"/></svg>
<div class="info-callout-body">
<div class="info-callout-title">Streaming methods excluded</div>
<div class="info-callout-text"><span id="proto-streaming-count">0</span> streaming method(s) found and hidden — not compatible with MCP tool calls.</div>
</div>
</div>
<div class="config-card" style="margin-bottom: 20px;">
<div class="config-card-header">
<div><div class="config-card-title">Select method</div><div class="config-card-subtitle" id="proto-services-summary"></div></div>
<span class="config-card-optional" style="color:var(--error,#f87171);">Required</span>
</div>
<div class="config-card-body" style="padding: 0 16px 16px; gap: 0;">
<div id="proto-services-list"></div>
</div>
</div>
</div>
</div><!-- /grpc-src-proto -->
<!-- ══ Reflection source ══ -->
<div id="grpc-src-reflection" style="display:none">
<div class="config-card" style="margin-bottom: 20px;">
<div class="config-card-header">
<div class="config-card-header-icon">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3.1 3.1l1.4 1.4M11.5 11.5l1.4 1.4M3.1 12.9l1.4-1.4M11.5 4.5l1.4-1.4"/></svg>
</div>
<div>
<div class="config-card-title">Descriptor-driven discovery</div>
<div class="config-card-subtitle">Use the saved draft and a descriptor set to discover services through the backend</div>
</div>
</div>
<div class="config-card-body" style="gap: 14px; padding: 16px;">
<!-- Upstream info strip -->
<div class="grpc-reflect-upstream">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-muted)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="3" width="6" height="10" rx="1"/><rect x="9" y="3" width="6" height="10" rx="1"/></svg>
<div class="grpc-reflect-upstream-info">
<span class="grpc-reflect-upstream-name" id="grpc-reflect-upstream-name">No upstream selected</span>
<span class="grpc-reflect-upstream-url" id="grpc-reflect-upstream-url">Select an upstream in step 2 first</span>
</div>
</div>
<!-- Idle -->
<div id="grpc-reflect-idle">
<button class="btn-primary-sm" onclick="startReflection()">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg>
Open live descriptor tools
</button>
</div>
<!-- Loading -->
<div id="grpc-reflect-loading" style="display:none">
<div class="grpc-reflect-status">
<div class="grpc-reflect-spinner"></div>
<span id="grpc-reflect-status-text">Connecting…</span>
</div>
</div>
<!-- Error -->
<div id="grpc-reflect-error" style="display:none">
<div class="grpc-reflect-error-row">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="var(--error,#f87171)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="7"/><path d="M8 5v3M8 10.5v.5"/></svg>
<span id="grpc-reflect-error-text">Live descriptor discovery is handled in step 5.</span>
<button class="proto-file-change" onclick="startReflection()">Retry</button>
</div>
</div>
</div>
</div>
<div id="reflect-parsed-view" style="display:none">
<div class="info-callout" id="reflect-streaming-notice" style="margin-bottom:20px;display:none">
<svg class="info-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><polygon points="8,1 15,14 1,14" fill="none"/><path d="M8 6v4M8 11.5v.5"/></svg>
<div class="info-callout-body">
<div class="info-callout-title">Streaming methods excluded</div>
<div class="info-callout-text"><span id="reflect-streaming-count">0</span> streaming method(s) discovered and hidden.</div>
</div>
</div>
<div class="config-card" style="margin-bottom: 20px;">
<div class="config-card-header">
<div><div class="config-card-title">Select method</div><div class="config-card-subtitle" id="reflect-services-summary"></div></div>
<span class="config-card-optional" style="color:var(--error,#f87171);">Required</span>
</div>
<div class="config-card-body" style="padding: 0 16px 16px; gap: 0;">
<div id="reflect-services-list"></div>
</div>
</div>
</div>
</div><!-- /grpc-src-reflection -->
<!-- ══ Manual source ══ -->
<div id="grpc-src-manual" style="display:none">
<div class="config-card" style="margin-bottom: 20px;">
<div class="config-card-header">
<div class="config-card-header-icon">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8h12M10 4l4 4-4 4"/></svg>
</div>
<div>
<div class="config-card-title">Method route</div>
<div class="config-card-subtitle">Full gRPC path to the unary method</div>
</div>
<span class="config-card-optional" style="color:var(--error,#f87171);">Required</span>
</div>
<div class="config-card-body" style="gap: 16px;">
<div class="form-group">
<label class="form-label">gRPC route <span class="form-label-required">required</span></label>
<input class="form-input input-mono" id="grpc-manual-route" type="text" placeholder="/package.ServiceName/MethodName" autocomplete="off" spellcheck="false" oninput="grpcManualRouteInput(this.value)">
<div class="form-hint">Format: <code style="font-family:monospace;font-size:11px;color:var(--accent);">/[package.]ServiceName/MethodName</code></div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Request type <span class="form-label-optional">(optional)</span></label>
<input class="form-input input-mono" id="grpc-manual-req-type" type="text" placeholder="GetContactRequest" autocomplete="off" spellcheck="false" oninput="grpcManualTypeInput()">
<div class="form-hint">Protobuf message name, for documentation.</div>
</div>
<div class="form-group">
<label class="form-label">Response type <span class="form-label-optional">(optional)</span></label>
<input class="form-input input-mono" id="grpc-manual-res-type" type="text" placeholder="Contact" autocomplete="off" spellcheck="false" oninput="grpcManualTypeInput()">
<div class="form-hint">Protobuf message name, for documentation.</div>
</div>
</div>
</div>
</div>
<div class="info-callout" style="margin-bottom: 20px;">
<svg class="info-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="7"/><path d="M8 11V8M8 5v-.5"/></svg>
<div class="info-callout-body">
<div class="info-callout-title">Input and output schemas are defined in step 4</div>
<div class="info-callout-text">In manual mode you only need the route. The MCP tool input/output schemas — which the LLM uses to call this tool — are configured in the next step regardless of Protobuf definitions.</div>
</div>
</div>
</div><!-- /grpc-src-manual -->
<!-- ══ Shared method detail (proto + reflection) ══ -->
<div id="grpc-method-detail" class="config-card" style="margin-bottom: 20px; display:none">
<div class="config-card-header">
<div class="config-card-header-icon">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8h12M10 4l4 4-4 4"/></svg>
</div>
<div>
<div class="config-card-title">Method signature</div>
<div class="config-card-subtitle" id="grpc-method-subtitle"></div>
</div>
</div>
<div class="config-card-body" style="gap: 16px; padding: 16px;">
<div class="proto-path-row">
<span class="proto-path-label">gRPC route</span>
<code class="proto-path-value" id="grpc-path-value"></code>
</div>
<div class="proto-sig-grid" id="grpc-detail-sig">
<div class="proto-sig-col">
<div class="proto-sig-col-header">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M2 8h12M8 3l5 5-5 5"/></svg>
Request <code class="proto-type-badge" id="grpc-req-type"></code>
</div>
<div class="proto-field-list" id="grpc-req-fields"></div>
</div>
<div class="proto-sig-col">
<div class="proto-sig-col-header">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M14 8H2M8 3l-5 5 5 5"/></svg>
Response <code class="proto-type-badge" id="grpc-res-type"></code>
</div>
<div class="proto-field-list" id="grpc-res-fields"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Template: proto service group -->
<template id="tmpl-proto-service-group">
<div class="proto-service-group">
<div class="proto-service-label">
<svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor"><rect x="1" y="3" width="6" height="10" rx="1"/><rect x="9" y="3" width="6" height="10" rx="1"/></svg>
<span class="proto-service-name"></span>
</div>
<div class="proto-method-grid"></div>
</div>
</template>
<!-- Template: proto method card -->
<template id="tmpl-proto-method-card">
<button class="proto-method-card" onclick="selectRpcMethod(this)">
<span class="proto-method-name"></span>
<span class="proto-method-types">
<span class="proto-req-type"></span>
<svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor"><path d="M3 8h10M9 4l4 4-4 4"/></svg>
<span class="proto-res-type"></span>
</span>
</button>
</template>
<!-- Template: proto field row -->
<template id="tmpl-proto-field-row">
<div class="proto-field-row">
<span class="proto-field-name"></span>
<span class="proto-field-type"></span>
</div>
</template>