feat: improve channel and setup guidance flows

This commit is contained in:
晴天
2026-04-02 00:36:32 +08:00
parent 2013ff98b9
commit 7b4640b355
7 changed files with 267 additions and 90 deletions

View File

@@ -49,6 +49,7 @@ export default {
channelsTitle: _('渠道绑定', 'Channel Bindings'),
channelsDesc: _('管理此 Agent 绑定的消息渠道', 'Manage message channels bound to this Agent.'),
addBinding: _('+ 添加绑定', '+ Add Binding'),
manageChannels: _('去消息渠道配置', 'Open Channels Page'),
noBindings: _('此 Agent 尚未绑定任何渠道', 'This Agent has no bindings yet.'),
removeBinding: _('解绑', 'Unbind'),
bindingRemoved: _('已解除绑定', 'Binding removed'),

View File

@@ -240,6 +240,13 @@ export default {
operations: _('操作', 'Operations'),
setup: _('接入', 'Setup'),
close: _('关闭', 'Close', '關閉'),
manualCommands: _('手动命令', 'Manual Commands', '手動命令'),
manualCommandsHint: _('如果面板自动安装失败,可以复制下面的命令到终端手动执行。', 'If panel-based installation fails, copy the commands below and run them manually in your terminal.', '如果面板自動安裝失敗,可以複製下面的命令到終端手動執行。'),
manualInstallCommand: _('手动安装命令', 'Manual Install Command', '手動安裝命令'),
manualInstallHint: _('用于手动安装 {platform} 对应插件。', 'Use this command to manually install the plugin for {platform}.', '用於手動安裝 {platform} 對應外掛。'),
manualLoginCommand: _('手动登录命令', 'Manual Login Command', '手動登入命令'),
manualLoginHint: _('插件安装完成后,可在终端执行此命令继续登录流程。', 'After plugin installation completes, run this command in your terminal to continue the login flow.', '外掛安裝完成後,可在終端執行此命令繼續登入流程。'),
copyCommandFailed: _('复制命令失败', 'Failed to copy command', '複製命令失敗'),
pluginStatusFailed: _('无法获取插件状态', 'Failed to get plugin status', '無法取得外掛狀態'),
pluginInstalled: _('已安装', 'Installed', '已安裝', 'プラグインインストール済み'),
version: _('版本', 'Version'),

View File

@@ -5,6 +5,7 @@ export default {
desc: _('安装和配置 OpenClaw', 'Install and configure OpenClaw', '安裝和設定 OpenClaw', 'OpenClaw のインストールと設定', 'OpenClaw 설치 및 설정', 'Cài đặt và cấu hình OpenClaw', 'Instalar y configurar OpenClaw', 'Instalar e configurar OpenClaw', 'Установка и настройка OpenClaw', 'Installer et configurer OpenClaw', 'OpenClaw installieren und konfigurieren'),
headerTitle: _('欢迎使用 ClawPanel', 'Welcome to ClawPanel', '', 'ClawPanel へようこそ', 'ClawPanel에 오신 것을 환영합니다', 'Chào mừng đến ClawPanel', 'Bienvenido a ClawPanel', 'Bem-vindo ao ClawPanel', 'Добро пожаловать в ClawPanel', 'Bienvenue sur ClawPanel', 'Willkommen bei ClawPanel'),
headerDesc: _('OpenClaw AI Agent 框架的桌面管理面板', 'Desktop management panel for OpenClaw AI Agent framework', '', 'OpenClaw AI Agent フレームワークのデスクトップ管理パネル', 'OpenClaw AI Agent 프레임워크의 데스크톱 관리 패널'),
officialWebsite: _('官网', 'Official Website', '官網', '公式サイト', '공식 웹사이트', 'Trang chính thức', 'Sitio oficial', 'Site oficial', 'Официальный сайт', 'Site officiel', 'Offizielle Website'),
recheck: _('重新检测', 'Re-detect', '重新檢測', '再検出', '재감지', 'Kiểm tra lại', 'Verificar de nuevo', 'Verificar novamente', 'Проверить снова', 'Revérifier', 'Erneut prüfen'),
stepNode: _('Node.js 环境', 'Node.js Environment', 'Node.js 環境', 'Node.js 環境', 'Node.js 환경', 'Môi trường Node.js', 'Entorno Node.js', 'Ambiente Node.js', 'Среда Node.js', 'Environnement Node.js', 'Node.js-Umgebung'),
installed: _('已安装', 'Installed', '已安裝', 'インストール済み', '설치됨', 'Đã cài đặt', 'Instalado', 'Instalado', 'Установлено', 'Installé', 'Installiert'),
@@ -149,6 +150,7 @@ export default {
restoreFailed: _('恢复失败: {err}', 'Restore failed: {err}', '恢復失敗: {err}'),
initializing: _('初始化中...', 'Initializing...'),
configCreated: _('配置文件已创建', 'Config file created', '設定檔案已建立'),
configRestored: _('已从备份恢复配置文件', 'Config restored from backup', '已從備份還原設定檔案'),
configExists: _('配置文件已存在', 'Config file already exists', '設定檔案已存在'),
initFailed: _('初始化失败: {err}', 'Init failed: {err}', '初始化失敗: {err}'),
scanning: _('扫描中...', 'Scanning...', '掃描中...'),

View File

@@ -7,12 +7,21 @@ import { toast } from '../components/toast.js'
import { showConfirm } from '../components/modal.js'
import { CHANNEL_LABELS } from '../lib/channel-labels.js'
import { t } from '../lib/i18n.js'
import { navigate } from '../router.js'
function esc(str) {
if (!str) return ''
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}
function openChannelsBindingPage(agentId) {
const params = new URLSearchParams()
params.set('tab', 'agents')
params.set('agent', agentId || 'main')
params.set('action', 'bind')
navigate(`/channels?${params.toString()}`)
}
export async function render() {
const params = new URLSearchParams(location.hash.split('?')[1] || '')
const agentId = params.get('id') || 'main'
@@ -568,10 +577,6 @@ async function openFileEditor(container, state, name, isNew = false) {
async function renderChannels(container, state) {
const bindings = state.detail?.bindings || []
// 获取已配置的渠道
let platforms = []
try { platforms = await api.listConfiguredPlatforms() } catch {}
container.innerHTML = `
<div class="agent-channels-section">
<div class="agent-section-header">
@@ -579,7 +584,7 @@ async function renderChannels(container, state) {
<h3 class="agent-section-title">${t('agentDetail.channelsTitle')}</h3>
<p class="agent-section-desc">${t('agentDetail.channelsDesc')}</p>
</div>
<button class="btn btn-sm btn-primary" id="btn-add-binding">${t('agentDetail.addBinding')}</button>
<button class="btn btn-sm btn-primary" id="btn-add-binding">${t('agentDetail.manageChannels')}</button>
</div>
<div id="agent-bindings-list"></div>
</div>
@@ -588,7 +593,7 @@ async function renderChannels(container, state) {
renderBindingsList(container, state, bindings)
container.querySelector('#btn-add-binding').addEventListener('click', () => {
showAddBindingDialog(container, state, platforms)
openChannelsBindingPage(state.agentId)
})
}
@@ -627,7 +632,6 @@ function renderBindingsList(container, state, bindings) {
try {
await api.deleteAgentBinding(state.agentId, channel, account, binding?.match || null)
toast(t('agentDetail.bindingRemoved'), 'success')
// 刷新
invalidate('get_agent_detail')
state.detail = await api.getAgentDetail(state.agentId)
renderBindingsList(container, state, state.detail.bindings || [])
@@ -636,62 +640,3 @@ function renderBindingsList(container, state, bindings) {
}
})
}
function showAddBindingDialog(container, state, platforms) {
const overlay = document.createElement('div')
overlay.className = 'modal-overlay'
// 构建渠道选项:已配置的渠道 + 所有已知渠道
const channels = new Set()
for (const p of platforms) {
if (p.platform || p.id) channels.add(p.platform || p.id)
}
// 确保常用渠道在列表中
for (const key of Object.keys(CHANNEL_LABELS)) channels.add(key)
const channelOptions = [...channels].map(ch =>
`<option value="${esc(ch)}">${esc(CHANNEL_LABELS[ch] || ch)}</option>`
).join('')
overlay.innerHTML = `
<div class="modal" style="max-width:400px">
<div class="modal-title">${t('agentDetail.addBinding')}</div>
<div class="form-group">
<label class="form-label">${t('agentDetail.selectChannel')}</label>
<select class="form-input" id="bind-channel">${channelOptions}</select>
</div>
<div class="form-group">
<label class="form-label">${t('agentDetail.accountOptional')}</label>
<input class="form-input" id="bind-account" placeholder="${t('agentDetail.accountOptionalPlaceholder')}">
</div>
<div class="modal-actions">
<button class="btn btn-secondary btn-sm" data-action="cancel">${t('common.cancel')}</button>
<button class="btn btn-primary btn-sm" data-action="confirm">${t('common.confirm')}</button>
</div>
</div>
`
document.body.appendChild(overlay)
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove() })
overlay.querySelector('[data-action="cancel"]').onclick = () => overlay.remove()
overlay.querySelector('[data-action="confirm"]').onclick = async () => {
const channel = overlay.querySelector('#bind-channel').value
const account = overlay.querySelector('#bind-account').value.trim() || null
if (!channel) return
try {
await api.saveAgentBinding(state.agentId, channel, account)
toast(t('agentDetail.bindingAdded'), 'success')
overlay.remove()
invalidate('get_agent_detail')
state.detail = await api.getAgentDetail(state.agentId)
renderBindingsList(container, state, state.detail.bindings || [])
} catch (e) {
toast(t('agentDetail.bindingFailed') + ': ' + e, 'error')
overlay.remove()
}
}
overlay.querySelector('[data-action="confirm"]').addEventListener('keydown', (e) => {
if (e.key === 'Enter') overlay.querySelector('[data-action="confirm"]').click()
if (e.key === 'Escape') overlay.remove()
})
}

View File

@@ -245,6 +245,26 @@ const PLATFORM_REGISTRY = {
},
}
function parseChannelsRouteIntent() {
const params = new URLSearchParams(location.hash.split('?')[1] || '')
return {
tab: params.get('tab') || '',
action: params.get('action') || '',
agentId: params.get('agent') || '',
}
}
function activateChannelsPageTab(page, key = 'channels') {
const activeKey = key === 'agents' ? 'agents' : 'channels'
page.querySelectorAll('#channels-page-tabs .tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.chTab === activeKey)
})
const listEl = page.querySelector('#channels-panel-list')
const agentsEl = page.querySelector('#channels-panel-agents')
if (listEl) listEl.style.display = activeKey === 'channels' ? '' : 'none'
if (agentsEl) agentsEl.style.display = activeKey === 'agents' ? '' : 'none'
}
// ── 页面生命周期 ──
export async function render() {
@@ -275,7 +295,14 @@ export async function render() {
bindChannelTabs(page)
const state = { configured: [], bindings: [], agents: [] }
const state = {
configured: [],
bindings: [],
agents: [],
routeIntent: parseChannelsRouteIntent(),
routeIntentConsumed: false,
routeIntentHintShown: false,
}
await loadPlatforms(page, state)
return page
@@ -284,12 +311,7 @@ export async function render() {
function bindChannelTabs(page) {
page.querySelectorAll('#channels-page-tabs .tab').forEach(tab => {
tab.addEventListener('click', () => {
const key = tab.dataset.chTab
page.querySelectorAll('#channels-page-tabs .tab').forEach(t => t.classList.toggle('active', t === tab))
const listEl = page.querySelector('#channels-panel-list')
const agentsEl = page.querySelector('#channels-panel-agents')
if (listEl) listEl.style.display = key === 'channels' ? '' : 'none'
if (agentsEl) agentsEl.style.display = key === 'agents' ? '' : 'none'
activateChannelsPageTab(page, tab.dataset.chTab)
})
})
}
@@ -321,6 +343,40 @@ async function loadPlatforms(page, state) {
renderConfigured(page, state)
renderAvailable(page, state)
renderAgentBindings(page, state)
applyRouteIntent(page, state)
}
function applyRouteIntent(page, state) {
const intent = state.routeIntent
if (!intent) return
const requestedTab = intent.action === 'bind'
? 'agents'
: (intent.tab === 'agents' ? 'agents' : intent.tab === 'channels' ? 'channels' : '')
if (requestedTab) activateChannelsPageTab(page, requestedTab)
if (state.routeIntentConsumed) return
if (intent.action === 'bind' && intent.agentId) {
const enabledConfigured = (state.configured || []).filter(p => p.enabled !== false)
if (!enabledConfigured.length) {
activateChannelsPageTab(page, 'channels')
if (!state.routeIntentHintShown) {
state.routeIntentHintShown = true
toast(t('channels.enableChannelFirst'), 'info')
}
return
}
state.routeIntentConsumed = true
const targetCard = Array.from(page.querySelectorAll('.agent-binding-card')).find(card => card.dataset.agentId === intent.agentId)
targetCard?.scrollIntoView?.({ block: 'center', behavior: 'smooth' })
setTimeout(() => openAddAgentBindingModal(intent.agentId, page, state), 0)
return
}
if (requestedTab) state.routeIntentConsumed = true
}
// ── 已配置平台渲染 ──
@@ -1037,6 +1093,102 @@ function openExternalUrl(href) {
import('@tauri-apps/plugin-shell').then(({ open }) => open(href)).catch(() => window.open(href, '_blank'))
}
function getManualCommandSpecs(pid, reg) {
if (pid === 'weixin') {
return [
{
id: 'install',
title: t('channels.manualInstallCommand'),
hint: t('channels.manualInstallHint', { platform: reg.label }),
command: 'npx -y @tencent-weixin/openclaw-weixin-cli@latest install',
},
{
id: 'login',
title: t('channels.manualLoginCommand'),
hint: t('channels.manualLoginHint'),
command: 'openclaw channels login --channel openclaw-weixin',
},
]
}
if (!['qqbot', 'feishu', 'dingtalk'].includes(pid) || !reg.pluginRequired) {
return []
}
return [{
id: 'install',
title: t('channels.manualInstallCommand'),
hint: t('channels.manualInstallHint', { platform: reg.label }),
command: `openclaw plugins install ${reg.pluginRequired}`,
}]
}
function buildManualCommandPanel(commandSpecs) {
if (!commandSpecs.length) return ''
return `
<div style="margin-top:var(--space-md);padding:12px 14px;background:var(--bg-tertiary);border-radius:var(--radius-md)">
<div style="font-weight:600;font-size:var(--font-size-sm);margin-bottom:6px">${t('channels.manualCommands')}</div>
<div style="font-size:var(--font-size-xs);color:var(--text-secondary);line-height:1.7;margin-bottom:10px">${t('channels.manualCommandsHint')}</div>
<div style="display:flex;flex-direction:column;gap:10px">
${commandSpecs.map(spec => `
<div style="background:var(--bg-secondary);border:1px solid var(--border-primary);border-radius:var(--radius-md);padding:10px 12px">
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px">
<div style="min-width:0;flex:1">
<div style="font-weight:600;font-size:var(--font-size-sm)">${spec.title}</div>
<div style="font-size:var(--font-size-xs);color:var(--text-secondary);line-height:1.6;margin-top:4px">${spec.hint}</div>
</div>
<button type="button" class="btn btn-xs btn-secondary" data-manual-copy="${escapeAttr(spec.id)}">${t('common.copy')}</button>
</div>
<pre style="margin:8px 0 0;font-family:var(--font-mono);font-size:11px;white-space:pre-wrap;word-break:break-all;color:var(--text-primary)">${escapeAttr(spec.command)}</pre>
</div>
`).join('')}
</div>
</div>
`
}
async function copyTextToClipboard(text) {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text)
return
}
const textarea = document.createElement('textarea')
textarea.value = text
textarea.setAttribute('readonly', '')
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.select()
const ok = document.execCommand('copy')
textarea.remove()
if (!ok) throw new Error('copy failed')
}
function bindManualCommandCopy(root, commandSpecs) {
if (!root || !commandSpecs.length) return
const commandMap = new Map(commandSpecs.map(spec => [spec.id, spec.command]))
root.querySelectorAll('[data-manual-copy]').forEach(btn => {
btn.addEventListener('click', async () => {
const command = commandMap.get(btn.dataset.manualCopy)
if (!command) return
const prev = btn.textContent
try {
await copyTextToClipboard(command)
btn.textContent = t('common.copied')
toast(t('common.copied'), 'success')
setTimeout(() => {
btn.textContent = prev
}, 1200)
} catch (e) {
toast(t('channels.copyCommandFailed') + ': ' + e, 'error')
}
})
})
}
/** QQ展示后端完整诊断凭证 + Gateway + 插件 + chatCompletions可选一键修复插件 */
function showQqDiagnoseModal(result, options = {}) {
const accountId = options.accountId != null ? options.accountId : null
@@ -1288,6 +1440,9 @@ async function openConfigDialog(pid, page, state, accountId) {
${t('channels.detectingPlugin')}
</div>` : ''
const manualCommandSpecs = getManualCommandSpecs(pid, reg)
const manualCommandHtml = buildManualCommandPanel(manualCommandSpecs)
const actionOnlyBtns = reg.actions?.length ? `
<div style="padding:12px 14px;background:var(--bg-tertiary);border-radius:var(--radius-md)">
<div style="font-weight:600;font-size:var(--font-size-sm);margin-bottom:8px">${t('channels.operations')}</div>
@@ -1300,12 +1455,13 @@ async function openConfigDialog(pid, page, state, accountId) {
const modal = showContentModal({
title: `${reg.label} ${t('channels.setup')}`,
content: actionOnlyGuide + pluginStatusHtml + actionOnlyBtns,
content: actionOnlyGuide + pluginStatusHtml + manualCommandHtml + actionOnlyBtns,
buttons: [
{ label: t('channels.close'), className: 'btn btn-secondary', id: 'btn-close' },
],
width: 560,
})
bindManualCommandCopy(modal, manualCommandSpecs)
modal.querySelector('#btn-close')?.addEventListener('click', () => modal.close?.() || modal.remove?.())
modal.addEventListener('click', (e) => {
const a = e.target.closest('a[href]')
@@ -1620,6 +1776,9 @@ async function openConfigDialog(pid, page, state, accountId) {
</details>
` : ''
const manualCommandSpecs = getManualCommandSpecs(pid, reg)
const manualCommandHtml = buildManualCommandPanel(manualCommandSpecs)
const pairingHtml = reg.pairingChannel ? `
<div style="margin-top:var(--space-md);padding:12px 14px;background:var(--bg-tertiary);border-radius:var(--radius-md)">
<div style="font-weight:600;font-size:var(--font-size-sm);margin-bottom:6px">${t('channels.pairingApproval')}</div>
@@ -1654,6 +1813,7 @@ async function openConfigDialog(pid, page, state, accountId) {
${accountIdHtml}
${agentBindingHtml}
</form>
${manualCommandHtml}
${actionPanelHtml}
${pairingHtml}
<div id="verify-result" style="margin-top:var(--space-sm)"></div>
@@ -1673,6 +1833,7 @@ async function openConfigDialog(pid, page, state, accountId) {
],
width: 520,
})
bindManualCommandCopy(modal, manualCommandSpecs)
// 外部链接用系统浏览器打开
modal.addEventListener('click', (e) => {

View File

@@ -55,7 +55,7 @@ function renderDetectionHint(pathValue, sourceLabel = '') {
if (!normalizedPath && !normalizedSource) return ''
return `
<div class="setup-inline-note" style="margin-top:8px;line-height:1.6">
${normalizedPath ? `<div><span style="color:var(--text-secondary)">${t('setup.detectedPathLabel')}:</span> <code style="font-size:11px">${escapeHtml(normalizedPath)}</code></div>` : ''}
${normalizedPath ? `<div><span style="color:var(--text-secondary)">${t('setup.detectedPathLabel')}:</span> <code class="setup-path-code" title="${escapeHtml(normalizedPath)}">${escapeHtml(normalizedPath)}</code></div>` : ''}
${normalizedSource ? `<div${normalizedPath ? ' style="margin-top:4px"' : ''}><span style="color:var(--text-secondary)">${t('setup.detectedFromLabel')}:</span> ${escapeHtml(normalizedSource)}</div>` : ''}
</div>
`
@@ -67,7 +67,7 @@ function renderStatusCard(title, ok, meta) {
<div class="setup-status-icon">${ok ? '✓' : '✦'}</div>
<div class="setup-status-body">
<div class="setup-status-title">${title}</div>
<div class="setup-status-meta">${escapeHtml(meta)}</div>
<div class="setup-status-meta" title="${escapeHtml(meta)}">${escapeHtml(meta)}</div>
</div>
</div>
`
@@ -85,6 +85,13 @@ export async function render() {
<div class="setup-hero-copy">
<h1 class="setup-hero-title">${t('setup.headerTitle')}</h1>
<p class="setup-hero-desc">${t('setup.headerDesc')}</p>
<div class="setup-hero-site-row">
<a class="setup-hero-site-link" href="https://claw.qt.cool" target="_blank" rel="noopener noreferrer" title="https://claw.qt.cool">
${icon('link', 14)}
<span class="setup-hero-site-label">${t('setup.officialWebsite')}</span>
<span class="setup-hero-site-value">claw.qt.cool</span>
</a>
</div>
</div>
</div>
<div class="setup-hero-actions">
@@ -131,18 +138,6 @@ async function runDetect(page) {
let config = configRes.status === 'fulfilled' ? configRes.value : { installed: false }
const version = versionRes.status === 'fulfilled' ? versionRes.value : null
// CLI 已装但配置缺失 → 自动创建默认配置
if (cliOk && !config.installed) {
try {
const initResult = await api.initOpenclawConfig()
if (initResult?.created) {
config = await api.checkInstallation()
}
} catch (e) {
console.warn('[setup] 自动初始化配置失败:', e)
}
}
// Git 已安装时,自动配置 HTTPS 替代 SSH静默执行
if (git.installed) {
api.configureGitHttps().catch(() => {})
@@ -277,7 +272,7 @@ function renderSteps(page, { node, git, cliOk, config, version }) {
${stepIcon(config.installed)} ${t('setup.stepConfig')}
</div>
${config.installed
? `<p style="color:var(--success);font-size:var(--font-size-sm)">${t('setup.configAt', { path: config.path || '' })}</p>
? `<p class="setup-path-text" style="color:var(--success);font-size:var(--font-size-sm)" title="${escapeHtml(config.path || '')}">${t('setup.configAt', { path: config.path || '' })}</p>
${renderDetectionHint(config.path)}`
: `<p style="color:var(--text-secondary);font-size:var(--font-size-sm);margin-bottom:var(--space-sm)">
${t('setup.configMissing')}
@@ -694,7 +689,9 @@ function bindEvents(page, nodeOk, detectState) {
btn.textContent = t('setup.initializing')
try {
const result = await api.initOpenclawConfig()
if (result?.created) {
if (result?.restored) {
toast(t('setup.configRestored'), 'success')
} else if (result?.created) {
toast(t('setup.configCreated'), 'success')
} else {
toast(result?.message || t('setup.configExists'), 'info')

View File

@@ -1248,6 +1248,43 @@
min-width: 0;
}
.setup-hero-site-row {
margin-top: 12px;
}
.setup-hero-site-link {
display: inline-flex;
align-items: center;
gap: 8px;
max-width: 100%;
padding: 8px 12px;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 8%, var(--bg-secondary));
border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border-primary));
color: var(--text-primary);
text-decoration: none;
font-size: var(--font-size-xs);
line-height: 1.4;
transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.setup-hero-site-link:hover {
transform: translateY(-1px);
border-color: color-mix(in srgb, var(--accent) 40%, var(--border-primary));
background: color-mix(in srgb, var(--accent) 12%, var(--bg-secondary));
}
.setup-hero-site-label {
color: var(--text-secondary);
}
.setup-hero-site-value {
min-width: 0;
font-weight: 700;
color: var(--accent);
overflow-wrap: anywhere;
}
.setup-hero-title {
margin: 0 0 6px;
font-size: clamp(28px, 4vw, 36px);
@@ -1333,6 +1370,9 @@
font-size: var(--font-size-xs);
color: var(--text-secondary);
line-height: 1.5;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.setup-main-grid {
@@ -1379,6 +1419,25 @@
line-height: 1.6;
}
.setup-path-text {
margin: 0;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.setup-path-code {
display: inline-block;
max-width: 100%;
margin-left: 4px;
vertical-align: top;
font-size: 11px;
line-height: 1.6;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-all;
}
.setup-help-details {
border: 1px solid var(--border-primary);
border-radius: var(--radius-md);
@@ -1466,6 +1525,11 @@
align-items: flex-start;
}
.setup-hero-site-link {
border-radius: var(--radius-md);
flex-wrap: wrap;
}
.setup-status-grid {
grid-template-columns: 1fr;
}