diff --git a/README.md b/README.md index 2275409..b41da0f 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,14 @@ InsightReply 采用现代化解耦的三端架构: - 数据库:PostgreSQL 14+ - 功能模块:Nitter 高可用爬虫引擎、多模态 AI 接入层、策略派发与遥测统计引擎。 2. **`/web` (SaaS 管理控制台)** - - 核心选型:Vue 3 + Vite + Tailwind 玻璃态美学 - - 功能模块:数据指标大盘、策略雷达与组合词配置、生成表现记录仪表板。 + - 核心选型:Vue 3 + Vite + Tailwind **Apple Pro Max 玻璃态美学** + - 功能模块:数据指标大盘、策略雷达、生成表现记录。 3. **`/extension` (Chrome 浏览器助手)** - - 核心选型:CRXJS + Vue 3 - - 功能模块:侵入 X 原生界面,一键触发 AI 分析、克隆高赞评论语言风格。 + - 核心选型:CRXJS + Vue 3 + **macOS Settings 交互规范** + - 功能模块: + - **Spatial Sidebar**: 侵入 X 原生界面,全玻璃态侧边栏一键触发 AI 分析。 + - **Pro Dashboard**: 类似 macOS 系统设置的后台管理,极简高效。 + - **Magic Toggle**: `Cmd + Shift + I` 全局快捷键秒开侧边栏。 --- diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index b4da55f..03a4ee6 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -1,7 +1,8 @@ -# InsightReply 用户使用指南 +# InsightReply 用户使用指南 💎 -> InsightReply 是一个帮助创始人和独立开发者在 X (Twitter) 行业热点中输出更有洞察力评论的 AI 助手。 -> 它是**社交表达增强系统**,不是自动化机器人 —— 所有评论由你决定是否发布。 +> InsightReply 是一款专为独立开发者与数字游民设计的 **AI 驱动 X (Twitter) 自动化营销套件**。 +> 它采用 **Apple Pro Max 玻璃态美学** 与 **macOS Settings 交互规范**,为你提供丝滑、专业的社交增长体验。 +> 所有评论由你决定是否发布 —— 我们是**社交表达增强系统**,不是自动化机器人。 --- @@ -20,63 +21,67 @@ ### 1.2 首次设置 (Onboarding) -安装后首次点击扩展图标,会进入 **3 步引导**: +安装后首次点击扩展图标,会进入 **Pro Max 引导流程**: | 步骤 | 设置内容 | 说明 | |------|---------|------| -| Step 1 | 🧑‍💻 **你的身份** | AI 创始人 / SaaS Builder / 投资人 / 独立开发者 / 技术分析者 | -| Step 2 | 🌐 **偏好语言** | English / 中文 / 跟随原推文 (Auto) | +| Step 1 | 🧑‍💻 **身份定义** | AI 创始人 / SaaS Builder / 投资人 / 独立开发者 / 技术分析者 | +| Step 2 | 🌐 **语言智能** | English / 中文 / 跟随原推文 (Auto) | | Step 3 | 🎨 **风格倾向** | 专业严谨 / 轻松幽默 / 犀利锐评 | 这些设置会影响 AI 生成评论的语气和风格,后续可随时在设置中修改。 --- -## 二、核心功能使用 +## 二、核心功能:Spatial Sidebar -### 2.1 生成评论(主流程) +### 2.1 沉浸式生成(主流程) ``` 打开 X (Twitter) → 浏览 Timeline → 找到想评论的推文 ↓ -点击推文操作栏旁的紫色 ✦ Insight 按钮 +点击推文操作栏旁的紫色 ✦ Insight 按钮(或 Cmd+Shift+I 唤起面板) ↓ -右侧弹出 InsightReply 面板 +右侧滑出 Spatial Sidebar(高斯模糊玻璃态设计) ↓ -选择评论策略 → 点击「Generate High-Quality Reply」 +选择评论策略 → 点击「Generate Pro Reply」 ↓ AI 返回多条备选评论 → 选择最满意的一条 ↓ -点击「Copy」→ 粘贴到推文评论框 → 发布 +点击「COPY」→ 粘贴到推文评论框 → 发布 ``` -### 2.2 评论策略一览 +### 2.2 顶级策略库 系统内置 5 种策略,适用于不同场景: -| 策略 | 图标 | 适用场景 | 典型开头 | -|------|------|---------|---------| -| **认知升级型** | 🧠 | 对话题提出更深层的洞察 | "Most people miss this part..." | -| **反向观点型** | 🔥 | 提出不同角度,引发讨论 | "Unpopular opinion:" | -| **数据补充型** | 📊 | 用数据/案例补充论点 | "Data shows that..." | -| **共鸣支持型** | ❤️ | 表达认同并延伸讨论 | "This resonates deeply..." | -| **创始人经验型** | 🚀 | 以自身实战经验为论据 | "We faced this building our product..." | +| 策略 | 深度目标 | 适用场景 | 典型 AI 开头 | +|------|---------|---------|-------------| +| **Cognitive Lift** | 🧠 认知升级 | 对话题提出更深层的洞察 | "Most people miss this part..." | +| **Witty & Fun** | 😄 幽默破冰 | 提出不同角度,引发讨论 | "I can't be the only one who thinks..." | +| **Pro Rigor** | 💼 专业严谨 | 用数据/案例补充论点 | "Based on our engineering data..." | +| **Empathetic** | ❤️ 情感共鸣 | 表达认同并延伸讨论 | "This resonates deeply..." | +| **Sharp Critique** | 🔥 犀利锐评 | 以自身实战经验为论据 | "Unpopular opinion, but..." | > [!TIP] > **选择策略的技巧**: -> - 大 V 发的行业观点 → 用「认知升级型」或「反向观点型」更容易获得关注 -> - 有人吐槽痛点 → 用「创始人经验型」分享你的解决方案 -> - 行业数据/报告 → 用「数据补充型」让你的评论更有说服力 +> - 大 V 发的行业观点 → 用「Cognitive Lift」或「Sharp Critique」更容易获得关注 +> - 有人吐槽痛点 → 用「Empathetic」分享你的解决方案 +> - 行业数据/报告 → 用「Pro Rigor」让你的评论更有说服力 -### 2.3 识别高价值推文 +### 2.3 识别高价值推文(智能标签) -InsightReply 会在推文旁显示提示标签,帮你快速判断: +InsightReply 会在推文旁自动显示提示标签,帮你快速判断互动价值: -| 标签 | 含义 | 建议 | -|------|------|------| -| 🔥 **Trending** | 高热度推文(Likes > 1000,发帖 < 2h) | 快速评论,争取高曝光 | -| ⚡ **Rising** | 增长中的推文(Likes > 100,发帖 < 1h) | 黄金窗口期,强烈建议评论 | -| 🎯 **High Relevance** | 与你的产品领域高度相关 | 最适合你评论的推文! | +| 标签 | 触发条件 | 建议 | +|------|---------|------| +| 🔥 **Trending** | 综合热度 > 50,000(Likes×1 + Retweets×2 + Replies×3) | 流量巅峰,快速评论争取高曝光 | +| ⚡ **Rising** | 综合热度 > 5,000 | 黄金窗口期,强烈建议评论 | +| 🎯 **Relevant** | 推文内容匹配你在产品档案中设置的关键词 | **最适合你评论的推文!** | + +> [!NOTE] +> 热度公式:`heatScore = likes × 1 + retweets × 2 + replies × 3` +> 关键词匹配基于你在 Dashboard → Product DNA 中配置的竞品名与相关关键词。 --- @@ -84,9 +89,9 @@ InsightReply 会在推文旁显示提示标签,帮你快速判断: > 如果你是创始人/独立开发者,正在用 InsightReply 提升产品曝光,以下功能专为你设计。 -### 3.1 配置你的产品档案 +### 3.1 配置你的产品档案 (Product DNA) -在扩展设置中填写你的产品信息,AI 会自动从你的产品领域出发生成评论: +在 Dashboard 中填写你的产品信息,AI 会自动从你的产品领域出发生成评论: | 配置项 | 填什么 | 作用 | |-------|-------|------| @@ -103,24 +108,24 @@ InsightReply 会在推文旁显示提示标签,帮你快速判断: ### 3.2 切换与自定义 AI 引擎 (多模型支持) -为了满足成本、速度、质量等不同维度的需求,InsightReply 支持四大主流平台,并允许**完全自定义模型**。 +InsightReply 支持四大主流平台,并允许**完全自定义模型**。 在产品档案的「重写 AI 引擎」设置中: -1. **下拉选择**:你可以从系统管理员预设的模型列表中快速选择(如 `gpt-4o-mini`, `claude-3-5-haiku-latest`)。 -2. **手动输入 (支持本地/代理)**:如果你使用的平台兼容 OpenAI(如 Groq、vLLM、Ollama),或者你想使用列表中没有的最新模型(如刚刚发布的 `gpt-4.5-turbo`),可以直接在框内**手动打字输入任意模型名称**。 +1. **下拉选择**:从系统预设的模型列表中快速切换(如 `gpt-4o-mini`, `claude-3-5-haiku-latest`)。 +2. **手动输入 (支持本地/代理)**:如果你使用兼容 OpenAI 的平台(如 Groq、vLLM、Ollama),可以直接**手动输入任意模型名称**。 | 引擎 | 推荐模型 | 适用场景特点 | |------|---------|-------------| -| **Anthropic** | `claude-3-5-haiku-latest` | **默认推荐**。响应速度极快,文本语气最自然、最像真人社交媒体发言,不易产生"AI味"。 | -| **OpenAI (或兼容接口)** | `gpt-4o-mini` | 表现稳定,成本极低,适合大批量生成。 | -| **DeepSeek** | `deepseek-chat` | 逻辑分析能力强,中文语感极佳,价格优势明显,适合技术长文讨论。 | -| **Google** | `gemini-2.5-flash` | 速度快,多语言处理能力强。 | +| **Anthropic** | `claude-3-5-haiku-latest` | **默认推荐**。语气最自然,不易产生"AI味" | +| **OpenAI (或兼容接口)** | `gpt-4o-mini` | 表现稳定,成本极低,适合大批量生成 | +| **DeepSeek** | `deepseek-chat` | 逻辑分析强,中文语感极佳,价格优势明显 | +| **Google** | `gemini-2.5-flash` | 速度快,多语言处理能力强 | *注:切换平台需要系统后台配置了相应的 API Key 或 Base URL。管理员可将 OpenAI Base URL 指向任意兼容代理,实现模型的无限扩展。* ### 3.3 创建自定义策略 -除了内置的 5 种策略,你可以创建专属策略: +除了内置的 5 种策略,你可以在 Dashboard → Custom Logic 中创建专属策略: **示例:创建「Builder Story」策略** @@ -148,7 +153,7 @@ Few-shot 示例: | `{language}` | 输出语言 | | `{max_length}` | 最大字符数 | -### 3.3 推广效果最大化小贴士 +### 3.4 推广效果最大化小贴士 1. **优化你的 X Profile** - Bio 中明确写 "Building @YourProduct" @@ -161,7 +166,7 @@ Few-shot 示例: 3. **评论质量 > 数量** - 一条有深度的评论 > 十条"Great post!" - - 用「反向观点型」或「数据补充型」更容易引发讨论 → 更多曝光 + - 用「Sharp Critique」或「Pro Rigor」更容易引发讨论 → 更多曝光 4. **不要硬推产品** - ❌ "Check out my product SwiftBiu!" @@ -169,9 +174,13 @@ Few-shot 示例: --- -## 四、评论历史与效果追踪 +## 四、Dashboard (macOS 风格) -### 4.1 查看评论历史 +InsightReply 的管理后台采用 **macOS Settings 侧边栏布局**,操作直观高效。 + +打开方式:点击扩展图标 → 点击「Launch Dashboard」按钮,或直接使用快捷键。 + +### 4.1 评论历史 点击扩展图标 → `History` Tab: - 查看所有生成过的评论 @@ -179,18 +188,28 @@ Few-shot 示例: - 搜索关键词 - 查看复制/跳过状态 -### 4.2 效果追踪 +### 4.2 效果追踪 (24h Performance Feedback) InsightReply 会自动追踪你发布的评论效果: 1. 你复制并发布了一条评论 -2. 24 小时后,当你再次浏览 X 时,系统自动回查该评论的互动数据 -3. 互动数据(Likes、Replies)会记录到你的个人面板 +2. 24 小时后,系统自动回查该评论的互动数据(Likes、Replies) +3. 互动数据会记录到你的个人面板 > 效果数据积累越多 → AI 越懂你的风格 → 生成质量越高 → 形成正向飞轮 🔄 --- -## 五、版本与定价 +## 五、快捷键 (Shortcuts) + +| 功能 | Mac 快捷键 | Win 快捷键 | 状态 | +|------|------|------|------| +| **唤起/隐藏 Sidebar** | `Cmd + Shift + I` | `Ctrl + Shift + I` | ✅ 已实现 | +| **快速生成** | `Alt + G` | `Alt + G` | 📋 规划中 | +| **一键复制** | `Alt + C` | `Alt + C` | 📋 规划中 | + +--- + +## 六、版本与定价 | 版本 | 价格 | 包含功能 | |------|------|---------| @@ -200,7 +219,7 @@ InsightReply 会自动追踪你发布的评论效果: --- -## 六、常见问题 (FAQ) +## 七、常见问题 (FAQ) ### Q: InsightReply 会自动发布评论吗? **不会**。InsightReply 只生成评论建议,你决定是否复制和发布。我们是 AI 写作增强工具,不是自动化机器人。 @@ -217,21 +236,14 @@ Chrome 和 Edge(基于 Manifest V3)。Firefox 支持规划中。 - 我们不会代替你操作你的 X 账号 ### Q: 如何修改已保存的设置? -点击扩展图标 → 齿轮图标 ⚙️ → 可修改身份标签、语言偏好、产品档案、自定义策略。 +点击扩展图标 → 「Launch Dashboard」→ 在 macOS 风格的侧边栏中选择对应模块修改。 ### Q: 竞品关键词如何工作? -在产品档案中添加竞品名称后,当 Timeline 中出现包含竞品关键词的推文,InsightReply 会在推文旁显示 🎯 标签,提示你这是一条高价值评论机会。 +在 Dashboard → Product DNA 中添加竞品名称后,当 Timeline 中出现包含竞品关键词的推文,InsightReply 会在推文旁显示 🎯 标签,提示你这是一条高价值评论机会。 + +### Q: 界面被推文遮挡怎么办? +我们已升级为 **Shadow DOM 最高优先级渲染**(z-index: 2147483647),面板将永远浮于页面最顶层。 --- -## 七、快捷键 - -| 快捷键 | 功能 | -|-------|------| -| `Alt + I` | 打开/关闭 InsightReply 面板 | -| `Alt + G` | 快速生成评论 | -| `Alt + C` | 复制选中的评论 | - ---- - -> **反馈与建议**:如果你有功能建议或遇到问题,欢迎在我们的 Gitea 仓库提交 Issue。 +> **反馈与建议**:如果你有功能建议或遇到问题,欢迎在我们的 [Gitea 仓库](https://git.buildapp.eu.org/) 提交 Issue。 diff --git a/extension/manifest.json b/extension/manifest.json index 0dcfa12..1e27937 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -33,5 +33,14 @@ "https://x.com/*" ] } - ] + ], + "commands": { + "toggle-sidebar": { + "suggested_key": { + "default": "Ctrl+Shift+I", + "mac": "Command+Shift+I" + }, + "description": "Toggle InsightReply Sidebar" + } + } } \ No newline at end of file diff --git a/extension/src/App.vue b/extension/src/App.vue index 35153e6..7edfa07 100644 --- a/extension/src/App.vue +++ b/extension/src/App.vue @@ -47,31 +47,50 @@ const clearHistory = () => { historyList.value = [] }) } + +const launchDashboard = () => { + chrome.tabs.create({ url: chrome.runtime.getURL('options.html') }) +} diff --git a/extension/src/background/index.ts b/extension/src/background/index.ts index 6ba46b3..2fa9d04 100644 --- a/extension/src/background/index.ts +++ b/extension/src/background/index.ts @@ -103,3 +103,15 @@ chrome.runtime.onMessage.addListener((message: { type: string; payload?: any }, } return true; }); + +// Handle Command (Keyboard Shortcut) +chrome.commands.onCommand.addListener((command) => { + if (command === 'toggle-sidebar') { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const activeTab = tabs[0]; + if (activeTab?.id) { + chrome.tabs.sendMessage(activeTab.id, { type: 'TOGGLE_SIDEBAR' }); + } + }); + } +}); diff --git a/extension/src/content/Sidebar.vue b/extension/src/content/Sidebar.vue index f4a807c..6f9c257 100644 --- a/extension/src/content/Sidebar.vue +++ b/extension/src/content/Sidebar.vue @@ -14,29 +14,24 @@ const props = defineProps<{ } }>() -const isVisible = ref(true) -const selectedStrategy = ref('Insightful') -const generatedReplies = ref>([]) -const isGenerating = ref(false) - -const defaultStrategies = [ - { id: 'Insightful', label: '认知升级型', icon: '🧠' }, - { id: 'Humorous', label: '幽默风趣型', icon: '😄' }, - { id: 'Professional', label: '专业严谨型', icon: '⚖️' }, - { id: 'Supportive', label: '共鸣支持型', icon: '❤️' }, - { id: 'Critical', label: '锐评批判型', icon: '🔥' }, - { id: 'Quote', label: '引用转发型', icon: '💬' } -] - -const strategies = ref([...defaultStrategies]) +const isVisible = ref(false) // Start hidden, wait for trigger onMounted(() => { + // Listen for toggle messages directly in the component + chrome.runtime.onMessage.addListener((message) => { + if (message.type === 'TOGGLE_SIDEBAR') { + isVisible.value = !isVisible.value + } else if (message.type === 'SHOW_INSIGHT') { + isVisible.value = true + } + }) + chrome.runtime.sendMessage({ type: 'FETCH_CUSTOM_STRATEGIES' }, (response) => { if (response && response.success && response.data) { const customStrategies = response.data.map((s: any) => ({ id: s.strategy_key, label: s.label, - icon: '✨' + icon: '' })) strategies.value = [...defaultStrategies, ...customStrategies] } @@ -72,7 +67,7 @@ const generate = () => { generatedReplies.value = [{ strategy: 'Auth Required', content: 'Connection required. Please log in first.' }] chrome.runtime.openOptionsPage() } else { - generatedReplies.value = [{ strategy: 'Error', content: response?.error || 'Failed to generate reply. Please check your connection or API key.' }] + generatedReplies.value = [{ strategy: 'Error', content: response?.error || 'Failed to generate reply. Please check your connection.' }] } }) }) @@ -85,7 +80,6 @@ const copyToClipboard = async (reply: any) => { showProfileTip.value = true setTimeout(() => { showProfileTip.value = false }, 7000) - // Epic 13: Record generated reply for performance tracking telemetry if (!props.tweetData || !props.tweetData.id) return; chrome.storage.local.get(['jwt_token'], async (result) => { @@ -107,7 +101,7 @@ const copyToClipboard = async (reply: any) => { }) }); } catch (err) { - console.error('Failed to log telemetry:', err) // Non blocking telemetry + console.error('Failed to log telemetry:', err) } } }); @@ -115,92 +109,115 @@ const copyToClipboard = async (reply: any) => { diff --git a/extension/src/content/sidebar-mount.ts b/extension/src/content/sidebar-mount.ts index f7e8bd1..d434a41 100644 --- a/extension/src/content/sidebar-mount.ts +++ b/extension/src/content/sidebar-mount.ts @@ -5,49 +5,59 @@ import '../assets/tailwind.css' // We might need to handle this specially for Sh const MOUNT_ID = 'insight-reply-sidebar-root' function initSidebar(tweetData?: any) { - if (document.getElementById(MOUNT_ID)) return + let host = document.getElementById(MOUNT_ID) + + if (host) return; // 1. Create Host Element - const host = document.createElement('div') + host = document.createElement('div') host.id = MOUNT_ID + host.style.cssText = ` + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 2147483647; + pointer-events: none; + ` document.body.appendChild(host) // 2. Create Shadow Root const shadowRoot = host.attachShadow({ mode: 'open' }) - // 3. Create Container for Vue + // 3. Create Container const container = document.createElement('div') container.id = 'app' + container.style.cssText = ` + pointer-events: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + ` shadowRoot.appendChild(container) - // 4. Inject Styles into Shadow DOM - // Note: In development/build, we need to find the generated CSS and inject it. - // CRXJS usually puts CSS in tags in the head for content scripts. - // For Shadow DOM, we need to move or clone them into the shadow root. + // 4. Inject Styles const injectStyles = () => { const styles = document.querySelectorAll('style, link[rel="stylesheet"]') styles.forEach(style => { - // Only clone styles that look like they belong to our extension - // This is a heuristic, in a real build we'd use the asset URL shadowRoot.appendChild(style.cloneNode(true)) }) } - - // Initial injection injectStyles() // 5. Create Vue App const app = createApp(Sidebar, { tweetData }) app.mount(container) - - console.log('InsightReply Sidebar Mounted in Shadow DOM'); } -// Listen for messages to show/hide or update data +// Ensure it mounts on load if needed, but primarily triggered by messages chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { - if (message.type === 'SHOW_INSIGHT') { - console.log('[InsightReply Sidebar Mount] Received SHOW_INSIGHT message:', message.payload); + if (message.type === 'SHOW_INSIGHT' || message.type === 'TOGGLE_SIDEBAR') { initSidebar(message.payload); + // The component itself listens for TOGGLE_SIDEBAR to show/hide sendResponse({ received: true }); } return true; diff --git a/extension/src/options/Auth.vue b/extension/src/options/Auth.vue index 3fbe510..e082c0e 100644 --- a/extension/src/options/Auth.vue +++ b/extension/src/options/Auth.vue @@ -16,7 +16,7 @@ const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api const submitAuth = async () => { if (!form.value.email || !form.value.password) { - errorMsg.value = 'Email and password are required' + errorMsg.value = 'Security credentials required.' return } @@ -38,7 +38,7 @@ const submitAuth = async () => { const data = await res.json() if (!res.ok || data.code !== 200) { - throw new Error(data.message || 'Authentication failed') + throw new Error(data.message || 'Authentication sequence failed.') } // Auth Success @@ -58,52 +58,68 @@ const submitAuth = async () => { + + diff --git a/extension/src/options/Competitors.vue b/extension/src/options/Competitors.vue index dd140a3..610ba42 100644 --- a/extension/src/options/Competitors.vue +++ b/extension/src/options/Competitors.vue @@ -51,7 +51,6 @@ const createCompetitor = async () => { throw new Error(data.message || 'Failed to add competitor') } - // Reset form & reload form.value = { competitor_name: '', platform: 'twitter', target_handle: '', keywords: '' } showForm.value = false await fetchCompetitors() @@ -85,108 +84,140 @@ onMounted(() => { + + diff --git a/extension/src/options/HotTweets.vue b/extension/src/options/HotTweets.vue index c82bc50..0d24475 100644 --- a/extension/src/options/HotTweets.vue +++ b/extension/src/options/HotTweets.vue @@ -1,80 +1,3 @@ - - + + diff --git a/extension/src/options/Options.vue b/extension/src/options/Options.vue index 2eaf23f..cf199cc 100644 --- a/extension/src/options/Options.vue +++ b/extension/src/options/Options.vue @@ -8,7 +8,7 @@ import HotTweets from './HotTweets.vue' const token = ref('') const isLoading = ref(true) -const activeTab = ref('profile') // 'profile', 'strategies', 'competitors' +const activeTab = ref('profile') onMounted(() => { chrome.storage.local.get(['jwt_token'], (res) => { @@ -31,93 +31,140 @@ const logout = () => { + + diff --git a/extension/src/options/Profile.vue b/extension/src/options/Profile.vue index 1959438..07bcd4b 100644 --- a/extension/src/options/Profile.vue +++ b/extension/src/options/Profile.vue @@ -10,7 +10,7 @@ const isSaving = ref(false) const savedMessage = ref('') const errorMsg = ref('') -// Default LLM Options based on the API docs GET /sys/config/llms (mocking for now as we didn't build that API fully) +// Default LLM Options const providers = ['openai', 'anthropic', 'gemini', 'deepseek'] const form = ref({ @@ -43,7 +43,6 @@ const fetchProfile = async () => { default_llm_model: p.default_llm_model || '' } - // Sync relevance keywords to local storage for Content Script chrome.storage.local.set({ relevance_keywords: form.value.relevance_keywords }) } } catch (err: any) { @@ -74,11 +73,10 @@ const saveProfile = async () => { throw new Error(data.message || 'Failed to save profile') } - // Sync relevance keywords to local storage for Content Script chrome.storage.local.set({ relevance_keywords: form.value.relevance_keywords }) - savedMessage.value = 'Profile saved successfully!' - setTimeout(() => { savedMessage.value = '' }, 3000) + savedMessage.value = 'Profile successfully synchronized.' + setTimeout(() => { savedMessage.value = '' }, 4000) } catch (err: any) { errorMsg.value = err.message @@ -95,121 +93,120 @@ onMounted(() => { + + diff --git a/extension/src/options/Strategies.vue b/extension/src/options/Strategies.vue index 1a57ed3..a69e2cb 100644 --- a/extension/src/options/Strategies.vue +++ b/extension/src/options/Strategies.vue @@ -50,7 +50,6 @@ const createStrategy = async () => { throw new Error(data.message || 'Failed to create strategy') } - // Reset form & reload form.value = { strategy_key: '', label: '', description: '' } showForm.value = false await fetchStrategies() @@ -84,75 +83,91 @@ onMounted(() => { + + diff --git a/extension/src/style.css b/extension/src/style.css index f691315..c0c9184 100644 --- a/extension/src/style.css +++ b/extension/src/style.css @@ -1,79 +1,54 @@ :root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + --apple-bg-base: #09090b; + --apple-bg-translucent: rgba(9, 9, 11, 0.7); + --apple-border: rgba(255, 255, 255, 0.08); + --apple-text-primary: #f8fafc; + --apple-text-secondary: #94a3b8; + --apple-accent-rose: #881337; + --apple-accent-blue: #3b82f6; + --apple-blur: blur(20px) saturate(180%); + + font-family: 'Inter', system-ui, -apple-system, sans-serif; line-height: 1.5; font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; } body { margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + padding: 0; + background-color: transparent; /* Allows glassmorphism */ + color: var(--apple-text-primary); + overflow: hidden; } -h1 { - font-size: 3.2em; - line-height: 1.1; +/* Custom Scrollbar - Apple Style */ +::-webkit-scrollbar { + width: 5px; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; +::-webkit-scrollbar-track { + background: transparent; } -.card { - padding: 2em; +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; } -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2); } -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +/* Base Transitions */ +.transition-apple { + transition: all 400ms cubic-bezier(0.23, 1, 0.32, 1); +} + +/* Glass Card */ +.glass-card { + background: var(--apple-bg-translucent); + backdrop-filter: var(--apple-blur); + -webkit-backdrop-filter: var(--apple-blur); + border: 1px solid var(--apple-border); } diff --git a/rules/frontend-guidelines.md b/rules/frontend-guidelines.md index 0e43095..57e8b88 100644 --- a/rules/frontend-guidelines.md +++ b/rules/frontend-guidelines.md @@ -4,7 +4,7 @@ globs: *.ts, *.tsx, *.vue, *.css, *.html --- # InsightReply 前端 UI/UX 编码规范与实现方案 -本文档基于 `ui-ux-pro-max` 高级设计理念以及现代前端工程化标准,定义了 InsightReply 浏览器插件和 Web 后台的开发规范。 +本文档基于 /Users/zs/.agents/skills `ui-ux-pro-max` 高级设计理念以及现代前端工程化标准,定义了 InsightReply 浏览器插件和 Web 后台的开发规范。 ## 一、 核心视觉原则 (ui-ux-pro-max 特性)