Add AI chat with skills management and model provider settings
- Model providers for OpenAI and Anthropic message formats (e.g. via Sub2API), with API keys encrypted at rest and never returned - Skills tree with markdown editing, priorities, drafts, publishing and rollback; only published skills enter the chat prompt - Chat page with tool calls over collected news and Binance USD-M klines, a persisted CONTEXT.md, reset, and stop-generation support - Chat may only create skill drafts and update CONTEXT.md; publishing, deleting, moving and priority changes stay with the user
This commit is contained in:
1 parent
f042f27013
commit
5db3ead883
34 files changed
+3196
-12
No files matched your search
@@ -0,0 +1,85 @@
|
||||
<script lang="ts" setup>
|
||||
import { renderMarkdown } from "@/utils/markdown"
|
||||
|
||||
export interface ToolCard {
|
||||
id: string
|
||||
name: string
|
||||
arguments: Record<string, unknown>
|
||||
/** 未返回时为 null */
|
||||
result: string | null
|
||||
}
|
||||
|
||||
export interface DisplayItem {
|
||||
key: string
|
||||
kind: "user" | "assistant"
|
||||
text: string
|
||||
toolCalls: ToolCard[]
|
||||
stopped?: boolean
|
||||
error?: string
|
||||
/** 生成中:这次模型调用已结束,后续文字另起一条 */
|
||||
closed?: boolean
|
||||
}
|
||||
|
||||
defineProps<{ items: DisplayItem[] }>()
|
||||
|
||||
const TOOL_LABELS: Record<string, string> = {
|
||||
search_news: "检索新闻",
|
||||
get_news: "读取新闻正文",
|
||||
search_symbols: "查找交易对",
|
||||
get_klines: "获取 K 线",
|
||||
get_ticker: "获取 24h 行情",
|
||||
list_skills: "列出 skills",
|
||||
read_skill: "读取 skill",
|
||||
propose_skill_edit: "提交 skill 草稿",
|
||||
create_skill: "新建 skill(草稿)",
|
||||
update_context: "更新 CONTEXT.md"
|
||||
}
|
||||
|
||||
function prettyResult(result: string) {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(result), null, 2)
|
||||
} catch {
|
||||
// 前端拿到的是截断后的预览,可能不是完整 JSON
|
||||
return result
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div v-for="item in items" :key="item.key" :class="item.kind === 'user' ? 'self-end max-w-3/4' : 'self-stretch'">
|
||||
<div v-if="item.kind === 'user'" class="whitespace-pre-wrap rounded-lg bg-[var(--el-color-primary-light-9)] px-3 py-2 text-sm">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-2">
|
||||
<!-- markdown-it 已关闭 html,模型输出中的标签按文本显示 -->
|
||||
<div v-if="item.text" class="markdown-body" v-html="renderMarkdown(item.text)" />
|
||||
<el-collapse v-if="item.toolCalls.length" class="tool-cards">
|
||||
<el-collapse-item v-for="call in item.toolCalls" :key="call.id" :name="call.id">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2 overflow-hidden text-xs">
|
||||
<el-tag size="small" :type="call.result === null ? 'warning' : 'info'">
|
||||
{{ call.result === null ? "执行中" : "工具" }}
|
||||
</el-tag>
|
||||
<span class="font-bold">{{ TOOL_LABELS[call.name] ?? call.name }}</span>
|
||||
<span class="truncate text-[var(--el-text-color-secondary)] font-mono">{{ JSON.stringify(call.arguments) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<pre class="m-0 max-h-80 overflow-auto whitespace-pre-wrap break-all rounded bg-[var(--el-fill-color-light)] p-2 text-xs">{{ call.result === null ? "等待结果…" : prettyResult(call.result) }}</pre>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<div v-if="item.stopped" class="text-xs text-[var(--el-text-color-secondary)]">
|
||||
(已停止生成)
|
||||
</div>
|
||||
<el-alert v-if="item.error" :title="item.error" type="error" :closable="false" show-icon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tool-cards :deep(.el-collapse-item__header) {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user