From 0329c74ecb9a96fc2cbd8f1fc701300d26a09ac4 Mon Sep 17 00:00:00 2001 From: cixuuz <26782336+cixuuz@users.noreply.github.com> Date: Sat, 24 Oct 2026 06:55:31 +0000 Subject: [PATCH] feat(customize): register Knowledge's Sources view and Add Source with find_ui --- src/kiro_crew/docs/ui-index.generated.json | 34 +++++++++++-- website/src/pages/knowledge/SourcesList.tsx | 3 +- website/src/pages/knowledge/index.tsx | 29 ++++++++--- website/src/test/KnowledgeEmbedded.test.tsx | 17 ++++++- website/src/uiLocations/areas/capabilities.ts | 22 +++++++++ website/src/uiLocations/guidePlans.gen.ts | 48 ++++++++++++++++++- ...e-artifacts-capabilities.locations.test.ts | 21 +++++++- 7 files changed, 160 insertions(+), 14 deletions(-) diff --git a/src/kiro_crew/docs/ui-index.generated.json b/src/kiro_crew/docs/ui-index.generated.json index 5b717a9b672..8dd9c7126e5 100644 --- a/src/kiro_crew/docs/ui-index.generated.json +++ b/src/kiro_crew/docs/ui-index.generated.json @@ -1,14 +1,14 @@ { "$comment": "AUTO-GENERATED by website/scripts/gen-ui-index.mjs from the dashboard source — DO NOT EDIT. Regenerate with `npm run gen:ui`; read by the find_ui tool (kiro_crew/mcp_guide.py).", "schema_version": 1, -"input_digest": "sha256:d8f1d09843c57523713a079e5cdd541ea4e70c3ee919a7eafd9e69018ba84be3", -"build_digest": "sha256:69676fa761dfeffc29cdeb0343091ee8fb923dd6093e1f2c353bf74525ae360d", -"coverage": {"scope":"pages, page tabs, Settings tabs and sub-pages, every Settings control, registered controls; not every dashboard control","counts":{"button":85,"list":6,"field":3,"tab":86,"menu-item":29,"toggle":10,"disclosure":1,"page":12,"setting":265,"link":1},"tiers":{"generated":356,"curated":142},"registered_controls":142}, +"input_digest": "sha256:dd68dfcf5c5b1216ee9007dabef8caa209c4c873e0f23a1eb4340a136f85bc66", +"build_digest": "sha256:fa1255c6a4b6df380952c43f4d144816d226ee8e1ffd30b0baef063378d81b20", +"coverage": {"scope":"pages, page tabs, Settings tabs and sub-pages, every Settings control, registered controls; not every dashboard control","counts":{"button":86,"list":6,"field":3,"tab":87,"menu-item":29,"toggle":10,"disclosure":1,"page":12,"setting":265,"link":1},"tiers":{"generated":356,"curated":144},"registered_controls":144}, "locales": ["en","zh-CN","hi","es","fr","bn","pt","ru","de","ja","ko","it"], "surfaces": ["apps","apps-library","artifacts","capabilities","chat","logs","members","notifications","projects","schedule","settings","shell","webhooks"], "conditions": {"has_open_sessions":"at least one open session is listed","app_enabled":"the app that provides this page is installed and enabled","full_dashboard":"the full dashboard is open, not an embedded chat or sessions view","no_active_session":"no session is open in the chat pane","no_schedules":"no scheduled job exists yet","has_schedules":"at least one scheduled job exists","search_bar_unclaimed":"no enabled app has claimed the top-bar search (an app that does turns it into its own launcher)","session_open":"a session is open in the chat pane","no_response_running":"the open session is not answering right now (the control is disabled while a response runs)","empty_session":"the open session has no messages yet","not_on_sessions_page":"the open page is not Sessions (on a phone, Sessions has its own drawer instead)","has_closed_sessions":"at least one closed session is in the history","older_sessions_collapsed":"the Older Sessions list is collapsed","sessions_board_view":"the sessions list is in Board view, with sessions in columns (switch it from the sessions ⋯ menu)","sessions_list_view":"the sessions list is in List view (the default)","pointer_on_session_row":"the pointer or keyboard focus is on a session row of this machine that is not being renamed (the control is drawn on that row)","terminal_enabled":"the terminal is turned on in Settings","phone_connect_available":"pairing a phone is available on this machine","kiro_account_entry":"the menu offers a Kiro account row (always when this installation runs on Kiro; with another backend, only once an account usage reading has arrived)","has_notifications":"at least one notification is listed","has_unread_notifications":"at least one notification is unread","notification_selected":"a notification is open in the detail panel (select it in the list)","notification_read":"the open notification is already read (opening one marks it read; an unread one shows Mark read here instead)","crewmate_selected":"a crewmate is selected in the list","no_crewmates":"no crewmate exists yet","has_crewmates":"at least one crewmate exists","crewmate_editor_open":"a crewmate's editor is open (choose Edit on that crewmate)","has_installed_apps":"at least one app is installed","app_details_open":"an app's detail page is open (in Discover, click the app's card)","app_tile_menu_open":"the app card's ⋯ (More actions) menu is open (in Library, point at the app's card to show ⋯, then click it; clicking the card itself opens the app instead)","job_open":"a scheduled job's detail panel is open (select the job in the list)","job_running":"the open job is running now","job_secret_request_pending":"the open job is waiting for a secret approval","job_details_tab":"the job panel's Details tab is selected (the default; the Logs tab hides this)","schedule_list_view":"the Schedule page shows its List view (the default; the Calendar and Executions views hide this)","jobs_checked":"one or more jobs are checked in the list","one_job_checked":"exactly one job is checked in the list (tick that job's own box)","skill_selected":"a skill is selected in the list","message_typed":"something is ready to send: text in the message box, an attached file, or a referenced session","board_missing_state_lanes":"one of the Board view’s default status columns was removed (this appears only then)","response_running":"the open session is answering right now","message_box_empty":"nothing is waiting to send: no text in the message box and no attached file (with either, this place holds Queue or Steer instead)","mouse_input":"the device is used with a mouse or trackpad, not a touchscreen","touch_input":"the device is used by touch (a tablet or touchscreen), so the message box shows its touch controls","goal_loop_running":"a goal loop is running in the open session (its panel then offers Pause)","monitor_running":"a monitor is watching for the open session and has not finished (its panel then offers Stop monitor)","screen_capture_available":"taking a screenshot is supported here (the desktop app, or a Mac)","context_usage_reported":"the open session has reported how full its context is (after its first answer)","new_channel_prompt":"the first notification from a new channel is asking whether to keep or mute that channel","crewmate_danger_zone_open":"the crewmate editor shows its Danger zone section (choose Danger zone in the editor's section list)","crewmate_panel_not_docked":"the crewmate's details panel is not already docked open beside the chat","open_app_not_installed":"the open app is not installed yet and can be installed from the dashboard","open_app_enabled":"the open app is a regular installed app (not a built-in or independently managed one) and is turned on","open_app_syncable":"the open app is a regular installed app with no newer version waiting; Sync reloads its installed files and does not install a new release","open_app_update_available":"the open app is a regular installed app and a newer version is waiting (Update stands where Sync otherwise is)","open_app_removable":"the open app is a regular installed app (not a built-in or independently managed one) that is not locked against removal","job_not_running":"the open job is not running now (while it runs, Cancel Run stands in this place)","nav_rail_expanded":"the navigation rail is expanded, showing labels","cloud_deploy_available":"this installation can publish to a public cloud URL (the built-in AWS destination is offered)","voice_input_supported":"this browser can record from a microphone (the mic button is not drawn otherwise)","job_enabled":"the open job is active (not paused)","job_paused":"the open job is paused","crewmate_place_pane_open":"the crewmate editor shows its Memory section (choose it in the editor's section list)","crewmate_model_pane_open":"the crewmate editor shows its General section, which holds the model (choose it in the editor's section list)","artifact_open":"an artifact's own page is open (click the artifact in the library)","terminal_not_popped_out":"the terminal is docked in the dashboard, not popped out to its own window","terminal_docked_bottom":"the terminal panel sits below the chat (the default)","terminal_docked_right":"the terminal panel sits to the right of the chat, side by side with it"}, "reveal_states": {"sessions_sidebar_collapsed":"the sessions sidebar is collapsed","sessions_drawer_closed":"the sessions drawer is closed","nav_rail_collapsed":"the navigation rail is collapsed to icons","composer_collapsed":"the message box is collapsed to a single bar (the choice is remembered after a reload)","side_panel_closed":"the chat's side panel is closed","terminal_panel_closed":"the terminal panel is closed (the Terminal row in the navigation rail opens it)","crewmate_roster_folded":"a crewmate chat is open on a wide screen, which folds the roster column away","crewmate_chat_open_phone":"a crewmate chat fills the phone screen, so the roster is behind it","crewmate_profile_closed":"the crewmate's profile card is closed (pressing the crewmate's name in the chat header opens it)","crewmate_profile_tab_closed":"the crewmate's profile card is on another tab (the card opens on Sessions; its Profile tab is the last one on the rail)","crewmate_settings_closed":"the crewmate's Settings page is closed (the Settings row on its profile card's Profile tab opens it)"}, -"reveal_scopes": {"chat.sessions-drawer":{"kind":"state","revealer":"chat.mobile-sessions-toggle","state":"sessions_drawer_closed"},"chat.sessions-sidebar":{"kind":"state","revealer":"chat.sessions-sidebar-toggle","state":"sessions_sidebar_collapsed"},"chat.side-panel":{"kind":"state","revealer":"chat.side-panel-open","state":"side_panel_closed"},"composer.box":{"kind":"state","revealer":"composer.expand","state":"composer_collapsed"},"members.profile":{"kind":"state","revealer":"members.edit","state":"crewmate_profile_closed"},"members.profile-tab":{"kind":"state","revealer":"members.profile-tab","state":"crewmate_profile_tab_closed"},"members.roster":{"kind":"state","revealer":"members.switcher.show-roster","state":"crewmate_roster_folded"},"members.roster-phone":{"kind":"state","revealer":"members.back","state":"crewmate_chat_open_phone"},"members.settings":{"kind":"state","revealer":"members.settings","state":"crewmate_settings_closed"},"menu:apps.sources":{"kind":"menu","revealer":"apps.sources"},"menu:artifacts.add-menu":{"kind":"menu","revealer":"artifacts.add-menu"},"menu:artifacts.detail.more":{"kind":"menu","revealer":"artifacts.detail.more"},"menu:chat.side-panel.add":{"kind":"menu","revealer":"chat.side-panel.add"},"menu:composer.add-menu":{"kind":"menu","revealer":"composer.add-menu"},"menu:members.add-menu":{"kind":"menu","revealer":"members.add-menu"},"menu:members.switcher":{"kind":"menu","revealer":"members.switcher"},"menu:sessions.create-menu":{"kind":"menu","revealer":"sessions.create-menu"},"menu:sessions.list-menu":{"kind":"menu","revealer":"sessions.list-menu"},"menu:sessions.row-menu":{"kind":"menu","revealer":"sessions.row-menu"},"menu:shell.mobile-menu":{"kind":"menu","revealer":"shell.mobile-menu"},"menu:shell.notifications":{"kind":"menu","revealer":"shell.notifications"},"menu:shell.terminal-more":{"kind":"menu","revealer":"shell.terminal-more"},"open:apps.library.tile-details":{"kind":"open","revealer":"apps.library.tile-details"},"open:chat.older-sessions":{"kind":"open","revealer":"chat.older-sessions"},"open:composer.automation":{"kind":"open","revealer":"composer.automation"},"open:connections.mcp-servers-tab":{"kind":"open","revealer":"connections.mcp-servers-tab"},"shell.nav-rail":{"kind":"state","revealer":"shell.nav-toggle","state":"nav_rail_collapsed"},"shell.terminal-panel":{"kind":"state","revealer":"shell.terminal","state":"terminal_panel_closed"},"tab:agents.section-danger":{"kind":"tab","revealer":"agents.section-danger"},"tab:agents.section-model":{"kind":"tab","revealer":"agents.section-model"}}, +"reveal_scopes": {"chat.sessions-drawer":{"kind":"state","revealer":"chat.mobile-sessions-toggle","state":"sessions_drawer_closed"},"chat.sessions-sidebar":{"kind":"state","revealer":"chat.sessions-sidebar-toggle","state":"sessions_sidebar_collapsed"},"chat.side-panel":{"kind":"state","revealer":"chat.side-panel-open","state":"side_panel_closed"},"composer.box":{"kind":"state","revealer":"composer.expand","state":"composer_collapsed"},"members.profile":{"kind":"state","revealer":"members.edit","state":"crewmate_profile_closed"},"members.profile-tab":{"kind":"state","revealer":"members.profile-tab","state":"crewmate_profile_tab_closed"},"members.roster":{"kind":"state","revealer":"members.switcher.show-roster","state":"crewmate_roster_folded"},"members.roster-phone":{"kind":"state","revealer":"members.back","state":"crewmate_chat_open_phone"},"members.settings":{"kind":"state","revealer":"members.settings","state":"crewmate_settings_closed"},"menu:apps.sources":{"kind":"menu","revealer":"apps.sources"},"menu:artifacts.add-menu":{"kind":"menu","revealer":"artifacts.add-menu"},"menu:artifacts.detail.more":{"kind":"menu","revealer":"artifacts.detail.more"},"menu:chat.side-panel.add":{"kind":"menu","revealer":"chat.side-panel.add"},"menu:composer.add-menu":{"kind":"menu","revealer":"composer.add-menu"},"menu:members.add-menu":{"kind":"menu","revealer":"members.add-menu"},"menu:members.switcher":{"kind":"menu","revealer":"members.switcher"},"menu:sessions.create-menu":{"kind":"menu","revealer":"sessions.create-menu"},"menu:sessions.list-menu":{"kind":"menu","revealer":"sessions.list-menu"},"menu:sessions.row-menu":{"kind":"menu","revealer":"sessions.row-menu"},"menu:shell.mobile-menu":{"kind":"menu","revealer":"shell.mobile-menu"},"menu:shell.notifications":{"kind":"menu","revealer":"shell.notifications"},"menu:shell.terminal-more":{"kind":"menu","revealer":"shell.terminal-more"},"open:apps.library.tile-details":{"kind":"open","revealer":"apps.library.tile-details"},"open:chat.older-sessions":{"kind":"open","revealer":"chat.older-sessions"},"open:composer.automation":{"kind":"open","revealer":"composer.automation"},"open:connections.mcp-servers-tab":{"kind":"open","revealer":"connections.mcp-servers-tab"},"open:knowledge.sources-tab":{"kind":"open","revealer":"knowledge.sources-tab"},"shell.nav-rail":{"kind":"state","revealer":"shell.nav-toggle","state":"nav_rail_collapsed"},"shell.terminal-panel":{"kind":"state","revealer":"shell.terminal","state":"terminal_panel_closed"},"tab:agents.section-danger":{"kind":"tab","revealer":"agents.section-danger"},"tab:agents.section-model":{"kind":"tab","revealer":"agents.section-model"}}, "runtime_predicates": ["crewmate_danger_zone_open","crewmate_model_pane_open","crewmate_place_pane_open","full_dashboard","goal_loop_running","has_crewmates","has_open_sessions","has_schedules","monitor_running","mouse_input","no_response_running","not_on_sessions_page","phone_connect_available","schedule_list_view"], "guide_selections": {"app_tile_menu_open":{"picker":"apps.library.app-list","entity":"app"},"artifact_open":{"picker":"artifacts.list","entity":"artifact"},"crewmate_editor_open":{"picker":"agents.crew-list","entity":"crewmate"},"crewmate_selected":{"picker":"members.roster-list","entity":"crewmate"},"job_open":{"picker":"schedule.job-list","entity":"job"},"one_job_checked":{"picker":"schedule.job-list","entity":"tickjob"},"session_open":{"picker":"sessions.list","entity":"session"}}, "guide_gates": {"preview_flag:mc-preview-artifact-deploy":{"setting_id":"developer.artifact-deploy"},"preview_flag:mc-preview-session-environments":{"setting_id":"developer.session-environments"},"preview_flag:mc-preview-webhooks":{"setting_id":"developer.webhooks"},"terminal_enabled":{"setting_id":null}}, @@ -75,6 +75,8 @@ {"id":"composer.stop","kind":"button","label_key":"components.chatInput.stop_generation","placements":[{"surface_id":"chat","route":"/chat","parent_ids":["page.chat"],"entry_kind":"toolbar","requires":[{"kind":"condition","id":"session_open"},{"kind":"shown_by","location":"composer.expand","when":"composer_collapsed"},{"kind":"condition","id":"response_running"},{"kind":"condition","id":"message_box_empty"}]}],"guide_policy":"point","terms":{"en":["stop the answer","stop the response","stop responding","cancel the response","interrupt the agent","stop the agent","stop it talking","stop it from talking","stop the reply","stop generating","stop it while it is answering","stop the agent while it is answering"],"zh-CN":["停止回答","停止响应","打断回答","中止回答","停止回复","让它别说了"]},"tier":"curated"}, {"id":"composer.voice","kind":"button","label_key":"uiLocations.description.composer_voice_input","label_kind":"description","placements":[{"surface_id":"chat","route":"/chat","parent_ids":["page.chat"],"entry_kind":"toolbar","requires":[{"kind":"condition","id":"session_open"},{"kind":"shown_by","location":"composer.expand","when":"composer_collapsed"},{"kind":"condition","id":"voice_input_supported"}]}],"guide_policy":"point","terms":{"en":["voice input","dictate a message","dictation","talk instead of typing","speak my message","microphone","use my voice","speech to text","voice typing"],"zh-CN":["语音输入","用语音说","麦克风","说话输入","语音转文字","不想打字","语音按钮"],"es":["entrada de voz","dictar un mensaje"],"fr":["saisie vocale","dicter un message"],"pt":["entrada de voz","ditar uma mensagem"],"ru":["голосовой ввод","надиктовать сообщение"],"de":["Spracheingabe","Nachricht diktieren"],"ja":["音声入力","マイク"],"ko":["음성 입력","마이크"],"it":["input vocale","dettare un messaggio"]},"tier":"curated"}, {"id":"connections.mcp-servers-tab","kind":"tab","label_key":"pages.connectionsPage.mcp_servers","placements":[{"surface_id":"capabilities","route":"/capabilities?tab=mcp","parent_ids":["page.capabilities","tab.capabilities.mcp"],"entry_kind":"tab","requires":[]}],"guide_plan":{"version":2,"label_key":"pages.connectionsPage.mcp_servers","placements":[{"id":"any","route":"/capabilities?tab=mcp","steps":[{"id":"any:connections.mcp-servers-tab","location":"connections.mcp-servers-tab","label_key":"pages.connectionsPage.mcp_servers"}]}]},"guide_policy":"point","terms":{"en":["mcp","mcp list","tool servers","my mcp servers"],"zh-CN":["MCP 列表","工具服务器"]},"tier":"curated"}, +{"id":"knowledge.add-source","kind":"button","label_key":"pages.knowledge.sourcesList.add_source","placements":[{"surface_id":"capabilities","route":"/capabilities?tab=knowledge","parent_ids":["page.capabilities","tab.capabilities.knowledge","knowledge.sources-tab"],"entry_kind":"toolbar","requires":[]}],"guide_plan":{"version":2,"label_key":"pages.knowledge.sourcesList.add_source","placements":[{"id":"any","route":"/capabilities?tab=knowledge","steps":[{"id":"any:knowledge.sources-tab","location":"knowledge.sources-tab","label_key":"pages.knowledge.index.sources_tab","scope":"open:knowledge.sources-tab"},{"id":"any:knowledge.add-source","location":"knowledge.add-source","label_key":"pages.knowledge.sourcesList.add_source"}]}]},"guide_policy":"point","terms":{"en":["add a folder to the knowledge base","add files to knowledge","upload a document to knowledge","index a folder","watch a folder","add knowledge","add to knowledge base"],"zh-CN":["添加文件夹到知识库","上传文档到知识库","索引文件夹","添加知识"]},"tier":"curated"}, +{"id":"knowledge.sources-tab","kind":"tab","label_key":"pages.knowledge.index.sources_tab","placements":[{"surface_id":"capabilities","route":"/capabilities?tab=knowledge","parent_ids":["page.capabilities","tab.capabilities.knowledge"],"entry_kind":"tab","requires":[]}],"guide_plan":{"version":2,"label_key":"pages.knowledge.index.sources_tab","placements":[{"id":"any","route":"/capabilities?tab=knowledge","steps":[{"id":"any:knowledge.sources-tab","location":"knowledge.sources-tab","label_key":"pages.knowledge.index.sources_tab"}]}]},"guide_policy":"point","terms":{"en":["knowledge sources","my knowledge folders","indexed folders","watched folders"],"zh-CN":["知识来源","知识库文件夹","已索引的文件夹"]},"tier":"curated"}, {"id":"mcp.add-custom","kind":"button","label_key":"pages.overview.mcpTab.add_custom","placements":[{"surface_id":"capabilities","route":"/capabilities?tab=mcp","parent_ids":["page.capabilities","tab.capabilities.mcp","connections.mcp-servers-tab"],"entry_kind":"toolbar","requires":[]}],"guide_ref":{"action_id":"mcp.open_add"},"guide_policy":"point","terms":{"en":["add mcp","add an mcp server","add mcp server","new mcp server","connect an mcp server","custom mcp server","add mcp manually","mcp json","paste mcp config","my own mcp server","mcp server with an environment variable","mcp server with environment variables","mcp environment variables","mcp env vars"],"zh-CN":["添加 MCP","添加 MCP 服务器","新增 MCP","接入 MCP","新建 MCP 服务器","自定义 MCP 服务器","手动添加 MCP","MCP 配置","MCP 环境变量","带环境变量的 MCP 服务器"]},"tier":"curated"}, {"id":"mcp.add-server","kind":"button","label_key":"pages.overview.mcpTab.add_server","placements":[{"surface_id":"capabilities","route":"/capabilities?tab=mcp","parent_ids":["page.capabilities","tab.capabilities.mcp","connections.mcp-servers-tab"],"entry_kind":"toolbar","requires":[]}],"guide_plan":{"version":2,"label_key":"pages.overview.mcpTab.add_server","placements":[{"id":"any","route":"/capabilities?tab=mcp","steps":[{"id":"any:connections.mcp-servers-tab","location":"connections.mcp-servers-tab","label_key":"pages.connectionsPage.mcp_servers","scope":"open:connections.mcp-servers-tab"},{"id":"any:mcp.add-server","location":"mcp.add-server","label_key":"pages.overview.mcpTab.add_server"}]}]},"guide_policy":"point","terms":{"en":["add mcp","add an mcp server","add mcp server","new mcp server","connect an mcp server","install an mcp server","browse mcp servers","mcp store","mcp catalog","find mcp servers"],"zh-CN":["添加 MCP","添加 MCP 服务器","新增 MCP","接入 MCP","新建 MCP 服务器","安装 MCP","安装 MCP 服务器","MCP 市场","浏览 MCP 服务器"]},"tier":"curated"}, {"id":"mcp.probe","kind":"button","label_key":"pages.overview.mcpTab.probe_mcp_servers","placements":[{"surface_id":"capabilities","route":"/capabilities?tab=mcp","parent_ids":["page.capabilities","tab.capabilities.mcp","connections.mcp-servers-tab"],"entry_kind":"toolbar","requires":[]}],"guide_plan":{"version":2,"label_key":"pages.overview.mcpTab.probe_mcp_servers","placements":[{"id":"any","route":"/capabilities?tab=mcp","steps":[{"id":"any:connections.mcp-servers-tab","location":"connections.mcp-servers-tab","label_key":"pages.connectionsPage.mcp_servers","scope":"open:connections.mcp-servers-tab"},{"id":"any:mcp.probe","location":"mcp.probe","label_key":"pages.overview.mcpTab.probe_mcp_servers"}]}]},"guide_policy":"point","terms":{"en":["test my mcp servers","check mcp servers","refresh mcp servers","reconnect mcp servers","are my mcp servers working"],"zh-CN":["检测 MCP 服务器","测试 MCP 服务器","刷新 MCP 服务器","检查 MCP 连接"]},"tier":"curated"}, @@ -690,6 +692,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Deleting a mate only unbinds it from new sessions. Its workspace, memory store and past transcripts are kept.", "pages.kiroCrewAgentsPage.edit_model": "Edit default model", "pages.kiroCrewAgentsPage.model": "Model", +"pages.knowledge.index.sources_tab": "Sources", +"pages.knowledge.sourcesList.add_source": "+ Add Source", "pages.libraryPage.installed_apps": "Installed apps", "pages.libraryPage.tile_details": "Details", "pages.membersPage.add_member": "New mate", @@ -1174,6 +1178,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "删除队友只会解除它与新会话的绑定。它的工作区、记忆存储和历史记录都会保留。", "pages.kiroCrewAgentsPage.edit_model": "编辑默认模型", "pages.kiroCrewAgentsPage.model": "模型", +"pages.knowledge.index.sources_tab": "来源", +"pages.knowledge.sourcesList.add_source": "+ 添加来源", "pages.libraryPage.installed_apps": "已安装的应用", "pages.libraryPage.tile_details": "详情", "pages.membersPage.add_member": "新建队友", @@ -1658,6 +1664,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "क्रूमेट हटाने से वह सिर्फ़ नए सत्रों से अलग हो जाता है। उसका वर्कस्पेस, मेमोरी स्टोर और पिछली बातचीत के रिकॉर्ड बने रहते हैं।", "pages.kiroCrewAgentsPage.edit_model": "डिफ़ॉल्ट मॉडल संपादित करो", "pages.kiroCrewAgentsPage.model": "मॉडल", +"pages.knowledge.index.sources_tab": "स्रोत", +"pages.knowledge.sourcesList.add_source": "+ स्रोत जोड़ें", "pages.libraryPage.installed_apps": "इंस्टॉल किए गए ऐप", "pages.libraryPage.tile_details": "विवरण", "pages.membersPage.add_member": "नया साथी", @@ -2142,6 +2150,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Eliminar un compañero solo lo desvincula de las sesiones nuevas. Se conservan su espacio de trabajo, su almacén de memoria y las transcripciones anteriores.", "pages.kiroCrewAgentsPage.edit_model": "Editar el modelo predeterminado", "pages.kiroCrewAgentsPage.model": "Modelo", +"pages.knowledge.index.sources_tab": "Fuentes", +"pages.knowledge.sourcesList.add_source": "+ Añadir fuente", "pages.libraryPage.installed_apps": "Apps instaladas", "pages.libraryPage.tile_details": "Detalles", "pages.membersPage.add_member": "Nuevo compañero", @@ -2626,6 +2636,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Supprimer un coéquipier le dissocie seulement des nouvelles sessions. Son espace de travail, son magasin de mémoire et les transcriptions passées sont conservés.", "pages.kiroCrewAgentsPage.edit_model": "Modifier le modèle par défaut", "pages.kiroCrewAgentsPage.model": "Modèle", +"pages.knowledge.index.sources_tab": "Sources", +"pages.knowledge.sourcesList.add_source": "+ Ajouter une source", "pages.libraryPage.installed_apps": "Apps installées", "pages.libraryPage.tile_details": "Détails", "pages.membersPage.add_member": "Nouveau coéquipier", @@ -3110,6 +3122,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "ক্রুমেট মুছে ফেললে সেটি কেবল নতুন সেশন থেকে আলাদা হয়ে যায়। এর ওয়ার্কস্পেস, মেমরি স্টোর ও আগের চ্যাটের রেকর্ড রাখা হয়।", "pages.kiroCrewAgentsPage.edit_model": "ডিফল্ট মডেল সম্পাদনা করো", "pages.kiroCrewAgentsPage.model": "মডেল", +"pages.knowledge.index.sources_tab": "উৎস", +"pages.knowledge.sourcesList.add_source": "+ সোর্স যোগ করুন", "pages.libraryPage.installed_apps": "ইনস্টল করা অ্যাপ", "pages.libraryPage.tile_details": "বিস্তারিত", "pages.membersPage.add_member": "নতুন সঙ্গী", @@ -3594,6 +3608,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Excluir um colega apenas o desvincula das novas sessões. O espaço de trabalho, o armazenamento de memória e as transcrições anteriores são mantidos.", "pages.kiroCrewAgentsPage.edit_model": "Editar modelo padrão", "pages.kiroCrewAgentsPage.model": "Modelo", +"pages.knowledge.index.sources_tab": "Fontes", +"pages.knowledge.sourcesList.add_source": "+ Adicionar fonte", "pages.libraryPage.installed_apps": "Apps instalados", "pages.libraryPage.tile_details": "Detalhes", "pages.membersPage.add_member": "Novo colega", @@ -4078,6 +4094,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Удаление товарища по команде лишь отвязывает его от новых сессий. Его рабочая область, хранилище памяти и прежние расшифровки сохраняются.", "pages.kiroCrewAgentsPage.edit_model": "Изменить модель по умолчанию", "pages.kiroCrewAgentsPage.model": "Модель", +"pages.knowledge.index.sources_tab": "Источники", +"pages.knowledge.sourcesList.add_source": "+ Добавить источник", "pages.libraryPage.installed_apps": "Установленные приложения", "pages.libraryPage.tile_details": "Подробности", "pages.membersPage.add_member": "Новый напарник", @@ -4562,6 +4580,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Das Löschen eines Crew-Kameraden hebt nur seine Bindung an neue Sitzungen auf. Sein Workspace, sein Erinnerungsspeicher und frühere Transkripte bleiben erhalten.", "pages.kiroCrewAgentsPage.edit_model": "Standardmodell bearbeiten", "pages.kiroCrewAgentsPage.model": "Modell", +"pages.knowledge.index.sources_tab": "Quellen", +"pages.knowledge.sourcesList.add_source": "+ Quelle hinzufügen", "pages.libraryPage.installed_apps": "Installierte Apps", "pages.libraryPage.tile_details": "Details", "pages.membersPage.add_member": "Neuer Crewmate", @@ -5046,6 +5066,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "クルーメイトを削除すると、新しいセッションから外れるだけです。ワークスペース、メモリストア、過去のトランスクリプトは保持されます。", "pages.kiroCrewAgentsPage.edit_model": "デフォルトモデルを編集", "pages.kiroCrewAgentsPage.model": "モデル", +"pages.knowledge.index.sources_tab": "ソース", +"pages.knowledge.sourcesList.add_source": "+ ソースを追加", "pages.libraryPage.installed_apps": "インストール済みのアプリ", "pages.libraryPage.tile_details": "詳細", "pages.membersPage.add_member": "新しいクルーメイト", @@ -5530,6 +5552,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "크루메이트를 삭제하면 새 세션에서 연결이 해제될 뿐입니다. 워크스페이스, 메모리 저장소, 지난 기록은 유지됩니다.", "pages.kiroCrewAgentsPage.edit_model": "기본 모델 편집", "pages.kiroCrewAgentsPage.model": "모델", +"pages.knowledge.index.sources_tab": "소스", +"pages.knowledge.sourcesList.add_source": "+ 소스 추가", "pages.libraryPage.installed_apps": "설치된 앱", "pages.libraryPage.tile_details": "세부 정보", "pages.membersPage.add_member": "새 크루메이트", @@ -6014,6 +6038,8 @@ "pages.kiroCrewAgentsPage.deleting_a_crew_unbinds_it_from_new_sessions_its": "Eliminare un compagno lo scollega soltanto dalle nuove sessioni. Il suo workspace, il suo archivio della memoria e le trascrizioni passate vengono conservati.", "pages.kiroCrewAgentsPage.edit_model": "Modifica il modello predefinito", "pages.kiroCrewAgentsPage.model": "Modello", +"pages.knowledge.index.sources_tab": "Origini", +"pages.knowledge.sourcesList.add_source": "+ Aggiungi origine", "pages.libraryPage.installed_apps": "App installate", "pages.libraryPage.tile_details": "Dettagli", "pages.membersPage.add_member": "Nuovo compagno", diff --git a/website/src/pages/knowledge/SourcesList.tsx b/website/src/pages/knowledge/SourcesList.tsx index f6a7892f9b3..fddacc561a4 100644 --- a/website/src/pages/knowledge/SourcesList.tsx +++ b/website/src/pages/knowledge/SourcesList.tsx @@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Upload, FolderSync, FolderOpen, X, RefreshCw, AlertCircle, CheckCircle, ChevronDown, ChevronRight, Pause, Play, Pencil, Check, Coins, Globe } from 'lucide-react' import { Badge, Btn, EmptyState, ContentSkeleton, Input } from '../../components/ui' import Clickable from '../../components/Clickable' +import { uiLocation } from '../../uiLocations/uiLocation' import { knowledgeApi } from './api' import { formatRelativeDate, FALLBACK_SUPPORTED_FORMATS } from './helpers' import { parseSourceProps, shouldShowWordCount, syncStatusLabel } from './knowledgeUtils' @@ -482,7 +483,7 @@ export default function SourcesList({ onIngest, uploadNamespace, setUploadNamesp // and a site added in a terminal shows up without a reload. queryClient.invalidateQueries({ queryKey: ['knowledge-config'] }) setShowAdd(true) - }} className="px-3 py-1.5 text-[13px] bg-accent text-accent-fg rounded-md hover:bg-accent/80">{i18nT('pages.knowledge.sourcesList.add_source')} + }} className="px-3 py-1.5 text-[13px] bg-accent text-accent-fg rounded-md hover:bg-accent/80" {...uiLocation('knowledge.add-source')}>{i18nT('pages.knowledge.sourcesList.add_source')} {showAdd && ( diff --git a/website/src/pages/knowledge/index.tsx b/website/src/pages/knowledge/index.tsx index 351f07a905c..1925da43af3 100644 --- a/website/src/pages/knowledge/index.tsx +++ b/website/src/pages/knowledge/index.tsx @@ -31,6 +31,8 @@ const STATUS_LABEL_KEY = { } as const import { i18nT } from '../../i18n/t' +import { uiLocation } from '../../uiLocations/uiLocation' +import { GuideRevealScope } from '../../guide/GuideRevealScope' import { useImeGuard } from '../../hooks/useImeGuard' import { editableEventTarget } from '../../utils/editableTarget' const KnowledgeGraph = lazy(() => import('./KnowledgeGraph')) @@ -564,12 +566,22 @@ export default function KnowledgePage({ embedded = false }: { embedded?: boolean {/* Tabs — horizontally scrollable on narrow viewports so the active underline never spills past the container. */}
- {TABS.map(t => ( - - ))} + {TABS.map(t => { + const onClick = () => { setTab(t); setSelectedId(null); setSelectedItems(new Set()) } + const className = `flex items-center gap-1.5 px-3 py-2 text-[13px] font-medium border-b-2 transition-all bg-transparent cursor-pointer shrink-0 whitespace-nowrap ${tab === t ? 'border-accent text-text font-semibold' : 'border-transparent text-muted hover:text-text'}` + // Sources is registered with find_ui (uiLocations/areas/capabilities.ts): + // a registered row needs its own branch, with its label key written + // out, so the index can read the label from this render site. + return t === 'sources' ? ( + + ) : ( + + ) + })} {embedded && ( setShowHelp(true)}> {i18nT('pages.knowledge.index.help')} @@ -579,6 +591,10 @@ export default function KnowledgePage({ embedded = false }: { embedded?: boolean
+ {/* Owns the find_ui reveal scope for the Sources view, outside the + conditional so it can report "closed" while SourcesList is + unmounted (same pattern as ConnectionsPage's MCP Servers tab). */} + {isEmpty && tab === 'list' ? ( <> {listFilterBar} @@ -688,6 +704,7 @@ export default function KnowledgePage({ embedded = false }: { embedded?: boolean ) : ( )} +
{/* Stats bar */} diff --git a/website/src/test/KnowledgeEmbedded.test.tsx b/website/src/test/KnowledgeEmbedded.test.tsx index 906e7596819..3a0f3ba0770 100644 --- a/website/src/test/KnowledgeEmbedded.test.tsx +++ b/website/src/test/KnowledgeEmbedded.test.tsx @@ -6,9 +6,10 @@ * strip. Standalone mode keeps the original chrome byte-for-byte. */ import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen } from '@testing-library/react' +import { render, screen, fireEvent } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' +import { scopeOpen } from '../guide/liveRegistry' const mockKnowledgeApi = vi.fn() vi.mock('../pages/knowledge/api', () => ({ @@ -56,3 +57,17 @@ describe('KnowledgePage — embedded mode', () => { expect(screen.getByRole('button', { name: /help/i })).toBeInTheDocument() }) }) + +describe('KnowledgePage — find_ui reveal scope for the Sources view', () => { + // The find_ui guide to Add Source has a step whose scope is + // `open:knowledge.sources-tab`. The page owns that scope, so the step can + // tell "Sources is open" from "Add Source has not loaded yet". + it('reports closed on the list view, open on Sources, and drops the report on unmount', () => { + const { unmount } = mount() + expect(scopeOpen('open:knowledge.sources-tab')).toBe(false) + fireEvent.click(screen.getByRole('button', { name: /^sources$/i })) + expect(scopeOpen('open:knowledge.sources-tab')).toBe(true) + unmount() + expect(scopeOpen('open:knowledge.sources-tab')).toBeNull() + }) +}) diff --git a/website/src/uiLocations/areas/capabilities.ts b/website/src/uiLocations/areas/capabilities.ts index f6cd7d79a30..fdd25165d5d 100644 --- a/website/src/uiLocations/areas/capabilities.ts +++ b/website/src/uiLocations/areas/capabilities.ts @@ -77,6 +77,28 @@ export const LOCATIONS = { }, placements: [{ surface: 'capabilities', parent: 'tab.capabilities.skills', entry: 'toolbar' }], }, + // Customize > Knowledge opens on its List view (local state, not the URL), so + // Add Source hangs under the Sources view tab: the path names the click that + // reveals it. + 'knowledge.sources-tab': { + kind: 'tab', + terms: { + en: ['knowledge sources', 'my knowledge folders', 'indexed folders', 'watched folders'], + 'zh-CN': ['知识来源', '知识库文件夹', '已索引的文件夹'], + }, + placements: [{ surface: 'capabilities', parent: 'tab.capabilities.knowledge', entry: 'tab' }], + }, + 'knowledge.add-source': { + kind: 'button', + terms: { + en: [ + 'add a folder to the knowledge base', 'add files to knowledge', 'upload a document to knowledge', + 'index a folder', 'watch a folder', 'add knowledge', 'add to knowledge base', + ], + 'zh-CN': ['添加文件夹到知识库', '上传文档到知识库', '索引文件夹', '添加知识'], + }, + placements: [{ surface: 'capabilities', parent: 'knowledge.sources-tab', entry: 'toolbar' }], + }, 'skills.refresh': { kind: 'button', label: { from: 'attr', attr: 'aria-label' }, diff --git a/website/src/uiLocations/guidePlans.gen.ts b/website/src/uiLocations/guidePlans.gen.ts index 1b490ec7326..e4a7813a476 100644 --- a/website/src/uiLocations/guidePlans.gen.ts +++ b/website/src/uiLocations/guidePlans.gen.ts @@ -3,7 +3,7 @@ import type { UiGuidePlan } from './types' /** The `build_digest` of the packaged index this bundle was built with (see `guideBuildDigest`). */ -export const GUIDE_BUILD_DIGEST = "sha256:69676fa761dfeffc29cdeb0343091ee8fb923dd6093e1f2c353bf74525ae360d" +export const GUIDE_BUILD_DIGEST = "sha256:fa1255c6a4b6df380952c43f4d144816d226ee8e1ffd30b0baef063378d81b20" /** Curated location ids: the only ids a live observation may name. */ export const GUIDE_OBSERVABLE_IDS: readonly string[] = [ @@ -69,6 +69,8 @@ export const GUIDE_OBSERVABLE_IDS: readonly string[] = [ "composer.stop", "composer.voice", "connections.mcp-servers-tab", + "knowledge.add-source", + "knowledge.sources-tab", "mcp.add-custom", "mcp.add-server", "mcp.probe", @@ -266,6 +268,10 @@ export const GUIDE_REVEAL_SCOPES = { "kind": "open", "revealer": "connections.mcp-servers-tab" }, + "open:knowledge.sources-tab": { + "kind": "open", + "revealer": "knowledge.sources-tab" + }, "shell.nav-rail": { "kind": "state", "revealer": "shell.nav-toggle", @@ -1700,6 +1706,46 @@ export const GUIDE_PLANS: Readonly> = { } ] }, + "knowledge.add-source": { + "version": 2, + "label_key": "pages.knowledge.sourcesList.add_source", + "placements": [ + { + "id": "any", + "route": "/capabilities?tab=knowledge", + "steps": [ + { + "id": "any:knowledge.sources-tab", + "location": "knowledge.sources-tab", + "label_key": "pages.knowledge.index.sources_tab", + "scope": "open:knowledge.sources-tab" + }, + { + "id": "any:knowledge.add-source", + "location": "knowledge.add-source", + "label_key": "pages.knowledge.sourcesList.add_source" + } + ] + } + ] + }, + "knowledge.sources-tab": { + "version": 2, + "label_key": "pages.knowledge.index.sources_tab", + "placements": [ + { + "id": "any", + "route": "/capabilities?tab=knowledge", + "steps": [ + { + "id": "any:knowledge.sources-tab", + "location": "knowledge.sources-tab", + "label_key": "pages.knowledge.index.sources_tab" + } + ] + } + ] + }, "mcp.add-server": { "version": 2, "label_key": "pages.overview.mcpTab.add_server", diff --git a/website/src/uiLocations/schedule-artifacts-capabilities.locations.test.ts b/website/src/uiLocations/schedule-artifacts-capabilities.locations.test.ts index 6ea40e2c7ae..459f087fdef 100644 --- a/website/src/uiLocations/schedule-artifacts-capabilities.locations.test.ts +++ b/website/src/uiLocations/schedule-artifacts-capabilities.locations.test.ts @@ -1,7 +1,8 @@ /** * Goldens for the wave-3 additions to the `schedule`, `artifacts` and * `capabilities` areas (Schedule's job controls, the Artifacts toolbar extras, - * Customize > Skills and the MCP probe): the exact parent chain, entry, route, + * Customize > Skills, the MCP probe and Customize > Knowledge's Sources view + * and Add Source): the exact parent chain, entry, route, * label key and requirements each location carries in the generated index, and * the one render site its marker sits on, in the committed index. */ @@ -22,6 +23,8 @@ const SCHEDULE_SRC = src('../pages/SchedulePage.tsx') const ARTIFACTS_SRC = readPageFamily('ArtifactsPage') const SKILLS_SRC = src('../pages/overview/SkillsTab.tsx') const MCP_SRC = src('../pages/overview/McpTab.tsx') +const KNOWLEDGE_SRC = src('../pages/knowledge/index.tsx') +const SOURCES_SRC = src('../pages/knowledge/SourcesList.tsx') const cond = (...ids: string[]): Req[] => ids.map(id => ({ kind: 'condition', id })) @@ -85,6 +88,22 @@ const EXPECTED: Record = { 'skills.create': { kind: 'button', labelKey: 'pages.overview.skillsTab.create_new_skill', source: SKILLS_SRC, placement: skills }, 'skills.add': { kind: 'button', labelKey: 'pages.overview.skillsTab.add_skill', source: SKILLS_SRC, placement: skills }, 'skills.refresh': { kind: 'button', labelKey: 'pages.overview.skillsTab.refresh_skills', source: SKILLS_SRC, placement: skills }, + // Customize > Knowledge opens on its List view, so Add Source hangs under + // the Sources view tab that reveals it. + 'knowledge.sources-tab': { + kind: 'tab', labelKey: 'pages.knowledge.index.sources_tab', source: KNOWLEDGE_SRC, + placement: { + surface: 'capabilities', route: '/capabilities?tab=knowledge', + parents: ['page.capabilities', 'tab.capabilities.knowledge'], entry: 'tab', requires: [], + }, + }, + 'knowledge.add-source': { + kind: 'button', labelKey: 'pages.knowledge.sourcesList.add_source', source: SOURCES_SRC, + placement: { + surface: 'capabilities', route: '/capabilities?tab=knowledge', + parents: ['page.capabilities', 'tab.capabilities.knowledge', 'knowledge.sources-tab'], entry: 'toolbar', requires: [], + }, + }, 'mcp.probe': { kind: 'button', labelKey: 'pages.overview.mcpTab.probe_mcp_servers', source: MCP_SRC, placement: {