新增: obscura 安装引导(prompt AI 看 hint 帮装 + 前端工具卡片 AI 帮装按钮)

This commit is contained in:
lxy
2026-08-01 18:07:56 +08:00
parent d664bdc309
commit 3afaff9efe
11 changed files with 332 additions and 4 deletions
+136
View File
@@ -175,6 +175,40 @@
<pre v-if="httpBodyExpanded && parsed?.body" class="ai-tool-cmd-output"><code>{{ parsed.body }}</code></pre>
</div>
<!-- fetch_url 结果:URL + title + render_mode 徽标 + 可折叠 markdown
render_mode='static_fallback'(obscura 未装/失败)→ 显 obscura 未装提示 + "AI 帮装"按钮
(按钮 emit install-obscura → MessageList 调 store.sendMessage 触发 AI 装流式) -->
<div
v-else-if="tc.name === 'fetch_url' && tc.result && tc.status === 'completed' && parsed"
class="ai-tool-fetch-result"
>
<div class="ai-tool-fetch-line">
<span class="ai-tool-http-method ai-tool-fetch-mode" :class="'ai-tool-fetch-mode--' + (parsed?.render_mode || 'static')">
{{ parsed?.render_mode === 'obscura' ? $t('aiTool.fetchUrlRendered')
: parsed?.render_mode === 'static_fallback' ? $t('aiTool.fetchUrlStatic')
: $t('aiTool.fetchUrlStatic') }}
</span>
<code class="ai-tool-http-url">{{ parsed?.url }}</code>
<span v-if="typeof parsed?.length === 'number' && parsed?.length > 0" class="ai-tool-file-meta">{{ parsed?.length }} {{ $t('aiTool.charsUnit') }}</span>
<button
v-if="parsed?.markdown"
class="ai-tool-cmd-toggle"
:title="fetchMdExpanded ? $t('aiTool.collapse') : $t('aiTool.expand')"
@click="fetchMdExpanded = !fetchMdExpanded"
>{{ fetchMdExpanded ? $t('aiTool.collapse') : $t('aiTool.expand') }}</button>
</div>
<div v-if="parsed?.title" class="ai-tool-fetch-title">{{ parsed?.title }}</div>
<!-- obscura 未装引导(render=true 但回退静态):提示 + "AI 帮装"按钮 -->
<div v-if="obscuraMissing" class="ai-tool-obscura-hint">
<div class="ai-tool-obscura-hint-text">⚠ {{ parsed?.hint || $t('aiTool.fetchUrlFallbackHint') }}</div>
<button class="ai-tool-btn ai-tool-btn--install" @click="onInstallObscura">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
{{ $t('aiTool.installObscura') }}
</button>
</div>
<pre v-if="fetchMdExpanded && parsed?.markdown" class="ai-tool-cmd-output"><code>{{ parsed.markdown }}</code></pre>
</div>
<!-- 通用 CRUD 结果 -->
<div
v-else-if="tc.result && tc.status === 'completed'"
@@ -214,12 +248,19 @@ const props = defineProps<{
const emit = defineEmits<{
/** read_file "展开全部/收起" → 透传父级(由 ToolCard 继续冒泡) */
'expand-content': [id: string]
/**
* fetch_url static_fallback(obscura 未装)时"AI 帮装"按钮 → 透传父级 → MessageList
* 调 store.sendMessage 触发 AI 装流式安装。仅 fetch_url 工具会发此事件。
*/
'install-obscura': []
}>()
// run_command 输出折叠态:失败命令默认展开(便于看 stderr),成功默认折叠
const cmdOutputExpanded = ref(false)
// http_request 响应 body 折叠态:失败(非 2xx)默认展开便于看错误,成功默认折叠
const httpBodyExpanded = ref(false)
// fetch_url markdown 折叠态:默认折叠(可能很长,头部已有 title + 截断字符数摘要)
const fetchMdExpanded = ref(false)
/** 一次解析结果供模板多次读(模板引用多次,computed 避免每次 patch 重 parse) */
const parsed = computed(() => parseResult(props.tc.result))
@@ -267,6 +308,31 @@ const grepHitLabel = computed(() => {
return r ? formatGrep(r) : ''
})
/**
* fetch_url obscura 未装检测(render=true 但回退静态):
* 后端 render=true 且 obscura 未装/失败/超时 → result.render_mode='static_fallback' + hint 含 obscura 引导。
* 此 computed 供模板显"AI 帮装"按钮 + obscura 未装提示。
* 检测条件:render_mode==='static_fallback'(后端保证此值仅 render 分支回退时设,普通静态是 'static')。
* 双重确认 hint 含 obscura(防御后端未来改字段名),任一命中即视为未装。
*/
const obscuraMissing = computed(() => {
if (props.tc.name !== 'fetch_url') return false
const r = parsed.value
if (!r) return false
const mode = typeof r.render_mode === 'string' ? r.render_mode : ''
const hint = typeof r.hint === 'string' ? r.hint : ''
return mode === 'static_fallback' || hint.toLowerCase().includes('obscura')
})
/**
* fetch_url "AI 帮装 obscura" → emit 透传到 MessageList,调 store.sendMessage
* 触发 AI run_command 装流式。安装完成后用户重试即可用 obscura 渲染。
* 提示语足够具体(给出两种装法),AI 拿到能直接 run_command 不需再问。
*/
function onInstallObscura() {
emit('install-obscura')
}
// SW-260618-06: status 切到 completed 时按成功/失败初始化折叠态(失败默认展开便于看 stderr/错误 body)。
// immediate 时若初始非 completed,两 ref 保持默认 false,无副作用。
watch(() => props.tc.status, (s) => {
@@ -684,4 +750,74 @@ watch(() => props.tc.status, (s) => {
color: var(--df-warning, #f0c75e);
border-top: 0.5px solid var(--df-border);
}
/* -- fetch_url 结果:复用 http-line 视觉 token;render_mode 徽标按 mode 着色 -- */
.ai-tool-fetch-result {
margin: 0;
border-top: 0.5px solid var(--df-border);
font-family: var(--df-font-mono);
font-size: 11px;
}
.ai-tool-fetch-line {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: rgba(94,175,240,0.04);
}
.ai-tool-fetch-mode {
/* 复用 .ai-tool-http-method 基样(template 已加该 class),此处仅覆盖 mode 着色 */
font-size: 10px;
}
.ai-tool-fetch-mode--static,
.ai-tool-fetch-mode--obscura {
color: var(--df-success, #3ddb9c);
}
.ai-tool-fetch-mode--static_fallback {
color: var(--df-warning, #f0c75e);
}
.ai-tool-fetch-title {
padding: 4px 10px 2px;
font-family: var(--df-font-sans);
font-size: 11px;
font-weight: 600;
color: var(--df-text);
word-break: break-word;
}
/* obscura 未装引导(static_fallback):warning 色调提示 + AI 帮装按钮 */
.ai-tool-obscura-hint {
margin: 4px 10px 6px;
padding: 6px 8px;
background: var(--df-warning-bg, rgba(240,199,94,0.08));
border: 0.5px solid var(--df-warning, #f0c75e);
border-radius: var(--df-radius-sm, 4px);
display: flex;
flex-direction: column;
gap: 6px;
}
.ai-tool-obscura-hint-text {
font-family: var(--df-font-sans);
font-size: 10.5px;
color: var(--df-warning, #f0c75e);
line-height: 1.5;
word-break: break-word;
}
/* AI 帮装按钮(accent 色调,对齐 batch-approve 视觉) */
.ai-tool-btn {
align-self: flex-start;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
font-size: 11px;
font-weight: 500;
border-radius: var(--df-radius-sm, 4px);
border: none;
background: var(--df-accent, #5b8def);
color: #fff;
cursor: pointer;
font-family: var(--df-font-sans);
transition: filter 0.15s var(--df-ease);
}
.ai-tool-btn:hover { filter: brightness(1.1); }
</style>