Files
crank/apps/ui/html/wizard/step3-grpc.html
T
2026-05-02 13:33:24 +00:00

275 lines
20 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>
<span data-step-counter>Step 3 of 5</span>
</div>
<h1 class="step-panel-title" data-i18n="wizard.step3.grpc.title">RPC method</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step3.grpc.subtitle">Choose how to describe the gRPC service contract. Upload a .proto file, discover methods from a live server via reflection, or enter the route manually. Only unary RPCs are supported.</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>
<span data-i18n="wizard.step3.grpc.source_proto">Proto file</span>
</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>
<span data-i18n="wizard.step3.grpc.source_reflection">Descriptor set</span>
</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>
<span data-i18n="wizard.step3.grpc.source_manual">Manual</span>
</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" data-i18n="wizard.step3.grpc.proto_title">Proto definition</div>
<div class="config-card-subtitle" data-i18n="wizard.step3.grpc.proto_subtitle">Upload or paste your .proto file</div>
</div>
<span class="config-card-optional" style="color:var(--error,#f87171);" data-i18n="wizard.required">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" data-i18n="wizard.step3.grpc.drop_title">Drop .proto file here</div>
<div class="proto-dropzone-sub" data-i18n="wizard.step3.grpc.drop_subtitle">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()" data-i18n="wizard.step3.grpc.change_file">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" data-i18n="wizard.step3.grpc.paste_content">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()" data-i18n="wizard.step3.grpc.parse_proto">Parse proto</button>
<button class="btn-ghost-sm" onclick="cancelProtoPaste()" data-i18n="btn.cancel">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" data-i18n="wizard.step3.grpc.streaming_excluded">Streaming methods excluded</div>
<div class="info-callout-text" id="proto-streaming-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" data-i18n="wizard.step3.grpc.select_method">Select method</div><div class="config-card-subtitle" id="proto-services-summary"></div></div>
<span class="config-card-optional" style="color:var(--error,#f87171);" data-i18n="wizard.required">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" data-i18n="wizard.step3.grpc.discovery_live_title">Descriptor-driven discovery</div>
<div class="config-card-subtitle" data-i18n="wizard.step3.grpc.discovery_live_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" data-i18n="wizard.step3.grpc.no_upstream">No upstream selected</span>
<span class="grpc-reflect-upstream-url" id="grpc-reflect-upstream-url" data-i18n="wizard.step3.grpc.select_upstream_first">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>
<span data-i18n="wizard.step3.grpc.open_tools">Open live descriptor tools</span>
</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" data-i18n="wizard.step3.grpc.connecting">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" data-i18n="wizard.step3.grpc.live_step5">Live descriptor discovery is handled in step 5.</span>
<button class="proto-file-change" onclick="startReflection()" data-i18n="wizard.step3.grpc.retry">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" data-i18n="wizard.step3.grpc.streaming_excluded">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" data-i18n="wizard.step3.grpc.select_method">Select method</div><div class="config-card-subtitle" id="reflect-services-summary"></div></div>
<span class="config-card-optional" style="color:var(--error,#f87171);" data-i18n="wizard.required">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" data-i18n="wizard.step3.grpc.manual_title">Method route</div>
<div class="config-card-subtitle" data-i18n="wizard.step3.grpc.manual_subtitle">Full gRPC path to the unary method</div>
</div>
<span class="config-card-optional" style="color:var(--error,#f87171);" data-i18n="wizard.required">Required</span>
</div>
<div class="config-card-body" style="gap: 16px;">
<div class="form-group">
<label class="form-label"><span data-i18n="wizard.step3.grpc.route">gRPC route</span> <span class="form-label-required" data-i18n="workspace_setup.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" data-i18n="wizard.step3.grpc.route_hint">Format: /[package.]ServiceName/MethodName</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label"><span data-i18n="wizard.step3.grpc.request_type">Request type</span> <span class="form-label-optional" data-i18n="workspace_setup.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" data-i18n="wizard.step3.grpc.proto_doc_hint">Protobuf message name, for documentation.</div>
</div>
<div class="form-group">
<label class="form-label"><span data-i18n="wizard.step3.grpc.response_type">Response type</span> <span class="form-label-optional" data-i18n="workspace_setup.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" data-i18n="wizard.step3.grpc.proto_doc_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" data-i18n="wizard.step3.grpc.schema_title">Input and output schemas are defined in step 4</div>
<div class="info-callout-text" data-i18n="wizard.step3.grpc.schema_body">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" data-i18n="wizard.step3.grpc.signature_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" data-i18n="wizard.step3.grpc.route_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>
<span data-i18n="wizard.step3.grpc.request_label">Request</span> <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>
<span data-i18n="wizard.step3.grpc.response_label">Response</span> <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>