diff --git a/.github/workflows/extension-release.yml b/.github/workflows/extension-release.yml index 3babac8..70fe15a 100644 --- a/.github/workflows/extension-release.yml +++ b/.github/workflows/extension-release.yml @@ -81,20 +81,11 @@ jobs: CI: false EXTENSION_VERSION: ${{ needs.create-release.outputs.version }} - - name: Create extension bundle (Firefox) - run: | - cd app/extension - yarn build:firefox - env: - CI: false - EXTENSION_VERSION: ${{ needs.create-release.outputs.version }} - - name: Package release files run: | mkdir -p release cd app/extension zip -r ../../release/huntly-chrome-extension-${{ needs.create-release.outputs.version }}.zip ./dist/* - zip -r ../../release/huntly-firefox-extension-${{ needs.create-release.outputs.version }}.zip ./dist-firefox/* - name: Upload Chrome extension to release uses: actions/upload-release-asset@v1 @@ -106,13 +97,3 @@ jobs: asset_name: huntly-chrome-extension-${{ needs.create-release.outputs.version }}.zip asset_content_type: application/zip - - name: Upload Firefox extension to release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.release_upload_url }} - asset_path: release/huntly-firefox-extension-${{ needs.create-release.outputs.version }}.zip - asset_name: huntly-firefox-extension-${{ needs.create-release.outputs.version }}.zip - asset_content_type: application/zip - diff --git a/README.md b/README.md index 61db3cb..e3a9190 100644 --- a/README.md +++ b/README.md @@ -149,5 +149,6 @@ Thank you to all our sponsors for your generous support! jetbrains TARESKY 抹茶 + jtsang4 \ No newline at end of file diff --git a/README.zh.md b/README.zh.md index ef4b488..0b2790b 100644 --- a/README.zh.md +++ b/README.zh.md @@ -149,5 +149,6 @@ sudo xattr -r -d com.apple.quarantine /YOUR_PATH/Huntly.app jetbrains TARESKY 抹茶 + jtsang4 \ No newline at end of file diff --git a/app/extension/public/manifest.json b/app/extension/public/manifest.json index 21ef43b..72a3cc8 100644 --- a/app/extension/public/manifest.json +++ b/app/extension/public/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, "name": "Huntly", - "description": "Huntly - Automatic saving browsed contents", - "version": "0.5.1", + "description": "Huntly - AI shortcuts (summarization, bilingual translation, key points extraction, etc.), auto-saves web pages and tweets, and provides immersive article preview.", + "version": "0.5.2", "options_ui": { "page": "options.html", "open_in_tab": true diff --git a/app/extension/src/background.ts b/app/extension/src/background.ts index d3cb132..17857cd 100644 --- a/app/extension/src/background.ts +++ b/app/extension/src/background.ts @@ -27,32 +27,11 @@ function cancelVercelAITask(taskId: string): boolean { return false; } -function startProcessingWithShortcuts(task: any, shortcuts: any[]) { +function startProcessingWithShortcuts(task: any, shortcuts: any[], skipPreview: boolean = false) { if (!task) return; - - chrome.tabs.sendMessage(task.tabId, { - type: 'shortcuts_preview', - payload: { - shortcuts: shortcuts, - taskId: task.taskId, - page: { - title: task.title, - content: task.content, - url: task.url, - description: "", - thumbUrl: "", - author: "", - siteName: "", - language: "", - category: "", - isLiked: false, - isFavorite: false, - domain: "", - faviconUrl: "", - contentType: task.contentType, // Pass contentType for snippet mode - } - } - }, function(response) { + + // Function to start the actual processing + const startProcessing = () => { // 发送处理开始的消息 chrome.tabs.sendMessage(task.tabId, { type: 'shortcuts_processing_start', @@ -62,14 +41,14 @@ function startProcessingWithShortcuts(task: any, shortcuts: any[]) { taskId: task.taskId } }); - + let accumulatedContent = ""; - + // 使用新的 SSERequestManager 处理文章内容 sseRequestManager.processContentWithShortcutStream( task.taskId, task.tabId, - task.content, + task.content, task.shortcutId, task.shortcutName, task.url, @@ -77,7 +56,7 @@ function startProcessingWithShortcuts(task: any, shortcuts: any[]) { // onData callback - 接收流式数据 (data: string, taskId: string) => { accumulatedContent += data; - + // 发送流式数据到预览页面 try { chrome.tabs.sendMessage(task.tabId, { @@ -114,7 +93,7 @@ function startProcessingWithShortcuts(task: any, shortcuts: any[]) { // onError callback - 处理错误 (error: any, taskId: string) => { console.error("Error processing with shortcut for task:", taskId, error); - + try { chrome.tabs.sendMessage(task.tabId, { type: 'shortcuts_process_error', @@ -129,6 +108,39 @@ function startProcessingWithShortcuts(task: any, shortcuts: any[]) { } } ); + }; + + // If preview is already open, skip sending shortcuts_preview message + if (skipPreview) { + startProcessing(); + return; + } + + // Send shortcuts_preview to open the preview window first + chrome.tabs.sendMessage(task.tabId, { + type: 'shortcuts_preview', + payload: { + shortcuts: shortcuts, + taskId: task.taskId, + page: { + title: task.title, + content: task.content, + url: task.url, + description: "", + thumbUrl: "", + author: "", + siteName: "", + language: "", + category: "", + isLiked: false, + isFavorite: false, + domain: "", + faviconUrl: "", + contentType: task.contentType, // Pass contentType for snippet mode + } + } + }, function(response) { + startProcessing(); }); } @@ -304,7 +316,8 @@ chrome.runtime.onMessage.addListener(function (msg: Message, sender, sendRespons contentType: msg.payload.contentType, }; const shortcuts = msg.payload.shortcuts || []; - startProcessingWithShortcuts(task, shortcuts); + const skipPreview = msg.payload.skipPreview || false; + startProcessingWithShortcuts(task, shortcuts, skipPreview); } else { // Use Vercel AI SDK for other providers const task = { diff --git a/app/extension/src/components/AIToolbar.tsx b/app/extension/src/components/AIToolbar.tsx index fae56f4..866be43 100644 --- a/app/extension/src/components/AIToolbar.tsx +++ b/app/extension/src/components/AIToolbar.tsx @@ -76,6 +76,8 @@ export interface AIToolbarProps { externalModels?: ExternalModelsData; /** Initial selected model (used when auto-executing from popup) */ initialSelectedModel?: ModelItem | null; + /** Hide Huntly AI option (e.g., when server is not connected) */ + hideHuntlyAI?: boolean; } // Gradient definition for AI icon @@ -98,6 +100,7 @@ export const AIToolbar: React.FC = ({ externalShortcuts, externalModels, initialSelectedModel, + hideHuntlyAI = false, }) => { // Determine if using external data const useExternalShortcuts = !!externalShortcuts; @@ -152,6 +155,18 @@ export const AIToolbar: React.FC = ({ } }, [initialSelectedModel]); + // Switch away from Huntly AI if it's hidden + useEffect(() => { + if (hideHuntlyAI && selectedModel?.provider === 'huntly-server') { + const nonHuntlyModel = models.find(m => m.provider !== 'huntly-server'); + if (nonHuntlyModel) { + setSelectedModel(nonHuntlyModel); + } else { + setSelectedModel(null); + } + } + }, [hideHuntlyAI, selectedModel, models]); + // Menu state const [shortcutAnchorEl, setShortcutAnchorEl] = useState(null); const [modelAnchorEl, setModelAnchorEl] = useState(null); @@ -343,8 +358,8 @@ export const AIToolbar: React.FC = ({ (showUserSystemPrompts && (userPrompts.length > 0 || systemPrompts.length > 0)); const hasModels = models.length > 0; - // Group models by provider - only show Huntly AI when huntlyShortcutsEnabled is true - const huntlyModels = huntlyShortcutsEnabled + // Group models by provider - only show Huntly AI when huntlyShortcutsEnabled is true and not hidden + const huntlyModels = (huntlyShortcutsEnabled && !hideHuntlyAI) ? models.filter(m => m.provider === 'huntly-server') : []; const otherModels = models.filter(m => m.provider !== 'huntly-server'); diff --git a/app/extension/src/components/ArticlePreview.tsx b/app/extension/src/components/ArticlePreview.tsx index d8a8ac1..e7fe206 100644 --- a/app/extension/src/components/ArticlePreview.tsx +++ b/app/extension/src/components/ArticlePreview.tsx @@ -178,6 +178,7 @@ export const ArticlePreview: React.FC = ({ title: isSnippetMode ? "" : page.title, contentType: isSnippetMode ? 4 : undefined, selectedModel: selectedModel, + skipPreview: true, // Preview is already open, skip shortcuts_preview message }, }); }, [isProcessing, currentTaskId, page, isSnippetMode]); diff --git a/app/extension/src/popup.tsx b/app/extension/src/popup.tsx index 8806b81..856d801 100644 --- a/app/extension/src/popup.tsx +++ b/app/extension/src/popup.tsx @@ -75,6 +75,7 @@ const Popup = () => { const [storageSettings, setStorageSettings] = useState(null); const [username, setUsername] = useState(null); const [loadingUser, setLoadingUser] = useState(true); + const [serverConnectionFailed, setServerConnectionFailed] = useState(false); const [page, setPage] = useState(null); const [autoSavedPageId, setAutoSavedPageId] = useState(0); const [articleOperateResult, setArticleOperateResult] = useState(null); @@ -151,9 +152,11 @@ const Popup = () => { if (!settings.serverUrl) { // No server enabled - still load page info for parsing, but skip server-related operations setLoadingUser(false); + setServerConnectionFailed(false); loadPageInfoOnly(); } else { setLoadingUser(true); + setServerConnectionFailed(false); getLoginUserInfo().then((data) => { const result = JSON.parse(data); setUsername(result.username); @@ -161,6 +164,9 @@ const Popup = () => { loadPageInfo(); }).catch(() => { setUsername(null); + // Server connection failed - still show article preview in read-only mode + setServerConnectionFailed(true); + loadPageInfoOnly(); }).finally(() => { setLoadingUser(false); }); @@ -551,9 +557,9 @@ const Popup = () => { } - {/* Server configured but not signed in */} + {/* Server configured but not signed in and server is reachable */} { - !loadingUser && storageSettings?.serverUrl && !username &&
+ !loadingUser && storageSettings?.serverUrl && !username && !serverConnectionFailed &&
Please sign in to start.
@@ -562,9 +568,17 @@ const Popup = () => {
} - {/* Server configured and signed in, or no server configured (read-only mode) */} + {/* Server configured and signed in, no server configured (read-only mode), or server connection failed */} { - !loadingUser && (username || !storageSettings?.serverUrl) &&
+ !loadingUser && (username || !storageSettings?.serverUrl || serverConnectionFailed) &&
+ {/* Server connection failed warning */} + {serverConnectionFailed && ( +
+ + Cannot connect to Huntly server. + +
+ )} {/* RSS Feed Subscription Interface */} {checkingRssFeed && (
@@ -662,8 +676,8 @@ const Popup = () => {
- {/* Only show action buttons when server is configured and not on Huntly site */} - {storageSettings?.serverUrl && !isHuntlySite &&
+ {/* Only show action buttons when server is configured, connected, and not on Huntly site */} + {storageSettings?.serverUrl && !isHuntlySite && !serverConnectionFailed &&
{ activeOperateResult?.readLater ? ( @@ -784,6 +798,7 @@ const Popup = () => { onShortcutClick={handleAIShortcutClick} isProcessing={processingShortcut} compact={true} + hideHuntlyAI={serverConnectionFailed} />