diff --git a/static/demo/loaders-preview.html b/static/demo/loaders-preview.html new file mode 100644 index 0000000..df996e2 --- /dev/null +++ b/static/demo/loaders-preview.html @@ -0,0 +1,708 @@ + + + + + + 加载动画预览 + + + +
+

加载动画预览 - 统一风格

+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0. 方格网格 (已有)
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
1. 方块旋转
+
+ + +
+
+
+
+
2. 双方块追逐
+
+ + +
+
+
+
+
+
+
+
+
3. 弹跳方块
+
+ + +
+
+
+
+
+
+
+
+
+
+
4. 五方块序列
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
5. 九宫格动画
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
6. 波纹方块
+
+
+
+ + diff --git a/static/demo/loaders-v2.html b/static/demo/loaders-v2.html new file mode 100644 index 0000000..935bb55 --- /dev/null +++ b/static/demo/loaders-v2.html @@ -0,0 +1,123 @@ + + + + + 加载动画预览 V2 + + + +
+

加载动画预览 V2 - 统一风格

+
+
0. 方格网格 (已有)
+
1. 方块旋转
+
2. 双方块追逐
+
3. 弹跳方块
+
4. 五方块序列
+
5. 九宫格动画
+
6. 波纹方块
+
+
+ + diff --git a/static/demo/minimal-mode-v2/index.html b/static/demo/minimal-mode-v2/index.html new file mode 100644 index 0000000..3a18096 --- /dev/null +++ b/static/demo/minimal-mode-v2/index.html @@ -0,0 +1,195 @@ + + + + + + 极简模式 V2 - 无缝展开 + + + +
+
+ +
+
+ + + + + 用户 +
+
+ 帮我搜索一下最新的AI技术发展趋势 +
+
+ + +
+
+ + + + + AI Assistant +
+ + +
+ + + + . + . + . +
+ + + + + + + + + +
+
+ + +
+ + + +
+
+ + + + diff --git a/static/demo/minimal-mode-v2/script.js b/static/demo/minimal-mode-v2/script.js new file mode 100644 index 0000000..20f48fd --- /dev/null +++ b/static/demo/minimal-mode-v2/script.js @@ -0,0 +1,413 @@ +// 全局状态 +let currentStep = 0; +let isExpanded = false; +let demoRunning = false; + +// 步骤配置 +const steps = [ + { + type: 'thinking', + icon: 'brain', + summary: '我需要搜索最新的AI技术发展趋势。首先,我应该使用搜索工具来查找相关信息...', + fullText: '我需要搜索最新的AI技术发展趋势。首先,我应该使用搜索工具来查找相关信息。搜索关键词应该包括"AI技术"、"人工智能"、"2026"、"发展趋势"等。', + duration: 2000 + }, + { + type: 'tool', + icon: 'search', + summary: '搜索:AI技术发展趋势 2026', + duration: 2500 + }, + { + type: 'thinking', + icon: 'brain', + summary: '搜索结果显示了一些有价值的信息。现在我需要执行一个命令来获取更详细的数据分析...', + fullText: '搜索结果显示了一些有价值的信息。现在我需要执行一个命令来获取更详细的数据分析。我将使用curl命令来获取第一个链接的内容,然后进行分析。这将帮助我提供更准确和详细的答案。让我执行这个命令...\n\n在执行之前,我需要确保命令的安全性和有效性。curl命令是一个常用的网络请求工具,可以帮助我获取网页内容。我会添加适当的参数来确保请求的稳定性,比如设置超时时间、用户代理等。\n\n同时,我还需要考虑如何解析返回的HTML内容,提取出关键信息。可能需要使用一些文本处理工具,如grep、sed或awk来过滤和格式化数据。', + duration: 2000 + }, + { + type: 'tool', + icon: 'terminal', + summary: '执行命令:获取网页内容并分析', + duration: 2500 + }, + { + type: 'thinking', + icon: 'brain', + summary: '很好,我已经收集到了足够的信息。现在我可以整理这些内容,为用户提供一个清晰、全面的答案...', + fullText: '很好,我已经收集到了足够的信息。现在我可以整理这些内容,为用户提供一个清晰、全面的答案。我会按照趋势的重要性和影响力来组织内容。', + duration: 2000 + } +]; + +// 最终输出文本 +const finalOutputText = `根据我的搜索和分析,2026年AI技术的主要发展趋势包括: + +1. **多模态AI模型的普及**:能够同时处理文本、图像、音频等多种数据类型的AI模型将成为主流。 + +2. **AI安全性和可解释性**:随着AI应用的广泛部署,确保AI系统的安全性和决策的可解释性变得越来越重要。 + +3. **边缘AI计算**:将AI计算能力部署到边缘设备,实现更快的响应速度和更好的隐私保护。 + +这些趋势将深刻影响各行各业的数字化转型进程。`; + +// 开始演示 +async function startDemo() { + if (demoRunning) return; + + demoRunning = true; + resetDemo(); + + // 显示等待动画 + const placeholder = document.getElementById('generatingPlaceholder'); + placeholder.style.display = 'flex'; + + await sleep(1500); + + // 隐藏等待动画,显示摘要包装器 + placeholder.style.display = 'none'; + const summaryWrapper = document.getElementById('summaryWrapper'); + summaryWrapper.style.display = 'flex'; + summaryWrapper.classList.add('running'); + + // 执行每个步骤 + for (let i = 0; i < steps.length; i++) { + currentStep = i; + await executeStep(i); + } + + // 完成后移除运行状态 + await sleep(500); + summaryWrapper.classList.remove('running'); + + // 如果展开状态,显示最后一步 + if (isExpanded) { + await showDetailStep(currentStep, true); + } else { + // 折叠状态下,摘要显示第一步内容 + updateSummary(steps[0]); + } + + await sleep(800); + + // 显示最终输出(流式) + const textOutput = document.getElementById('textOutput'); + textOutput.style.display = 'block'; + setTimeout(() => { + textOutput.classList.add('show'); + }, 50); + + // 流式输出最终文本 + await streamText(document.querySelector('.text-content'), finalOutputText, 20); + + demoRunning = false; +} + +// 执行单个步骤 +async function executeStep(stepIndex) { + const step = steps[stepIndex]; + + // 更新摘要(仅在折叠状态下) + if (!isExpanded) { + // 流式输出摘要文本 + await streamSummary(step); + } + + // 如果展开状态,显示详细内容 + if (isExpanded) { + await showDetailStep(stepIndex, true); + + // 如果是思考类型,流式输出完整内容 + if (step.type === 'thinking' && step.fullText) { + const stepBody = document.querySelector(`.step-item[data-step="${stepIndex + 1}"] .step-body`); + if (stepBody) { + stepBody.textContent = ''; + await streamText(stepBody, step.fullText, 30); + // 更新竖线高度 + updateLineHeight(stepIndex + 1); + } + } + } + + // 显示下一个步骤的竖线(如果有下一个步骤) + if (stepIndex < steps.length - 1) { + if (isExpanded) { + // 展开状态:显示当前步骤的竖线 + const currentStepItem = document.querySelector(`.step-item[data-step="${stepIndex + 1}"]`); + if (currentStepItem) { + const line = currentStepItem.querySelector('.step-line'); + if (line) { + updateLineHeight(stepIndex + 1); + } + } + } else { + // 折叠状态:显示摘要的竖线(暂时不需要,因为只在展开时才有步骤) + } + } + + await sleep(step.duration); +} + +// 流式输出摘要文本 +async function streamSummary(step) { + const summaryIcon = document.getElementById('summaryIcon'); + const summaryText = document.getElementById('summaryText'); + + // 更新图标 + summaryIcon.innerHTML = getIconSVG(step.icon); + + // 流式输出文本 + summaryText.textContent = ''; + await streamText(summaryText, step.summary, 30); +} + +// 流式输出文本 +async function streamText(element, text, speed = 30) { + element.textContent = ''; + for (let i = 0; i < text.length; i++) { + element.textContent += text[i]; + await sleep(speed); + } +} + +// 更新摘要 +function updateSummary(step) { + const summaryIcon = document.getElementById('summaryIcon'); + const summaryText = document.getElementById('summaryText'); + + // 更新图标 + summaryIcon.innerHTML = getIconSVG(step.icon); + + // 更新文本 + summaryText.textContent = step.summary; +} + +// 显示详细步骤 +async function showDetailStep(stepIndex, isNewStep = false) { + const stepItem = document.querySelector(`.step-item[data-step="${stepIndex + 1}"]`); + if (!stepItem) return; + + // 添加动画类 + if (isNewStep) { + stepItem.classList.add('animated'); + } else { + stepItem.classList.add('instant'); + } + + // 显示步骤 + stepItem.style.display = 'flex'; + + // 等待动画 + await sleep(isNewStep ? 100 : 10); + + // 如果有下一个步骤,显示当前步骤的竖线 + if (stepIndex < steps.length - 1) { + updateLineHeight(stepIndex + 1); + } + + await sleep(isNewStep ? 250 : 10); +} + +// 更新竖线高度 +function updateLineHeight(stepIndex) { + const stepItem = document.querySelector(`.step-item[data-step="${stepIndex}"]`); + if (!stepItem) return; + + const line = stepItem.querySelector('.step-line'); + const stepContent = stepItem.querySelector('.step-content'); + if (!line || !stepContent) return; + + // 获取内容的实际高度 + const contentHeight = stepContent.offsetHeight; + + // 如果是最后一个步骤,不需要竖线 + if (stepIndex >= steps.length) { + line.style.height = '0'; + return; + } + + // 计算竖线高度:内容高度 + 间距 + const gap = 16; + const lineHeight = Math.max(contentHeight + gap, 40); + line.style.height = `${lineHeight}px`; +} + +// 切换展开/折叠 +async function toggleExpand() { + const summaryWrapper = document.getElementById('summaryWrapper'); + if (summaryWrapper.style.display === 'none') return; + + isExpanded = !isExpanded; + + if (isExpanded) { + // 展开:显示完整文本,隐藏摘要文本 + summaryWrapper.classList.add('expanded'); + + const summaryText = document.getElementById('summaryText'); + const summaryTextFull = document.getElementById('summaryTextFull'); + const summaryLine = document.getElementById('summaryLine'); + const stepsContainer = document.getElementById('stepsContainer'); + + // 切换文本显示 + summaryText.style.display = 'none'; + summaryTextFull.style.display = 'block'; + + // 显示连接线 + await sleep(50); + summaryLine.style.height = '20px'; + + // 显示步骤容器 + stepsContainer.style.display = 'block'; + await sleep(50); + stepsContainer.classList.add('show'); + + // 快速显示已完成的步骤 + for (let i = 0; i <= currentStep; i++) { + const isCurrentStep = i === currentStep && demoRunning; + await showDetailStep(i, isCurrentStep); + if (!isCurrentStep) { + await sleep(30); // 已有内容快速出现 + } + } + } else { + // 折叠:显示摘要文本,隐藏完整文本 + summaryWrapper.classList.remove('expanded'); + + const summaryText = document.getElementById('summaryText'); + const summaryTextFull = document.getElementById('summaryTextFull'); + const summaryLine = document.getElementById('summaryLine'); + const stepsContainer = document.getElementById('stepsContainer'); + + // 隐藏步骤容器 + stepsContainer.classList.remove('show'); + await sleep(250); + stepsContainer.style.display = 'none'; + + // 隐藏连接线 + summaryLine.style.height = '0'; + + // 切换文本显示 + summaryText.style.display = 'block'; + summaryTextFull.style.display = 'none'; + + // 更新摘要为第一步内容(不是当前步骤) + updateSummary(steps[0]); + + // 重置所有步骤显示 + document.querySelectorAll('.step-item').forEach(item => { + item.style.display = 'none'; + item.classList.remove('instant', 'animated'); + const line = item.querySelector('.step-line'); + if (line) { + line.style.height = '0'; + } + }); + } +} + +// 展开思考内容 +function expandThinking(stepIndex) { + const thinkingContent = document.getElementById(`thinkingContent${stepIndex}`); + const thinkingContentFull = document.getElementById(`thinkingContentFull${stepIndex}`); + + thinkingContent.style.display = 'none'; + thinkingContentFull.style.display = 'block'; + + // 重新计算并更新竖线高度 + setTimeout(() => { + updateLineHeight(stepIndex); + }, 50); +} + +// 收起思考内容 +function collapseThinking(stepIndex) { + const thinkingContent = document.getElementById(`thinkingContent${stepIndex}`); + const thinkingContentFull = document.getElementById(`thinkingContentFull${stepIndex}`); + + thinkingContent.style.display = 'block'; + thinkingContentFull.style.display = 'none'; + + // 重新计算并更新竖线高度 + setTimeout(() => { + updateLineHeight(stepIndex); + }, 50); +} + +// 重置演示 +function resetDemo() { + currentStep = 0; + isExpanded = false; + demoRunning = false; + + // 隐藏所有元素 + document.getElementById('generatingPlaceholder').style.display = 'none'; + + const summaryWrapper = document.getElementById('summaryWrapper'); + summaryWrapper.style.display = 'none'; + summaryWrapper.classList.remove('running', 'expanded'); + + const summaryText = document.getElementById('summaryText'); + const summaryTextFull = document.getElementById('summaryTextFull'); + summaryText.style.display = 'block'; + summaryTextFull.style.display = 'none'; + + const summaryLine = document.getElementById('summaryLine'); + summaryLine.style.height = '0'; + + const stepsContainer = document.getElementById('stepsContainer'); + stepsContainer.style.display = 'none'; + stepsContainer.classList.remove('show'); + + const textOutput = document.getElementById('textOutput'); + textOutput.style.display = 'none'; + textOutput.classList.remove('show'); + const textContent = textOutput.querySelector('.text-content'); + if (textContent) { + textContent.innerHTML = `

根据我的搜索和分析,2026年AI技术的主要发展趋势包括:

+
    +
  1. 多模态AI模型的普及:能够同时处理文本、图像、音频等多种数据类型的AI模型将成为主流。
  2. +
  3. AI安全性和可解释性:随着AI应用的广泛部署,确保AI系统的安全性和决策的可解释性变得越来越重要。
  4. +
  5. 边缘AI计算:将AI计算能力部署到边缘设备,实现更快的响应速度和更好的隐私保护。
  6. +
+

这些趋势将深刻影响各行各业的数字化转型进程。

`; + } + + // 重置所有步骤 + document.querySelectorAll('.step-item').forEach(item => { + item.style.display = 'none'; + item.classList.remove('instant', 'animated'); + const line = item.querySelector('.step-line'); + if (line) { + line.style.height = '0'; + } + }); + + // 重置思考内容 + document.querySelectorAll('.thinking-content').forEach(item => { + item.style.display = 'block'; + }); + document.querySelectorAll('.thinking-content-full').forEach(item => { + item.style.display = 'none'; + }); + + // 重置摘要文本 + updateSummary(steps[0]); +} + +// 获取图标SVG +function getIconSVG(iconType) { + const icons = { + brain: '', + search: '', + terminal: '' + }; + return icons[iconType] || icons.brain; +} + +// 工具函数:延迟 +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// 点击摘要包装器切换展开/折叠 +document.getElementById('summaryWrapper').addEventListener('click', toggleExpand); diff --git a/static/demo/minimal-mode-v2/style.css b/static/demo/minimal-mode-v2/style.css new file mode 100644 index 0000000..13b304b --- /dev/null +++ b/static/demo/minimal-mode-v2/style.css @@ -0,0 +1,448 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif; + background: #f8f9fa; + color: #1a1a1a; + line-height: 1.6; +} + +.container { + max-width: 900px; + margin: 0 auto; + padding: 40px 20px; +} + +.chat-area { + background: white; + border-radius: 12px; + padding: 32px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +/* 消息块 */ +.message-block { + margin-bottom: 32px; +} + +.message-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-size: 14px; + font-weight: 600; + color: #666; +} + +.icon { + flex-shrink: 0; +} + +/* 用户消息 */ +.user-message { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.user-message .message-text { + background: rgba(0, 0, 0, 0.03); + padding: 14px 18px; + border-radius: 16px; + max-width: 60%; + font-size: 15px; + line-height: 1.5; +} + +/* AI消息 */ +.assistant-message { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +/* 等待动画 */ +.generating-placeholder { + display: flex; + gap: 4px; + padding: 12px 0; + font-size: 15px; + font-weight: 500; + color: #999; +} + +.generating-placeholder .letter { + display: inline-block; + animation: wave 1.6s ease-in-out infinite; + opacity: 0.4; +} + +.generating-placeholder .letter:nth-child(1) { animation-delay: 0s; } +.generating-placeholder .letter:nth-child(2) { animation-delay: 0.1s; } +.generating-placeholder .letter:nth-child(3) { animation-delay: 0.2s; } +.generating-placeholder .letter:nth-child(4) { animation-delay: 0.3s; } +.generating-placeholder .letter:nth-child(5) { animation-delay: 0.4s; } +.generating-placeholder .letter:nth-child(6) { animation-delay: 0.5s; } + +@keyframes wave { + 0%, 100% { + opacity: 0.4; + transform: translateY(0); + } + 50% { + opacity: 1; + transform: translateY(-3px); + } +} + +/* 摘要包装器 */ +.summary-wrapper { + display: flex; + gap: 12px; + cursor: pointer; + position: relative; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.summary-wrapper:hover { + opacity: 0.7; +} + +/* 摘要时间线 */ +.summary-timeline { + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; +} + +.summary-icon { + color: #999; + flex-shrink: 0; + margin-top: 2px; +} + +.summary-line { + width: 2px; + height: 0; + background: #e0e0e0; + margin-top: 6px; + transition: height 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.summary-wrapper.expanded .summary-line { + height: 20px; +} + +/* 摘要内容 */ +.summary-content { + flex: 1; + min-width: 0; + position: relative; +} + +.summary-text { + font-size: 13px; + color: #999; + line-height: 1.6; + display: block; +} + +.summary-text.collapsed { + position: relative; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 100%; +} + +.summary-text.collapsed::after { + content: ''; + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 40px; + background: linear-gradient(to right, transparent, white); + pointer-events: none; +} + +.summary-text-full { + font-size: 14px; + color: #666; + line-height: 1.7; +} + +/* 运行中的发光效果(字体颜色变化) */ +.summary-wrapper.running .summary-text, +.summary-wrapper.running .summary-text-full { + animation: textGlow 1.5s ease-in-out infinite; +} + +@keyframes textGlow { + 0%, 100% { + color: #999; + } + 50% { + color: #6464ff; + } +} + +/* 步骤容器 */ +.steps-container { + opacity: 0; + max-height: 0; + overflow: hidden; + transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1), + max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.steps-container.show { + opacity: 1; + max-height: 3000px; +} + +/* 步骤项 */ +.step-item { + display: flex; + gap: 12px; + opacity: 0; + transform: translateY(-8px); + transition: opacity 0.25s ease, transform 0.25s ease; +} + +.step-item.instant { + animation: fadeInStep 0.15s ease forwards; +} + +.step-item.animated { + animation: fadeInStep 0.35s ease forwards; +} + +@keyframes fadeInStep { + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 步骤时间线 */ +.step-timeline { + display: flex; + flex-direction: column; + align-items: center; + flex-shrink: 0; +} + +.step-icon { + color: #666; + flex-shrink: 0; + margin-top: 2px; +} + +.step-line { + width: 2px; + background: #e0e0e0; + margin-top: 6px; + transition: height 0.2s ease; +} + +.step-item:last-child .step-line { + height: 0 !important; +} + +/* 步骤内容 */ +.step-content { + flex: 1; + min-width: 0; + padding-bottom: 16px; +} + +.step-header { + font-size: 12px; + font-weight: 600; + color: #999; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 8px; +} + +.step-body { + font-size: 14px; + line-height: 1.6; + color: #333; +} + +/* 思考内容 */ +.thinking-content { + color: #666; + font-size: 14px; + line-height: 1.7; + max-height: 100px; + overflow: hidden; + position: relative; +} + +.thinking-content-full { + color: #666; + font-size: 14px; + line-height: 1.7; +} + +.show-more-btn, +.show-less-btn { + display: inline-block; + margin-top: 8px; + padding: 4px 12px; + background: transparent; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 12px; + color: #666; + cursor: pointer; + transition: all 0.2s ease; +} + +.show-more-btn:hover, +.show-less-btn:hover { + background: #f5f5f5; + border-color: #999; +} + +/* 工具内容 */ +.tool-intent { + font-weight: 500; + color: #444; + margin-bottom: 12px; +} + +.tool-result { + background: #f8f9fa; + border-radius: 8px; + padding: 12px; +} + +.result-item { + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; +} + +.result-item:last-child { + border-bottom: none; +} + +.result-title { + font-weight: 500; + color: #333; + margin-bottom: 4px; +} + +.result-url { + font-size: 12px; + color: #999; +} + +/* 命令块 */ +.command-block { + background: #f8f9fa; + border-radius: 8px; + overflow: hidden; +} + +.command-header { + background: #e9ecef; + padding: 8px 12px; + font-size: 12px; + color: #666; +} + +.command-code { + padding: 12px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 13px; + color: #333; + overflow-x: auto; +} + +.command-output { + padding: 12px; + border-top: 1px solid #e0e0e0; +} + +.output-line { + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 12px; + color: #666; + line-height: 1.8; +} + +/* 最终输出 */ +.text-output { + margin-top: 16px; + opacity: 0; + transform: translateY(10px); + transition: all 0.4s ease; +} + +.text-output.show { + opacity: 1; + transform: translateY(0); +} + +.text-content { + font-size: 15px; + line-height: 1.7; + color: #333; +} + +.text-content p { + margin-bottom: 16px; +} + +.text-content ol { + margin-left: 24px; + margin-bottom: 16px; +} + +.text-content li { + margin-bottom: 12px; +} + +.text-content strong { + font-weight: 600; + color: #1a1a1a; +} + +/* 控制面板 */ +.control-panel { + margin-top: 32px; + display: flex; + gap: 12px; + justify-content: center; +} + +.control-btn { + padding: 10px 20px; + background: #007bff; + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.control-btn:hover { + background: #0056b3; + transform: translateY(-1px); +} + +.control-btn:active { + transform: translateY(0); +} diff --git a/static/demo/minimal-mode/index.html b/static/demo/minimal-mode/index.html new file mode 100644 index 0000000..feafe4a --- /dev/null +++ b/static/demo/minimal-mode/index.html @@ -0,0 +1,191 @@ + + + + + + 极简模式 Demo + + + +
+
+ +
+
+ + + + + 用户 +
+
+ 帮我搜索一下最新的AI技术发展趋势 +
+
+ + +
+
+ + + + + AI Assistant +
+ + +
+ + + + . + . + . +
+ + + + + + + + + +
+
+ + +
+ + + +
+
+ + + + diff --git a/static/demo/minimal-mode/index.html.backup b/static/demo/minimal-mode/index.html.backup new file mode 100644 index 0000000..feafe4a --- /dev/null +++ b/static/demo/minimal-mode/index.html.backup @@ -0,0 +1,191 @@ + + + + + + 极简模式 Demo + + + +
+
+ +
+
+ + + + + 用户 +
+
+ 帮我搜索一下最新的AI技术发展趋势 +
+
+ + +
+
+ + + + + AI Assistant +
+ + +
+ + + + . + . + . +
+ + + + + + + + + +
+
+ + +
+ + + +
+
+ + + + diff --git a/static/demo/minimal-mode/script.js b/static/demo/minimal-mode/script.js new file mode 100644 index 0000000..cf31b69 --- /dev/null +++ b/static/demo/minimal-mode/script.js @@ -0,0 +1,333 @@ +// 全局状态 +let currentStep = 0; +let isExpanded = false; +let demoRunning = false; + +// 步骤配置 +const steps = [ + { + type: 'thinking', + icon: 'brain', + summary: '正在思考如何搜索...', + duration: 2000 + }, + { + type: 'tool', + icon: 'search', + summary: '搜索:AI技术发展趋势 2026', + duration: 2500 + }, + { + type: 'thinking', + icon: 'brain', + summary: '搜索结果显示了一些有价值的信息。现在我需要执行...', + duration: 2000 + }, + { + type: 'tool', + icon: 'terminal', + summary: '执行命令:获取网页内容并分析', + duration: 2500 + }, + { + type: 'thinking', + icon: 'brain', + summary: '很好,我已经收集到了足够的信息。现在我可以整理...', + duration: 2000 + } +]; + +// 开始演示 +async function startDemo() { + if (demoRunning) return; + + demoRunning = true; + resetDemo(); + + // 显示等待动画 + const placeholder = document.getElementById('generatingPlaceholder'); + placeholder.style.display = 'flex'; + + await sleep(1500); + + // 隐藏等待动画,显示摘要行 + placeholder.style.display = 'none'; + const summaryLine = document.getElementById('summaryLine'); + summaryLine.style.display = 'flex'; + + // 执行每个步骤 + for (let i = 0; i < steps.length; i++) { + currentStep = i; + await executeStep(i); + } + + // 完成后显示最终输出 + await sleep(1000); + summaryLine.classList.add('completed'); + + // 如果展开状态,显示最后一步 + if (isExpanded) { + await showDetailStep(currentStep, true); + } + + // 淡化摘要并显示第一个步骤(极快) + await fadeAndSwitch(summaryLine, steps[0].summary, true); + + await sleep(800); + + // 显示最终输出 + const textOutput = document.getElementById('textOutput'); + textOutput.style.display = 'block'; + setTimeout(() => { + textOutput.classList.add('show'); + }, 50); + + demoRunning = false; +} + +// 执行单个步骤 +async function executeStep(stepIndex) { + const step = steps[stepIndex]; + + // 更新摘要文本 + updateSummary(step); + + // 如果展开状态,显示详细内容 + if (isExpanded) { + await showDetailStep(stepIndex); + } + + await sleep(step.duration); +} + +// 更新摘要 +function updateSummary(step) { + const summaryText = document.getElementById('summaryText'); + const summaryIcon = document.querySelector('.summary-icon'); + + // 更新图标 + summaryIcon.innerHTML = getIconSVG(step.icon); + + // 更新文本 + summaryText.textContent = step.summary; +} + +// 显示详细步骤 +async function showDetailStep(stepIndex, isNewStep = false) { + const timelineItem = document.querySelector(`.timeline-item[data-step="${stepIndex}"]`); + const contentItem = document.querySelector(`.content-item[data-step="${stepIndex}"]`); + + if (!timelineItem || !contentItem) return; + + // 添加动画类 + if (isNewStep) { + timelineItem.classList.add('animated'); + contentItem.classList.add('animated'); + } else { + timelineItem.classList.add('instant'); + contentItem.classList.add('instant'); + } + + // 显示时间线项和内容 + timelineItem.style.display = 'flex'; + contentItem.style.display = 'block'; + + // 计算并设置竖线高度 + await sleep(isNewStep ? 100 : 10); + updateLineHeight(stepIndex); + + await sleep(isNewStep ? 200 : 10); +} + +// 更新竖线高度 +function updateLineHeight(stepIndex) { + const timelineItem = document.querySelector(`.timeline-item[data-step="${stepIndex}"]`); + const contentItem = document.querySelector(`.content-item[data-step="${stepIndex}"]`); + + if (!timelineItem || !contentItem) return; + + const line = timelineItem.querySelector('.timeline-line'); + if (!line) return; + + // 获取内容的实际高度 + const contentHeight = contentItem.offsetHeight; + + // 对于第一个步骤(没有图标),竖线从顶部开始 + if (stepIndex === 0) { + const gap = 20; // 到下一个步骤的间距 + const lineHeight = contentHeight + gap; + line.style.height = `${lineHeight}px`; + return; + } + + // 对于其他步骤,计算竖线高度 + const iconHeight = 20; // 图标高度 + const gap = 20; // 间距 + + // 如果是最后一个步骤,不需要竖线 + if (stepIndex >= steps.length - 1) { + line.style.height = '0'; + return; + } + + // 计算竖线应该的高度:内容高度 + 间距 - 图标高度 + const lineHeight = Math.max(contentHeight + gap - iconHeight, 40); + line.style.height = `${lineHeight}px`; +} + +// 切换展开/折叠 +async function toggleSummary() { + const summaryLine = document.getElementById('summaryLine'); + const detailView = document.getElementById('detailView'); + + if (summaryLine.style.display === 'none') return; + + isExpanded = !isExpanded; + + if (isExpanded) { + // 展开:极快淡化当前摘要,显示第一个步骤的内容 + summaryLine.classList.add('expanded'); + await fadeAndSwitch(summaryLine, steps[0].summary, true); + + // 显示详细视图(快速) + detailView.style.display = 'block'; + setTimeout(() => { + detailView.classList.add('show'); + }, 10); + + // 已完成的步骤快速显示,当前步骤慢速动画 + for (let i = 0; i <= currentStep; i++) { + const isCurrentStep = i === currentStep && demoRunning; + await showDetailStep(i, isCurrentStep); + if (isCurrentStep) { + await sleep(400); // 只有新步骤才有较慢的动画 + } else { + await sleep(50); // 已有内容快速出现 + } + } + } else { + // 折叠:极快淡化第一个步骤,显示当前摘要 + summaryLine.classList.remove('expanded'); + await fadeAndSwitch(summaryLine, steps[currentStep].summary, true); + + // 隐藏详细视图(快速) + detailView.classList.remove('show'); + await sleep(150); + detailView.style.display = 'none'; + + // 重置所有步骤显示 + document.querySelectorAll('.timeline-item').forEach(item => { + item.style.display = 'none'; + item.classList.remove('instant', 'animated'); + }); + document.querySelectorAll('.content-item').forEach(item => { + item.style.display = 'none'; + item.classList.remove('instant', 'animated'); + }); + } +} + +// 淡化并切换文本 +async function fadeAndSwitch(element, newText, fast = false) { + const summaryText = element.querySelector('.summary-text'); + const duration = fast ? 75 : 200; + + // 淡出 + element.style.opacity = '0.3'; + await sleep(duration); + + // 切换文本 + summaryText.textContent = newText; + + // 淡入 + element.style.opacity = '1'; + await sleep(duration); +} + +// 展开思考内容 +function expandThinking(stepIndex) { + const thinkingContent = document.getElementById(`thinkingContent${stepIndex}`); + const thinkingContentFull = document.getElementById(`thinkingContentFull${stepIndex}`); + + thinkingContent.style.display = 'none'; + thinkingContentFull.style.display = 'block'; + + // 重新计算并更新竖线高度 + setTimeout(() => { + updateLineHeight(stepIndex); + }, 50); +} + +// 收起思考内容 +function collapseThinking(stepIndex) { + const thinkingContent = document.getElementById(`thinkingContent${stepIndex}`); + const thinkingContentFull = document.getElementById(`thinkingContentFull${stepIndex}`); + + thinkingContent.style.display = 'block'; + thinkingContentFull.style.display = 'none'; + + // 重新计算并更新竖线高度 + setTimeout(() => { + updateLineHeight(stepIndex); + }, 50); +} + +// 重置演示 +function resetDemo() { + currentStep = 0; + isExpanded = false; + demoRunning = false; + + // 隐藏所有元素 + document.getElementById('generatingPlaceholder').style.display = 'none'; + + const summaryLine = document.getElementById('summaryLine'); + summaryLine.style.display = 'none'; + summaryLine.classList.remove('completed', 'expanded'); + summaryLine.style.opacity = '1'; + + document.getElementById('detailView').style.display = 'none'; + document.getElementById('detailView').classList.remove('show'); + document.getElementById('textOutput').style.display = 'none'; + document.getElementById('textOutput').classList.remove('show'); + + // 重置所有步骤 + document.querySelectorAll('.timeline-item').forEach(item => { + item.style.display = 'none'; + item.classList.remove('instant', 'animated'); + const line = item.querySelector('.timeline-line'); + if (line) { + line.style.height = '0'; + } + }); + document.querySelectorAll('.content-item').forEach(item => { + item.style.display = 'none'; + item.classList.remove('instant', 'animated'); + }); + + // 重置思考内容 + document.querySelectorAll('.thinking-content').forEach(item => { + item.style.display = 'block'; + }); + document.querySelectorAll('.thinking-content-full').forEach(item => { + item.style.display = 'none'; + }); +} + +// 获取图标SVG +function getIconSVG(iconType) { + const icons = { + brain: '', + search: '', + terminal: '' + }; + return icons[iconType] || icons.brain; +} + +// 工具函数:延迟 +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// 点击摘要行切换展开/折叠 +document.getElementById('summaryLine').addEventListener('click', toggleSummary); diff --git a/static/demo/minimal-mode/style.css b/static/demo/minimal-mode/style.css new file mode 100644 index 0000000..aec4409 --- /dev/null +++ b/static/demo/minimal-mode/style.css @@ -0,0 +1,503 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif; + background: #f8f9fa; + color: #1a1a1a; + line-height: 1.6; +} + +.container { + max-width: 900px; + margin: 0 auto; + padding: 40px 20px; +} + +.chat-area { + background: white; + border-radius: 12px; + padding: 32px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +/* 消息块 */ +.message-block { + margin-bottom: 32px; +} + +.message-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-size: 14px; + font-weight: 600; + color: #666; +} + +.icon { + flex-shrink: 0; +} + +/* 用户消息 */ +.user-message { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.user-message .message-text { + background: rgba(0, 0, 0, 0.03); + padding: 14px 18px; + border-radius: 16px; + max-width: 60%; + font-size: 15px; + line-height: 1.5; +} + +/* AI消息 */ +.assistant-message { + display: flex; + flex-direction: column; + align-items: flex-start; +} + +/* 等待动画 */ +.generating-placeholder { + display: flex; + gap: 4px; + padding: 12px 0; + font-size: 15px; + font-weight: 500; + color: #999; +} + +.generating-placeholder .letter { + display: inline-block; + animation: wave 1.6s ease-in-out infinite; + opacity: 0.4; +} + +.generating-placeholder .letter:nth-child(1) { animation-delay: 0s; } +.generating-placeholder .letter:nth-child(2) { animation-delay: 0.1s; } +.generating-placeholder .letter:nth-child(3) { animation-delay: 0.2s; } +.generating-placeholder .letter:nth-child(4) { animation-delay: 0.3s; } +.generating-placeholder .letter:nth-child(5) { animation-delay: 0.4s; } +.generating-placeholder .letter:nth-child(6) { animation-delay: 0.5s; } + +@keyframes wave { + 0%, 100% { + opacity: 0.4; + transform: translateY(0); + } + 50% { + opacity: 1; + transform: translateY(-3px); + } +} + +/* 摘要行 */ +.summary-line { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 10px 0; + cursor: pointer; + position: relative; + overflow: hidden; + transition: opacity 0.15s ease; +} + +.summary-line:hover { + opacity: 0.7; +} + +.summary-icon { + flex-shrink: 0; + color: #999; + margin-top: 2px; +} + +.summary-text { + font-size: 13px; + color: #999; + position: relative; + display: inline-block; + line-height: 1.5; +} + +/* 文字渐变消失效果 */ +.summary-text::after { + content: ''; + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 30px; + background: linear-gradient(to right, transparent, white); + pointer-events: none; +} + +/* 运行中的闪光效果 */ +.shimmer-overlay { + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(100, 100, 255, 0.15) 50%, + transparent 100% + ); + animation: shimmer 2s infinite; + pointer-events: none; +} + +@keyframes shimmer { + 0% { + left: -100%; + } + 100% { + left: 100%; + } +} + +.summary-line.completed .shimmer-overlay { + display: none; +} + +/* 摘要行展开时的连接线 */ +.summary-line.expanded { + padding-bottom: 0; +} + +.summary-line.expanded .summary-icon { + position: relative; +} + +.summary-line.expanded .summary-icon::after { + content: ''; + position: absolute; + left: 50%; + top: 100%; + transform: translateX(-50%); + width: 2px; + height: 8px; + background: #e0e0e0; +} + +/* 详细视图 */ +.detail-view { + margin-top: 0; + opacity: 0; + max-height: 0; + overflow: hidden; + transition: opacity 0.15s ease, max-height 0.15s ease; +} + +.detail-view.show { + opacity: 1; + max-height: 2000px; +} + +.timeline-container { + display: flex; + gap: 20px; + align-items: flex-start; +} + +/* 时间线 */ +.timeline { + display: flex; + flex-direction: column; + padding-top: 0; + flex-shrink: 0; +} + +.timeline-item { + display: flex; + flex-direction: column; + align-items: center; + opacity: 0; + transform: translateY(-5px); +} + +/* 第一个时间线项(没有图标,只有竖线) */ +.timeline-item-first { + padding-top: 0; +} + +.timeline-item-first .timeline-line { + margin-top: 0; +} + +.timeline-item.instant { + animation: fadeInInstant 0.1s ease forwards; +} + +.timeline-item.animated { + animation: fadeInDown 0.4s ease forwards; +} + +.timeline-icon { + flex-shrink: 0; + color: #666; + background: white; + border-radius: 50%; + padding: 2px; +} + +.timeline-line { + width: 2px; + background: #e0e0e0; + margin: 4px 0; + transition: height 0.2s ease; +} + +.timeline-item:last-child .timeline-line { + display: none; +} + +@keyframes fadeInDown { + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInInstant { + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 内容区域 */ +.content-area { + flex: 1; + display: flex; + flex-direction: column; + gap: 0; + min-width: 0; +} + +.content-item { + opacity: 0; + transform: translateX(-5px); + margin-bottom: 20px; +} + +.content-item.instant { + animation: fadeInInstant 0.1s ease forwards; +} + +.content-item.animated { + animation: fadeInLeft 0.4s ease forwards; +} + +@keyframes fadeInLeft { + to { + opacity: 1; + transform: translateX(0); + } +} + +.content-header { + font-size: 12px; + font-weight: 600; + color: #999; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 8px; +} + +/* 第一个内容项不显示header */ +.content-item[data-step="0"] .content-header { + display: none; +} + +/* 思考类型的内容项不显示header */ +.content-item.thinking-type .content-header { + display: none; +} + +.content-body { + font-size: 14px; + line-height: 1.6; + color: #333; +} + +/* 思考内容 */ +.thinking-content { + color: #666; + font-size: 14px; + line-height: 1.7; + max-height: 100px; + overflow: hidden; + position: relative; +} + +.thinking-content-full { + color: #666; + font-size: 14px; + line-height: 1.7; +} + +.show-more-btn, +.show-less-btn { + display: inline-block; + margin-top: 8px; + padding: 4px 12px; + background: transparent; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 12px; + color: #666; + cursor: pointer; + transition: all 0.2s ease; +} + +.show-more-btn:hover, +.show-less-btn:hover { + background: #f5f5f5; + border-color: #999; +} + +/* 工具内容 */ +.tool-intent { + font-weight: 500; + color: #444; + margin-bottom: 12px; +} + +.tool-result { + background: #f8f9fa; + border-radius: 8px; + padding: 12px; +} + +.result-item { + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; +} + +.result-item:last-child { + border-bottom: none; +} + +.result-title { + font-weight: 500; + color: #333; + margin-bottom: 4px; +} + +.result-url { + font-size: 12px; + color: #999; +} + +/* 命令块 */ +.command-block { + background: #f8f9fa; + border-radius: 8px; + overflow: hidden; +} + +.command-header { + background: #e9ecef; + padding: 8px 12px; + font-size: 12px; + color: #666; +} + +.command-code { + padding: 12px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 13px; + color: #333; + overflow-x: auto; +} + +.command-output { + padding: 12px; + border-top: 1px solid #e0e0e0; +} + +.output-line { + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 12px; + color: #666; + line-height: 1.8; +} + +/* 最终输出 */ +.text-output { + margin-top: 16px; + opacity: 0; + transform: translateY(10px); + transition: all 0.5s ease; +} + +.text-output.show { + opacity: 1; + transform: translateY(0); +} + +.text-content { + font-size: 15px; + line-height: 1.7; + color: #333; +} + +.text-content p { + margin-bottom: 16px; +} + +.text-content ol { + margin-left: 24px; + margin-bottom: 16px; +} + +.text-content li { + margin-bottom: 12px; +} + +.text-content strong { + font-weight: 600; + color: #1a1a1a; +} + +/* 控制面板 */ +.control-panel { + margin-top: 32px; + display: flex; + gap: 12px; + justify-content: center; +} + +.control-btn { + padding: 10px 20px; + background: #007bff; + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.control-btn:hover { + background: #0056b3; + transform: translateY(-1px); +} + +.control-btn:active { + transform: translateY(0); +} diff --git a/static/demo/test.txt b/static/demo/test.txt new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/static/demo/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file