Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions .github/workflows/extension-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,6 @@ Thank you to all our sponsors for your generous support!
<td align="center"><a href="https://www.jetbrains.com/">jetbrains</a></td>
<td align="center"><a href="https://taresky.com/">TARESKY</a></td>
<td align="center"><a href="https://x.com/Db9el25LULCBrcn">抹茶</a></td>
<td align="center"><a href="https://github.com/jtsang4">jtsang4</a></td>
</tr>
</table>
1 change: 1 addition & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,6 @@ sudo xattr -r -d com.apple.quarantine /YOUR_PATH/Huntly.app
<td align="center"><a href="https://www.jetbrains.com/">jetbrains</a></td>
<td align="center"><a href="https://taresky.com/">TARESKY</a></td>
<td align="center"><a href="https://x.com/Db9el25LULCBrcn">抹茶</a></td>
<td align="center"><a href="https://github.com/jtsang4">jtsang4</a></td>
</tr>
</table>
4 changes: 2 additions & 2 deletions app/extension/public/manifest.json
Original file line number Diff line number Diff line change
@@ -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
Expand Down
75 changes: 44 additions & 31 deletions app/extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -62,22 +41,22 @@ 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,
task.title, // 传递文章标题
// onData callback - 接收流式数据
(data: string, taskId: string) => {
accumulatedContent += data;

// 发送流式数据到预览页面
try {
chrome.tabs.sendMessage(task.tabId, {
Expand Down Expand Up @@ -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',
Expand All @@ -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();
});
}

Expand Down Expand Up @@ -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 = {
Expand Down
19 changes: 17 additions & 2 deletions app/extension/src/components/AIToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -98,6 +100,7 @@ export const AIToolbar: React.FC<AIToolbarProps> = ({
externalShortcuts,
externalModels,
initialSelectedModel,
hideHuntlyAI = false,
}) => {
// Determine if using external data
const useExternalShortcuts = !!externalShortcuts;
Expand Down Expand Up @@ -152,6 +155,18 @@ export const AIToolbar: React.FC<AIToolbarProps> = ({
}
}, [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 | HTMLElement>(null);
const [modelAnchorEl, setModelAnchorEl] = useState<null | HTMLElement>(null);
Expand Down Expand Up @@ -343,8 +358,8 @@ export const AIToolbar: React.FC<AIToolbarProps> = ({
(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');
Expand Down
1 change: 1 addition & 0 deletions app/extension/src/components/ArticlePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const ArticlePreview: React.FC<ArticlePreviewProps> = ({
title: isSnippetMode ? "" : page.title,
contentType: isSnippetMode ? 4 : undefined,
selectedModel: selectedModel,
skipPreview: true, // Preview is already open, skip shortcuts_preview message
},
});
}, [isProcessing, currentTaskId, page, isSnippetMode]);
Expand Down
27 changes: 21 additions & 6 deletions app/extension/src/popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const Popup = () => {
const [storageSettings, setStorageSettings] = useState<StorageSettings>(null);
const [username, setUsername] = useState<string>(null);
const [loadingUser, setLoadingUser] = useState(true);
const [serverConnectionFailed, setServerConnectionFailed] = useState(false);
const [page, setPage] = useState<PageModel>(null);
const [autoSavedPageId, setAutoSavedPageId] = useState<number>(0);
const [articleOperateResult, setArticleOperateResult] = useState<PageOperateResult>(null);
Expand Down Expand Up @@ -151,16 +152,21 @@ 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);

loadPageInfo();
}).catch(() => {
setUsername(null);
// Server connection failed - still show article preview in read-only mode
setServerConnectionFailed(true);
loadPageInfoOnly();
}).finally(() => {
setLoadingUser(false);
});
Expand Down Expand Up @@ -551,9 +557,9 @@ const Popup = () => {
<CircularProgress/>
</div>
}
{/* Server configured but not signed in */}
{/* Server configured but not signed in and server is reachable */}
{
!loadingUser && storageSettings?.serverUrl && !username && <div>
!loadingUser && storageSettings?.serverUrl && !username && !serverConnectionFailed && <div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When serverConnectionFailed is true, the sign-in CTA is hidden entirely; if the outage is transient, the user can’t retry sign-in without closing/reopening the popup. Consider whether a retry/sign-in path should still be available in this state.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

<div className={'mt-5'}>
<Alert severity={'info'}>Please sign in to start.</Alert>
</div>
Expand All @@ -562,9 +568,17 @@ const Popup = () => {
</div>
</div>
}
{/* 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) && <div>
!loadingUser && (username || !storageSettings?.serverUrl || serverConnectionFailed) && <div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including serverConnectionFailed in this branch means the RSS subscription UI will still render and call server APIs (previewFeed/subscribeFeed), which will likely error and may confuse users. It might be worth gating RSS subscription similarly or surfacing a clearer “server required” message.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

{/* Server connection failed warning */}
{serverConnectionFailed && (
<div className={'mb-2'}>
<Alert severity={'warning'}>
Cannot connect to Huntly server.
</Alert>
</div>
)}
{/* RSS Feed Subscription Interface */}
{checkingRssFeed && (
<div className={'flex justify-center items-center h-[120px]'}>
Expand Down Expand Up @@ -662,8 +676,8 @@ const Popup = () => {
<div>
<div className={'flex items-center'}>
<TextField value={activePage.url} size={"small"} fullWidth={true} disabled={true}/>
{/* Only show action buttons when server is configured and not on Huntly site */}
{storageSettings?.serverUrl && !isHuntlySite && <div className={'grow shrink-0 pl-2'}>
{/* Only show action buttons when server is configured, connected, and not on Huntly site */}
{storageSettings?.serverUrl && !isHuntlySite && !serverConnectionFailed && <div className={'grow shrink-0 pl-2'}>
{
activeOperateResult?.readLater ? (
<Tooltip title={"Remove from read later"}>
Expand Down Expand Up @@ -784,6 +798,7 @@ const Popup = () => {
onShortcutClick={handleAIShortcutClick}
isProcessing={processingShortcut}
compact={true}
hideHuntlyAI={serverConnectionFailed}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of hideHuntlyAI here, but the preview window (ArticlePreview) also renders AIToolbar and may still show huntly-server models even when the popup detected a connection failure. If the intent is to hide Huntly AI whenever the server isn’t reachable, you may need to propagate that state into the preview as well.

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

/>
</div>
</div>
Expand Down
Loading