insika 0.9.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +237 -2
- data/README.md +5 -3
- data/bin/insika +3 -0
- data/bin/insika-router +0 -1
- data/docs/AGENTS.md +90 -7
- data/docs/ARCHITECTURE.md +12 -7
- data/docs/ARTIFACTS.md +33 -3
- data/docs/BENCHMARK.md +50 -7
- data/docs/CONTEXT.md +63 -0
- data/docs/CROSS-HARNESS-BENCH.md +157 -0
- data/docs/DEPLOY.md +57 -11
- data/docs/EMBEDDING.md +10 -0
- data/docs/EVALS.md +33 -4
- data/docs/FACTS.md +8 -6
- data/docs/HARVEST.md +8 -3
- data/docs/KNOWLEDGE.md +7 -0
- data/docs/MEDIA.md +6 -2
- data/docs/OBSERVABILITY.md +143 -22
- data/docs/PERFORMANCE.md +110 -0
- data/docs/PLUGINS.md +4 -4
- data/docs/POLICY.md +22 -13
- data/docs/RELEASING.md +5 -0
- data/docs/ROUTER.md +7 -0
- data/docs/RUBYLLM_2_MIGRATION.md +307 -0
- data/docs/SECURITY.md +7 -0
- data/docs/SHARED-CONVERSATIONS.md +40 -0
- data/docs/SKILLS.md +5 -3
- data/docs/STUDIO.md +122 -0
- data/docs/TOOLS.md +111 -9
- data/docs/operate.md +3 -0
- data/lib/insika/agent_defaults.rb +140 -0
- data/lib/insika/agent_file_store.rb +12 -0
- data/lib/insika/agent_profile.rb +78 -8
- data/lib/insika/artifact_signing.rb +11 -0
- data/lib/insika/chat_builder.rb +79 -25
- data/lib/insika/checkpoint.rb +6 -1
- data/lib/insika/checkpoint_store.rb +59 -7
- data/lib/insika/commands/agent_payload.rb +3 -3
- data/lib/insika/commands/delete_kit.rb +26 -0
- data/lib/insika/commands/gate_harvest.rb +3 -0
- data/lib/insika/commands/review_guardrail_flag.rb +38 -0
- data/lib/insika/commands/run_harvest.rb +17 -1
- data/lib/insika/commands/seed_session.rb +11 -1
- data/lib/insika/commands/send_message.rb +9 -3
- data/lib/insika/commands/update_settings.rb +23 -1
- data/lib/insika/commands/write_kit.rb +27 -0
- data/lib/insika/compaction.rb +33 -7
- data/lib/insika/config_store.rb +87 -7
- data/lib/insika/context/priority.rb +3 -0
- data/lib/insika/context/provider.rb +2 -2
- data/lib/insika/context/providers/knowledge.rb +25 -2
- data/lib/insika/context/providers/memory.rb +53 -4
- data/lib/insika/context/providers/pending_confirmation.rb +52 -0
- data/lib/insika/context/providers/prompt.rb +17 -4
- data/lib/insika/context/providers/session.rb +39 -3
- data/lib/insika/context/reranker.rb +75 -0
- data/lib/insika/distill.rb +22 -13
- data/lib/insika/doctor.rb +34 -4
- data/lib/insika/dsl/runtime.rb +36 -4
- data/lib/insika/dsl/server_boot.rb +4 -12
- data/lib/insika/dsl.rb +13 -5
- data/lib/insika/edge_limiter.rb +23 -15
- data/lib/insika/egress_guard.rb +11 -3
- data/lib/insika/env_schema.rb +33 -73
- data/lib/insika/evals/assertions.rb +171 -16
- data/lib/insika/evals/golden.rb +141 -4
- data/lib/insika/evals/judge.rb +19 -10
- data/lib/insika/evals/moderator_compare.rb +47 -0
- data/lib/insika/evals/report.rb +61 -9
- data/lib/insika/evals/runner.rb +44 -4
- data/lib/insika/evals/simulator.rb +10 -1
- data/lib/insika/evals/transport.rb +200 -0
- data/lib/insika/event_stream.rb +63 -7
- data/lib/insika/evidence.rb +29 -8
- data/lib/insika/executor.rb +668 -141
- data/lib/insika/harvest.rb +26 -14
- data/lib/insika/harvest_engine.rb +1 -0
- data/lib/insika/harvest_store.rb +12 -0
- data/lib/insika/http_client.rb +87 -4
- data/lib/insika/kits.rb +54 -0
- data/lib/insika/knowledge.rb +34 -20
- data/lib/insika/llm_configurator.rb +46 -4
- data/lib/insika/llm_provider_store.rb +1 -3
- data/lib/insika/llm_trace_store.rb +54 -0
- data/lib/insika/mcp_client.rb +27 -63
- data/lib/insika/mcp_live_tool.rb +55 -9
- data/lib/insika/mcp_store.rb +32 -1
- data/lib/insika/mcp_tool_registry.rb +69 -26
- data/lib/insika/media.rb +38 -25
- data/lib/insika/memory_store.rb +4 -3
- data/lib/insika/memory_trim.rb +60 -0
- data/lib/insika/middleware.rb +12 -3
- data/lib/insika/model_metrics_store.rb +285 -0
- data/lib/insika/model_registry.rb +110 -0
- data/lib/insika/model_resolver.rb +1 -1
- data/lib/insika/model_selection.rb +14 -21
- data/lib/insika/model_visible.rb +17 -6
- data/lib/insika/onboarding.rb +1 -0
- data/lib/insika/outcome_store.rb +1 -1
- data/lib/insika/overlay_tool_registry.rb +25 -2
- data/lib/insika/pack_importer.rb +54 -9
- data/lib/insika/packaging.rb +14 -5
- data/lib/insika/pending_action_store.rb +45 -18
- data/lib/insika/plugin/loader.rb +4 -2
- data/lib/insika/profile_source.rb +4 -0
- data/lib/insika/provider_error_classifier.rb +18 -3
- data/lib/insika/queue_policy.rb +7 -2
- data/lib/insika/refinement/proposer.rb +42 -17
- data/lib/insika/reliability.rb +12 -3
- data/lib/insika/router/app.rb +14 -5
- data/lib/insika/router/session_key.rb +35 -5
- data/lib/insika/safety/config.rb +20 -3
- data/lib/insika/safety/corpus.rb +10 -3
- data/lib/insika/safety/factory.rb +39 -1
- data/lib/insika/safety/input_guardrail.rb +21 -1
- data/lib/insika/safety/judge_moderator.rb +70 -0
- data/lib/insika/safety/output_validator.rb +29 -5
- data/lib/insika/safety/prompt_echo.rb +89 -0
- data/lib/insika/schema_guard.rb +53 -76
- data/lib/insika/server/app.rb +17 -8
- data/lib/insika/server/responses.rb +19 -1
- data/lib/insika/session_actor.rb +58 -28
- data/lib/insika/session_store.rb +159 -10
- data/lib/insika/settings_store.rb +47 -1
- data/lib/insika/shared_conversations.rb +305 -0
- data/lib/insika/skill_catalog.rb +28 -2
- data/lib/insika/skill_store.rb +6 -0
- data/lib/insika/soak/runner.rb +1 -1
- data/lib/insika/steer_injector.rb +7 -48
- data/lib/insika/store.rb +29 -1
- data/lib/insika/stores/json_value.rb +37 -0
- data/lib/insika/stores/memory.rb +22 -35
- data/lib/insika/stores/postgres.rb +295 -0
- data/lib/insika/stores/sqlite.rb +257 -43
- data/lib/insika/stores/turn_counter.rb +25 -0
- data/lib/insika/studio/app.rb +1453 -161
- data/lib/insika/studio/assets/dist/application.css +1 -1
- data/lib/insika/studio/assets/dist/application.js +25 -25
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- data/lib/insika/studio/assets/dist/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
- data/lib/insika/studio/forms.rb +171 -9
- data/lib/insika/studio/nav_icons.rb +3 -0
- data/lib/insika/studio/views/_agent_config_retrieval.erb +73 -0
- data/lib/insika/studio/views/_agent_picker.erb +15 -0
- data/lib/insika/studio/views/_agent_tab_config.erb +78 -242
- data/lib/insika/studio/views/_agent_tab_history.erb +56 -1
- data/lib/insika/studio/views/_agent_tab_performance.erb +34 -0
- data/lib/insika/studio/views/_agent_tab_prompts.erb +7 -9
- data/lib/insika/studio/views/_agent_tab_skills.erb +16 -7
- data/lib/insika/studio/views/_agent_tab_tools.erb +90 -0
- data/lib/insika/studio/views/_agents_master.erb +8 -12
- data/lib/insika/studio/views/_cfg_alerts.erb +4 -0
- data/lib/insika/studio/views/_cfg_budget.erb +20 -0
- data/lib/insika/studio/views/_cfg_distill.erb +26 -0
- data/lib/insika/studio/views/_cfg_followup.erb +30 -0
- data/lib/insika/studio/views/_cfg_grounding.erb +21 -0
- data/lib/insika/studio/views/_cfg_guardrails.erb +47 -0
- data/lib/insika/studio/views/_cfg_harvest.erb +35 -0
- data/lib/insika/studio/views/_cfg_reliability.erb +32 -0
- data/lib/insika/studio/views/_chats_master.erb +66 -0
- data/lib/insika/studio/views/_customers_master.erb +37 -0
- data/lib/insika/studio/views/_empty.erb +15 -0
- data/lib/insika/studio/views/_facts_master.erb +42 -0
- data/lib/insika/studio/views/_harvest_candidate_detail.erb +14 -0
- data/lib/insika/studio/views/_identity.erb +16 -0
- data/lib/insika/studio/views/_kpi.erb +17 -0
- data/lib/insika/studio/views/_message.erb +40 -2
- data/lib/insika/studio/views/_model_chart.erb +82 -0
- data/lib/insika/studio/views/_refinement_master.erb +38 -0
- data/lib/insika/studio/views/{tool_edit.erb → _tool_edit.erb} +32 -9
- data/lib/insika/studio/views/_tool_users.erb +17 -0
- data/lib/insika/studio/views/_why.erb +53 -0
- data/lib/insika/studio/views/agent_detail.erb +17 -10
- data/lib/insika/studio/views/agents.erb +28 -16
- data/lib/insika/studio/views/approvals.erb +16 -11
- data/lib/insika/studio/views/artifact.erb +20 -7
- data/lib/insika/studio/views/artifacts.erb +19 -14
- data/lib/insika/studio/views/chats.erb +22 -29
- data/lib/insika/studio/views/customer.erb +88 -43
- data/lib/insika/studio/views/customers.erb +16 -26
- data/lib/insika/studio/views/evals.erb +49 -31
- data/lib/insika/studio/views/fact_detail.erb +114 -0
- data/lib/insika/studio/views/facts.erb +15 -116
- data/lib/insika/studio/views/followups.erb +63 -52
- data/lib/insika/studio/views/funnel.erb +104 -43
- data/lib/insika/studio/views/harvest.erb +85 -59
- data/lib/insika/studio/views/home.erb +138 -23
- data/lib/insika/studio/views/inbox.erb +36 -0
- data/lib/insika/studio/views/kits.erb +176 -0
- data/lib/insika/studio/views/knowledge.erb +92 -80
- data/lib/insika/studio/views/layout.erb +42 -11
- data/lib/insika/studio/views/live.erb +45 -0
- data/lib/insika/studio/views/mcp.erb +43 -13
- data/lib/insika/studio/views/models.erb +187 -0
- data/lib/insika/studio/views/parity.erb +38 -32
- data/lib/insika/studio/views/playground.erb +13 -9
- data/lib/insika/studio/views/refinement.erb +15 -224
- data/lib/insika/studio/views/refinement_detail.erb +243 -0
- data/lib/insika/studio/views/session.erb +152 -117
- data/lib/insika/studio/views/settings.erb +86 -22
- data/lib/insika/studio/views/skills.erb +53 -19
- data/lib/insika/studio/views/system_files.erb +7 -7
- data/lib/insika/studio/views/task.erb +45 -15
- data/lib/insika/studio/views/tasks.erb +35 -18
- data/lib/insika/studio/views/tools.erb +62 -73
- data/lib/insika/task_actor.rb +4 -1
- data/lib/insika/task_store.rb +67 -1
- data/lib/insika/telemetry/pricing.rb +16 -14
- data/lib/insika/telemetry/recorder.rb +103 -9
- data/lib/insika/telemetry/ruby_llm_instrumenter.rb +110 -0
- data/lib/insika/telemetry.rb +12 -2
- data/lib/insika/templates/daily-digest/README.md +13 -0
- data/lib/insika/templates/daily-digest/agent.rb +22 -11
- data/lib/insika/testing/store_contract.rb +65 -0
- data/lib/insika/text_tool_call.rb +66 -0
- data/lib/insika/tick.rb +7 -1
- data/lib/insika/token_store.rb +5 -0
- data/lib/insika/tool_batch.rb +1 -1
- data/lib/insika/tool_catalog.rb +12 -2
- data/lib/insika/tool_definition.rb +10 -4
- data/lib/insika/tool_envelope.rb +165 -28
- data/lib/insika/tool_manifest.rb +1 -0
- data/lib/insika/tool_store.rb +3 -0
- data/lib/insika/tool_trace_store.rb +15 -8
- data/lib/insika/tool_usage_report.rb +2 -2
- data/lib/insika/tools/a2a_remote.rb +1 -1
- data/lib/insika/tools/agent_enum.rb +1 -1
- data/lib/insika/tools/confirm_pending.rb +112 -0
- data/lib/insika/tools/data_defined_tool.rb +23 -15
- data/lib/insika/tools/generate_image.rb +2 -2
- data/lib/insika/tools/load_knowledge.rb +7 -3
- data/lib/insika/tools/load_skill.rb +3 -3
- data/lib/insika/tools/present.rb +1 -8
- data/lib/insika/tools/remember.rb +7 -2
- data/lib/insika/tools/run_persona_eval.rb +3 -5
- data/lib/insika/tools/save_artifact.rb +16 -6
- data/lib/insika/tools/schedule_followup.rb +3 -3
- data/lib/insika/tools/stuck_signal.rb +3 -3
- data/lib/insika/tools/subagent.rb +5 -5
- data/lib/insika/tools/subagents.rb +3 -3
- data/lib/insika/tools/tool_search.rb +7 -5
- data/lib/insika/tools/tts.rb +2 -2
- data/lib/insika/tools/update_briefing.rb +4 -4
- data/lib/insika/turn_output.rb +2 -2
- data/lib/insika/turn_state.rb +18 -0
- data/lib/insika/turn_timing.rb +51 -14
- data/lib/insika/version.rb +1 -1
- data/lib/insika/wiring/graph.rb +81 -16
- data/lib/insika/workflow.rb +25 -64
- data/lib/insika.rb +19 -3
- metadata +69 -13
- data/lib/insika/commands/import_mcp_tools.rb +0 -48
- data/lib/insika/mcp_http_client.rb +0 -67
- data/lib/insika/mcp_tool_ingestor.rb +0 -165
|
@@ -1,38 +1,35 @@
|
|
|
1
|
-
<%# Knowledge — drill-down, single-agent-scoped (like Harvest, not Skills'
|
|
2
|
-
shared+override shape): the KnowledgeStore is a real per-agent partition.
|
|
3
|
-
@agent/@tenant/@status pick the scope; @selected drives the detail pane
|
|
4
|
-
(nil = none, "" = new, name = edit). %>
|
|
5
1
|
<header class="page-head">
|
|
6
2
|
<div>
|
|
7
|
-
|
|
8
|
-
<h1>Knowledge<span class="sub"
|
|
3
|
+
<%== crumbs_for("Knowledge") %>
|
|
4
|
+
<h1>Knowledge<span class="sub">Review what your agents learn</span></h1>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="head-actions">
|
|
7
|
+
<form method="get" action="/studio/knowledge" class="actions inline">
|
|
8
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
9
|
+
<% if @tenant %><input type="hidden" name="tenant" value="<%= @tenant %>"><% end %>
|
|
10
|
+
<label>Agent
|
|
11
|
+
<%== render("_agent_picker", locals: { name: "agent", options: @agents, value: @agent, auto_submit: true }) %>
|
|
12
|
+
</label>
|
|
13
|
+
</form>
|
|
14
|
+
<% if insika[:knowledge_store] && @agents.any? %>
|
|
15
|
+
<a class="btn primary" href="<%= knowledge_path(@agent, 'new', @tenant) %>">New concept</a>
|
|
16
|
+
<% end %>
|
|
9
17
|
</div>
|
|
10
18
|
</header>
|
|
11
19
|
|
|
12
|
-
<p class="muted matrix-hint">Concepts the engine extracted from finished conversations — facts, procedures, policies, objections. Learned content is <code>provenance: observed</code>; an operator promotes one to <code>policy</code> by editing it here. A conflict is never resolved automatically — a human decides.</p>
|
|
13
|
-
|
|
14
20
|
<% if insika[:knowledge_store].nil? %>
|
|
15
21
|
<div class="empty">No knowledge — the store is not wired.</div>
|
|
16
22
|
<% elsif @agents.empty? %>
|
|
17
23
|
<div class="empty">No agents yet. Add a <span class="mono">knowledge:</span> block to an agent's pack to start learning from its traffic.</div>
|
|
18
24
|
<% else %>
|
|
19
|
-
|
|
20
|
-
<div class="filter-bar">
|
|
21
|
-
<% @agents.each do |id| %>
|
|
22
|
-
<a class="btn" href="/studio/knowledge?agent=<%= Rack::Utils.escape(id) %>"<%== ' aria-current="page"' if id == @agent %>><%= id %></a>
|
|
23
|
-
<% end %>
|
|
24
|
-
</div>
|
|
25
|
-
<% end %>
|
|
26
|
-
<div class="filter-bar">
|
|
27
|
-
<a class="btn" href="/studio/knowledge?agent=<%= Rack::Utils.escape(@agent.to_s) %>&status=all"<%== ' aria-current="page"' if @status == "all" %>>all <span class="count"><%= @all_count %></span></a>
|
|
28
|
-
<a class="btn" href="/studio/knowledge?agent=<%= Rack::Utils.escape(@agent.to_s) %>&status=conflict"<%== ' aria-current="page"' if @status == "conflict" %>>conflicts <span class="count"><%= @conflict_count %></span></a>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="drill" data-controller="list-filter">
|
|
25
|
+
<div class="drill knowledge-workspace" data-controller="list-filter">
|
|
32
26
|
<aside class="drill-pane drill-master">
|
|
33
27
|
<div class="drill-pane-head">
|
|
34
28
|
<div class="title"><strong>Concepts <span class="count"><%= @concepts.size %></span></strong></div>
|
|
35
|
-
<
|
|
29
|
+
<div class="segmented" role="group" aria-label="status filter">
|
|
30
|
+
<a href="<%= knowledge_path(@agent, nil, @tenant, status: 'all') %>"<%== ' aria-current="page"' if @status == "all" %>>All <span class="count"><%= @all_count %></span></a>
|
|
31
|
+
<a href="<%= knowledge_path(@agent, nil, @tenant, status: 'conflict') %>"<%== ' aria-current="page"' if @status == "conflict" %>>Conflicts <span class="count"><%= @conflict_count %></span></a>
|
|
32
|
+
</div>
|
|
36
33
|
</div>
|
|
37
34
|
<div class="drill-search">
|
|
38
35
|
<input type="search" class="filter-input" placeholder="Search concepts…" aria-label="Filter concepts"
|
|
@@ -41,79 +38,94 @@
|
|
|
41
38
|
<nav class="drill-pane-body drill-list" aria-label="concepts">
|
|
42
39
|
<% @concepts.each do |c| %>
|
|
43
40
|
<a class="drill-item<%== ' active' if @selected == c[:name] %>"
|
|
44
|
-
href="
|
|
41
|
+
href="<%= knowledge_path(@agent, c[:name], @tenant, status: @status) %>"
|
|
42
|
+
<%== ' aria-current="page"' if @selected == c[:name] %>
|
|
45
43
|
data-list-filter-target="item" data-filter-text="<%= c[:name] %> <%= c[:description] %>">
|
|
46
|
-
<div class="drill-item-name">
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
<div class="drill-item-name"><span class="txt"><%= c[:name].tr('-', ' ') %></span></div>
|
|
45
|
+
<div class="drill-item-sub"><%= c[:description] %></div>
|
|
46
|
+
<div class="review-item-meta">
|
|
47
|
+
<% if concept_conflict?(c) %><span class="pill warn">conflict</span><% else %><span class="muted"><%= c[:type] %></span><% end %>
|
|
48
|
+
<span class="muted"><%= c[:occurrences] %> occurrence(s)</span>
|
|
50
49
|
</div>
|
|
51
|
-
<div class="drill-item-sub"><%= c[:description] %> · <span class="pill info"><%= format("%.2f", c[:confidence]) %></span> · ×<%= c[:occurrences] %></div>
|
|
52
50
|
</a>
|
|
53
51
|
<% end %>
|
|
54
|
-
<div class="empty" data-list-filter-target="empty" hidden
|
|
52
|
+
<div class="empty" data-list-filter-target="empty"<%== ' hidden' if @concepts.any? %>>No concepts match your filter.</div>
|
|
55
53
|
</nav>
|
|
56
54
|
</aside>
|
|
57
55
|
|
|
58
56
|
<section class="drill-pane drill-detail">
|
|
59
57
|
<% if @selected.nil? %>
|
|
60
|
-
<div class="drill-empty">
|
|
61
|
-
<div>
|
|
62
|
-
<h2>Knowledge</h2>
|
|
63
|
-
<p>Pick a concept on the left to read or edit it — or create one by hand.</p>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
58
|
+
<div class="drill-empty"><div><h2>No concepts to review</h2><p>Concepts appear as your agent learns from finished conversations. You can also create one by hand.</p></div></div>
|
|
66
59
|
<% else %>
|
|
67
|
-
<% new_concept = @selected == "" %>
|
|
68
|
-
<div class="drill-pane-head">
|
|
69
|
-
<div class="title">
|
|
70
|
-
<strong class="mono"><%= new_concept ? "New concept" : @selected %></strong>
|
|
71
|
-
<% if !new_concept && concept_conflict?(@selected_concept) %><span class="pill warn">conflict — resolve by editing below</span><% end %>
|
|
72
|
-
</div>
|
|
73
|
-
<button type="submit" form="concept-form" class="btn primary" data-turbo-submits-with="Saving…">Save concept</button>
|
|
74
|
-
</div>
|
|
60
|
+
<% new_concept = @selected == ""; concept = @selected_concept %>
|
|
75
61
|
<div class="drill-pane-body">
|
|
62
|
+
<div class="review-heading">
|
|
63
|
+
<div>
|
|
64
|
+
<h2><%= new_concept ? "New concept" : @selected.tr('-', ' ') %></h2>
|
|
65
|
+
<% if concept %><p class="muted"><%= concept[:description] %></p><% end %>
|
|
66
|
+
</div>
|
|
67
|
+
<% if concept %><span class="pill <%= concept_conflict?(concept) ? 'warn' : 'plain' %>"><%= concept_conflict?(concept) ? 'conflict' : concept[:provenance] %></span><% end %>
|
|
68
|
+
</div>
|
|
69
|
+
<% if concept_conflict?(concept) %>
|
|
70
|
+
<div class="flash warn" role="status">This concept contains conflicting claims. Review the sources, then edit it to resolve the contradiction.</div>
|
|
71
|
+
<% end %>
|
|
72
|
+
<% if !new_concept && concept %>
|
|
73
|
+
<article class="card knowledge-reading">
|
|
74
|
+
<div class="review-item-meta"><span class="muted"><%= concept[:type] %></span><span class="muted">Confidence <%= format('%.2f', concept[:confidence]) %> · <%= concept[:occurrences] %> occurrence(s)</span></div>
|
|
75
|
+
<div class="markdown-preview knowledge-body" data-controller="markdown"><%= concept[:body] %></div>
|
|
76
|
+
</article>
|
|
77
|
+
<% end %>
|
|
76
78
|
<% unless new_concept %>
|
|
77
|
-
<
|
|
79
|
+
<details class="card review-details">
|
|
80
|
+
<summary>Sources and history <span class="muted">· <%= Array(concept&.dig(:sources)).size %> source(s)</span></summary>
|
|
81
|
+
<% if concept %><p class="muted">Updated <%= concept[:updated_at] %> · Created <%= concept[:created_at] %></p><% end %>
|
|
82
|
+
<% if Array(concept&.dig(:sources)).empty? %><p class="muted">No source conversations recorded.</p><% end %>
|
|
83
|
+
<ul class="history">
|
|
84
|
+
<% Array(concept&.dig(:sources)).each do |sid| %><li><a class="mono" href="/studio/sessions/<%= Rack::Utils.escape(sid) %>"><%= sid %></a></li><% end %>
|
|
85
|
+
</ul>
|
|
86
|
+
<% Array(@concept_versions).each_with_index do |v, i| %>
|
|
87
|
+
<form method="post" action="/studio/knowledge/<%= Rack::Utils.escape(@selected) %>/restore" class="actions">
|
|
88
|
+
<%== csrf_tag %>
|
|
89
|
+
<input type="hidden" name="agent" value="<%= @agent %>">
|
|
90
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
91
|
+
<% if @tenant %><input type="hidden" name="tenant" value="<%= @tenant %>"><% end %>
|
|
92
|
+
<input type="hidden" name="version" value="<%= i %>">
|
|
93
|
+
<span class="muted"><%= v['at'] %></span><button type="submit" class="ghost">Restore</button>
|
|
94
|
+
</form>
|
|
95
|
+
<% end %>
|
|
96
|
+
</details>
|
|
97
|
+
<% end %>
|
|
98
|
+
<details class="card review-details"<%== ' open' if new_concept || concept.nil? %>>
|
|
99
|
+
<summary><%= new_concept ? 'Write a concept' : 'Edit concept' %></summary>
|
|
100
|
+
<p class="muted">Edit the claim and its metadata. Set <code>provenance: policy</code> only for a policy you have verified.</p>
|
|
101
|
+
<form id="concept-form" method="post" action="/studio/knowledge" data-controller="dirty-guard">
|
|
78
102
|
<%== csrf_tag %>
|
|
79
103
|
<input type="hidden" name="agent" value="<%= @agent %>">
|
|
80
|
-
<
|
|
104
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
105
|
+
<% if @tenant %><input type="hidden" name="tenant" value="<%= @tenant %>"><% end %>
|
|
106
|
+
<% if new_concept %>
|
|
107
|
+
<label>Name <span class="muted">Match the name in the frontmatter.</span><input type="text" name="name" placeholder="my-concept" required></label>
|
|
108
|
+
<% else %>
|
|
109
|
+
<input type="hidden" name="name" value="<%= @selected %>">
|
|
110
|
+
<% end %>
|
|
111
|
+
<label for="concept-content">Concept markdown</label>
|
|
112
|
+
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
113
|
+
<textarea id="concept-content" name="content" data-code-editor-target="source" rows="18"><%= @concept_content %></textarea>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Saving…">Save concept</button></div>
|
|
81
116
|
</form>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<input type="
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<label>Concept markdown <span class="muted">YAML frontmatter (<code>name</code>, <code>description</code>, <code>type</code>, <code>provenance</code>, <code>confidence</code>, …) + the durable claim</span></label>
|
|
95
|
-
<div data-controller="code-editor" data-code-editor-language-value="markdown" data-code-editor-preview-value="true">
|
|
96
|
-
<textarea name="content" data-code-editor-target="source" rows="18"><%= @concept_content %></textarea>
|
|
97
|
-
</div>
|
|
98
|
-
</form>
|
|
99
|
-
|
|
100
|
-
<% if !new_concept && Array(@concept_versions).any? %>
|
|
101
|
-
<details class="versions">
|
|
102
|
-
<summary><%= @concept_versions.size %> previous version(s)</summary>
|
|
103
|
-
<ul>
|
|
104
|
-
<% @concept_versions.each_with_index do |v, i| %>
|
|
105
|
-
<li>
|
|
106
|
-
<span class="muted mono"><%= v["at"] %></span>
|
|
107
|
-
<form method="post" action="/studio/knowledge/<%= Rack::Utils.escape(@selected) %>/restore">
|
|
108
|
-
<%== csrf_tag %>
|
|
109
|
-
<input type="hidden" name="agent" value="<%= @agent %>">
|
|
110
|
-
<% if @tenant %><input type="hidden" name="tenant" value="<%= @tenant %>"><% end %>
|
|
111
|
-
<input type="hidden" name="version" value="<%= i %>">
|
|
112
|
-
<button type="submit" class="ghost">Restore</button>
|
|
113
|
-
</form>
|
|
114
|
-
</li>
|
|
115
|
-
<% end %>
|
|
116
|
-
</ul>
|
|
117
|
+
</details>
|
|
118
|
+
<% unless new_concept %>
|
|
119
|
+
<details class="review-details">
|
|
120
|
+
<summary>Remove concept</summary>
|
|
121
|
+
<p class="muted">Permanently remove this concept from the selected scope.</p>
|
|
122
|
+
<form method="post" action="/studio/knowledge/<%= Rack::Utils.escape(@selected) %>/delete" data-turbo-confirm="Remove concept "<%= @selected %>"? This can't be undone.">
|
|
123
|
+
<%== csrf_tag %>
|
|
124
|
+
<input type="hidden" name="agent" value="<%= @agent %>">
|
|
125
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
126
|
+
<% if @tenant %><input type="hidden" name="tenant" value="<%= @tenant %>"><% end %>
|
|
127
|
+
<button type="submit" class="danger" data-turbo-submits-with="Removing…">Remove concept</button>
|
|
128
|
+
</form>
|
|
117
129
|
</details>
|
|
118
130
|
<% end %>
|
|
119
131
|
</div>
|
|
@@ -11,8 +11,21 @@
|
|
|
11
11
|
<meta name="csp-nonce" content="<%= csp_nonce %>">
|
|
12
12
|
<title>Insika Studio</title>
|
|
13
13
|
<link rel="icon" type="image/svg+xml" href="<%= asset_path("favicon.svg") %>">
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
<%# The two webfonts every page uses on first paint (body sans + mono at 400).
|
|
15
|
+
Without the preload the browser only discovers them after the stylesheet
|
|
16
|
+
parses, which is one round-trip of fallback text. The other mono weights
|
|
17
|
+
are rare enough to stay lazy. The hrefs must match the @font-face `src:`
|
|
18
|
+
in application.css exactly — see font_path in app.rb. %>
|
|
19
|
+
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
20
|
+
href="<%= font_path("ibm-plex-sans-latin-wght-normal.woff2") %>">
|
|
21
|
+
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
22
|
+
href="<%= font_path("ibm-plex-mono-latin-400-normal.woff2") %>">
|
|
23
|
+
<%# data-turbo-track="reload": after a deploy the ?v= changes, and a Turbo
|
|
24
|
+
visit from a tab opened before it must reload the page — merging the
|
|
25
|
+
new head would run a SECOND application.js next to the old one, and
|
|
26
|
+
every controller (live transcript included) would connect twice. %>
|
|
27
|
+
<link rel="stylesheet" href="<%= asset_path("application.css") %>" data-turbo-track="reload">
|
|
28
|
+
<script type="module" src="<%= asset_path("application.js") %>" defer data-turbo-track="reload"></script>
|
|
16
29
|
</head>
|
|
17
30
|
<body>
|
|
18
31
|
<%# Chip-icon sprite: one source of truth for transcript event
|
|
@@ -25,17 +38,21 @@
|
|
|
25
38
|
<% end %>
|
|
26
39
|
</template>
|
|
27
40
|
<% if authenticated? %>
|
|
28
|
-
<div class="app-shell">
|
|
41
|
+
<div class="app-shell" data-controller="palette">
|
|
29
42
|
<aside class="sidebar">
|
|
30
43
|
<a class="brand" href="/studio/home"><span class="dot"></span>Insika <small>Studio</small></a>
|
|
44
|
+
<button type="button" class="palette-open" data-action="palette#open">
|
|
45
|
+
<%== nav_icon(:search) %><span>Search</span><kbd>⌘K</kbd>
|
|
46
|
+
</button>
|
|
31
47
|
|
|
32
48
|
<% nav_sections.each do |group, items| %>
|
|
33
49
|
<div class="nav-group">
|
|
34
50
|
<% unless group.empty? %><div class="nav-label"><%= group %></div><% end %>
|
|
35
51
|
<nav class="nav" aria-label="<%= group %>">
|
|
36
|
-
<% items.each do |label, href, icon| %>
|
|
52
|
+
<% items.each do |label, href, icon, count| %>
|
|
37
53
|
<a href="<%= href %>"<%== ' aria-current="page"' if nav_active?(href) %>>
|
|
38
54
|
<%== nav_icon(icon) %><span class="lbl"><%= label %></span>
|
|
55
|
+
<% if count.to_i.positive? %><span class="nav-badge" aria-label="<%= count %> pending"><%= count %></span><% end %>
|
|
39
56
|
</a>
|
|
40
57
|
<% end %>
|
|
41
58
|
</nav>
|
|
@@ -63,17 +80,15 @@
|
|
|
63
80
|
<button type="submit" class="ghost">Sign out</button>
|
|
64
81
|
</form>
|
|
65
82
|
</div>
|
|
66
|
-
<div class="env-chip" title="runtime environment">
|
|
67
|
-
<span class="env-mark" aria-hidden="true"><%= env_label[0, 1].upcase %></span>
|
|
68
|
-
<div class="env-text">
|
|
69
|
-
<b><%= env_label %></b>
|
|
70
|
-
<small>environment</small>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
83
|
</div>
|
|
74
84
|
</aside>
|
|
75
85
|
|
|
76
86
|
<div class="app-main">
|
|
87
|
+
<% if (env = env_banner) %>
|
|
88
|
+
<div class="env-banner <%= env[:kind] %>" role="status">
|
|
89
|
+
<b><%= env[:label] %></b><span>Not production — changes here never reach customers.</span>
|
|
90
|
+
</div>
|
|
91
|
+
<% end %>
|
|
77
92
|
<% if flash["error"] %>
|
|
78
93
|
<div class="flash err" role="alert"><%= flash["error"] %></div>
|
|
79
94
|
<% end %>
|
|
@@ -85,6 +100,22 @@
|
|
|
85
100
|
<%== yield %>
|
|
86
101
|
</main>
|
|
87
102
|
</div>
|
|
103
|
+
|
|
104
|
+
<%# ⌘K palette: every page and agent, filtered client-side (list-filter). %>
|
|
105
|
+
<dialog class="palette" data-palette-target="dialog" data-controller="list-filter" aria-label="Jump to">
|
|
106
|
+
<input type="text" class="palette-query" autocomplete="off" placeholder="Jump to a page or agent…" aria-label="Jump to"
|
|
107
|
+
data-palette-target="query" data-list-filter-target="query"
|
|
108
|
+
data-action="input->list-filter#filter input->palette#reset keydown->palette#key">
|
|
109
|
+
<div class="palette-list" role="listbox">
|
|
110
|
+
<% palette_entries.each do |group, label, href| %>
|
|
111
|
+
<a href="<%= href %>" role="option" data-palette-target="item" data-list-filter-target="item"
|
|
112
|
+
data-filter-text="<%= group %> <%= label %>" data-action="click->palette#close">
|
|
113
|
+
<span><%= label %></span><small><%= group %></small>
|
|
114
|
+
</a>
|
|
115
|
+
<% end %>
|
|
116
|
+
<div class="palette-empty" data-list-filter-target="empty" hidden>No match.</div>
|
|
117
|
+
</div>
|
|
118
|
+
</dialog>
|
|
88
119
|
</div>
|
|
89
120
|
<% else %>
|
|
90
121
|
<% if flash["error"] %>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<%# Live floor — the latest conversations as cards, plus a ticker of the
|
|
2
|
+
events as they arrive. The server renders the baseline (presence and
|
|
3
|
+
flag from the stored session); live_floor_controller moves the cards over
|
|
4
|
+
/studio/events: tool chips, "agent is writing…" while a turn runs, guardrail
|
|
5
|
+
flags. Read-only — every card links to the session viewer. %>
|
|
6
|
+
<div data-controller="live-floor" data-live-floor-scoped-value="<%= @agent ? "true" : "false" %>" data-live-floor-max-value="<%= Studio::App::LIVE_CARDS %>">
|
|
7
|
+
<header class="page-head">
|
|
8
|
+
<div>
|
|
9
|
+
<%== crumbs_for("Live") %>
|
|
10
|
+
<h1>Live<span class="sub">Conversations happening now</span></h1>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="head-actions">
|
|
13
|
+
<%== agent_filter_form("/studio/live", @agent) %>
|
|
14
|
+
</div>
|
|
15
|
+
</header>
|
|
16
|
+
|
|
17
|
+
<div class="live-grid" data-live-floor-target="grid">
|
|
18
|
+
<% if @sessions.empty? %>
|
|
19
|
+
<div class="empty" data-live-floor-target="placeholder">No conversations yet. Start one in the <a href="/studio/playground">playground</a>.</div>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% @sessions.each do |s| %>
|
|
22
|
+
<% customer = presence(session_customer(s)) %>
|
|
23
|
+
<% preview = session_preview(s) %>
|
|
24
|
+
<a class="card live-card<%== ' flagged' if session_flagged?(s) %>" href="/studio/sessions/<%= Rack::Utils.escape(s.id) %>" data-session-id="<%= s.id %>">
|
|
25
|
+
<div class="live-card-top">
|
|
26
|
+
<%== render("_identity", locals: { id: customer || s.id, name: presence(session_customer_label(s)) || "anonymous", sub: presence(session_agent(s)) }) %>
|
|
27
|
+
<span class="presence<%== ' present' if session_fresh?(s) %>" title="activity in the last 5 minutes"></span>
|
|
28
|
+
</div>
|
|
29
|
+
<p class="live-preview"><%== preview ? mark_tags(preview, 140) : "No messages yet" %></p>
|
|
30
|
+
<div class="live-card-foot">
|
|
31
|
+
<span class="pill err live-flag">flagged</span>
|
|
32
|
+
<span class="live-tools"></span>
|
|
33
|
+
<span class="live-writing">agent is writing…</span>
|
|
34
|
+
</div>
|
|
35
|
+
</a>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<section class="card">
|
|
40
|
+
<div class="section-head"><h2>Events</h2><span class="muted">latest first · this page only</span></div>
|
|
41
|
+
<ol class="live-ticker" data-live-floor-target="ticker">
|
|
42
|
+
<li data-live-floor-target="waiting">Waiting for events…</li>
|
|
43
|
+
</ol>
|
|
44
|
+
</section>
|
|
45
|
+
</div>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<% unless frame_only %>
|
|
11
11
|
<header class="page-head">
|
|
12
12
|
<div>
|
|
13
|
-
<nav class="crumbs"><a href="/studio/agents">agents</a
|
|
13
|
+
<nav class="crumbs"><a href="/studio/agents">agents</a></nav>
|
|
14
14
|
<h1>MCP<span class="sub"><%= @instances.size %> instance(s) · credentials masked</span></h1>
|
|
15
15
|
</div>
|
|
16
16
|
</header>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<span class="txt" title="<%= m["name"] %>"><%= m["name"] %></span>
|
|
41
41
|
<span class="pill <%= status_cls %>"><%= status_label %></span>
|
|
42
42
|
</div>
|
|
43
|
-
<div class="drill-item-sub"><%= m["transport"] %><%== " · #{m['url']}" if m["url"].to_s != "" %><%== " · #{Array(m['tools_cache']).size} tool(s)" if Array(m["tools_cache"]).any? %></div>
|
|
43
|
+
<div class="drill-item-sub"><%= m["transport"] %><%== " · #{h(m['url'])}" if m["url"].to_s != "" %><%== " · #{Array(m['tools_cache']).size} tool(s)" if Array(m["tools_cache"]).any? %></div>
|
|
44
44
|
</a>
|
|
45
45
|
<% end %>
|
|
46
46
|
<div class="empty" data-list-filter-target="empty" hidden>No MCP instances match your filter.</div>
|
|
@@ -55,10 +55,11 @@
|
|
|
55
55
|
<% end %>
|
|
56
56
|
<% if @selected_mcp %>
|
|
57
57
|
<% m = @selected_mcp %>
|
|
58
|
+
<% status_label, status_cls = m["enabled"] ? mcp_status(m) : ["off", "warn"] %>
|
|
58
59
|
<div class="drill-pane-head">
|
|
59
60
|
<div class="title">
|
|
60
|
-
<strong class="mono"><%= m["name"] %></strong>
|
|
61
|
-
<span class="count"><%= m["transport"] %><%== " · #{m['description']}" if m["description"].to_s != "" %></span>
|
|
61
|
+
<strong class="mono"><%= m["name"] %> <span class="pill <%= status_cls %>"><%= status_label %></span></strong>
|
|
62
|
+
<span class="count"><%= m["transport"] %><%== " · #{h(m['description'])}" if m["description"].to_s != "" %><%== " · #{Array(m['tools_cache']).size} tool(s)" if Array(m["tools_cache"]).any? %></span>
|
|
62
63
|
</div>
|
|
63
64
|
<div class="head-actions">
|
|
64
65
|
<form method="post" action="/studio/mcp/test">
|
|
@@ -74,19 +75,37 @@
|
|
|
74
75
|
</div>
|
|
75
76
|
</div>
|
|
76
77
|
<div class="drill-pane-body">
|
|
78
|
+
<%# ~370 tools on a chatty server would be one unscannable wall of
|
|
79
|
+
paragraphs — a filterable row list (mono name + one-line preview,
|
|
80
|
+
<details> for the rest) scales the same way the master lists do. %>
|
|
77
81
|
<% tools = Array(m["tools_cache"]) %>
|
|
78
82
|
<% if tools.any? %>
|
|
79
|
-
<details class="mcp-tools-
|
|
80
|
-
<summary><%= tools.size
|
|
81
|
-
<
|
|
83
|
+
<details class="mcp-tools-panel newfile" data-controller="list-filter">
|
|
84
|
+
<summary>Discovered tools <span class="count"><%= tools.size %></span></summary>
|
|
85
|
+
<div class="filter-bar">
|
|
86
|
+
<input type="search" class="filter-input" placeholder="Filter tools by name or description…" aria-label="Filter tools"
|
|
87
|
+
data-list-filter-target="query" data-action="input->list-filter#filter keydown->list-filter#clear">
|
|
88
|
+
<span class="filter-count mono muted" data-list-filter-target="count"></span>
|
|
89
|
+
</div>
|
|
90
|
+
<ul class="mcp-tools-list">
|
|
82
91
|
<% tools.each do |t| %>
|
|
83
|
-
<li
|
|
92
|
+
<li data-list-filter-target="item" data-filter-text="<%= t["name"] %> <%= t["description"] %>">
|
|
93
|
+
<details class="mcp-tool-row">
|
|
94
|
+
<summary>
|
|
95
|
+
<code class="mono"><%= t["name"] %></code>
|
|
96
|
+
<span class="mcp-tool-desc"><%= presence(t["description"]) || "no description" %></span>
|
|
97
|
+
</summary>
|
|
98
|
+
<p class="mcp-tool-full"><%= presence(t["description"]) || "No description." %></p>
|
|
99
|
+
</details>
|
|
100
|
+
</li>
|
|
84
101
|
<% end %>
|
|
85
102
|
</ul>
|
|
103
|
+
<div class="empty" data-list-filter-target="empty" hidden>No tools match your filter.</div>
|
|
86
104
|
</details>
|
|
87
105
|
<% end %>
|
|
88
106
|
|
|
89
|
-
<
|
|
107
|
+
<div class="section-head"><h2>Connection</h2><p class="muted">Save changes before testing the connection. Test connection uses the saved settings.</p></div>
|
|
108
|
+
<form class="mcp-edit" method="post" action="/studio/mcp" data-controller="transport-fields dirty-guard">
|
|
90
109
|
<%== csrf_tag %>
|
|
91
110
|
<input type="hidden" name="name" value="<%= m["name"] %>">
|
|
92
111
|
<div class="grid-2">
|
|
@@ -114,12 +133,16 @@
|
|
|
114
133
|
<label>description
|
|
115
134
|
<input type="text" name="description" value="<%= m["description"] %>">
|
|
116
135
|
</label>
|
|
136
|
+
<fieldset class="subgroup">
|
|
137
|
+
<legend>Credentials & environment</legend>
|
|
138
|
+
<p class="muted">Saved credentials are masked. Keep masked values to preserve them; clear a line to remove it.</p>
|
|
117
139
|
<label data-transport-fields-target="stdio">env <span class="muted">(stdio process env, one <code>KEY=value</code> per line)</span>
|
|
118
140
|
<textarea name="env" rows="4"><%= env_lines(m["env"]) %></textarea>
|
|
119
141
|
</label>
|
|
120
142
|
<label data-transport-fields-target="http">headers <span class="muted">(http/sse request headers, one <code>Name=value</code> per line)</span>
|
|
121
143
|
<textarea name="headers" rows="4"><%= env_lines(m["headers"]) %></textarea>
|
|
122
144
|
</label>
|
|
145
|
+
</fieldset>
|
|
123
146
|
<div class="actions">
|
|
124
147
|
<button type="submit" class="primary" data-turbo-submits-with="Saving…">Save instance</button>
|
|
125
148
|
</div>
|
|
@@ -128,12 +151,12 @@
|
|
|
128
151
|
<% else %>
|
|
129
152
|
<div class="drill-pane-head">
|
|
130
153
|
<div class="title">
|
|
131
|
-
<strong>
|
|
132
|
-
<span class="count">
|
|
154
|
+
<strong>Connect an MCP server</strong>
|
|
155
|
+
<span class="count">Create an instance or import an existing configuration</span>
|
|
133
156
|
</div>
|
|
134
157
|
</div>
|
|
135
158
|
<div class="drill-pane-body">
|
|
136
|
-
<form method="post" action="/studio/mcp" data-controller="transport-fields" data-turbo-frame="_top">
|
|
159
|
+
<form method="post" action="/studio/mcp" data-controller="transport-fields dirty-guard" data-turbo-frame="_top">
|
|
137
160
|
<%== csrf_tag %>
|
|
138
161
|
<div class="grid-2">
|
|
139
162
|
<label>name <span class="muted">(slug)</span>
|
|
@@ -161,22 +184,29 @@
|
|
|
161
184
|
<label>description
|
|
162
185
|
<input type="text" name="description">
|
|
163
186
|
</label>
|
|
187
|
+
<fieldset class="subgroup">
|
|
188
|
+
<legend>Credentials & environment</legend>
|
|
189
|
+
<p class="muted">Saved credentials are masked. Keep masked values to preserve them; clear a line to remove it.</p>
|
|
164
190
|
<label data-transport-fields-target="stdio">env <span class="muted">(stdio process env, one <code>KEY=value</code> per line)</span>
|
|
165
191
|
<textarea name="env" rows="4" placeholder="TAVILY_API_KEY=tvly-…"></textarea>
|
|
166
192
|
</label>
|
|
167
193
|
<label data-transport-fields-target="http">headers <span class="muted">(http/sse request headers, one <code>Name=value</code> per line)</span>
|
|
168
194
|
<textarea name="headers" rows="4" placeholder="Authorization=Bearer tvly-…"></textarea>
|
|
169
195
|
</label>
|
|
196
|
+
</fieldset>
|
|
170
197
|
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Creating…">Create instance</button></div>
|
|
171
198
|
</form>
|
|
172
199
|
|
|
173
|
-
<
|
|
200
|
+
<details class="newfile">
|
|
201
|
+
<summary>Import an existing configuration</summary>
|
|
202
|
+
<form method="post" action="/studio/mcp/import" data-turbo-frame="_top" data-controller="dirty-guard">
|
|
174
203
|
<%== csrf_tag %>
|
|
175
204
|
<label>mcpServers JSON <span class="muted">(paste a <code>{"mcpServers": {...}}</code> document — the same format Claude Desktop/Cursor use)</span>
|
|
176
205
|
<textarea name="json" rows="8" placeholder='{"mcpServers": {"tavily": {"url": "https://mcp.tavily.com/mcp", "headers": {"Authorization": "Bearer …"}}}}'></textarea>
|
|
177
206
|
</label>
|
|
178
207
|
<div class="actions"><button type="submit" class="primary" data-turbo-submits-with="Importing…">Import</button></div>
|
|
179
208
|
</form>
|
|
209
|
+
</details>
|
|
180
210
|
</div>
|
|
181
211
|
<% end %>
|
|
182
212
|
</turbo-frame>
|