Skip to content

Commit 77868e3

Browse files
committed
Refactor AI provider handling and enhance popup functionality
- Removed the ChatPanel component as it is no longer needed. - Updated SummaryButton to improve error handling for API key and provider selection. - Enhanced SettingsPanel to support custom base URLs and model selection for local providers. - Implemented streaming request handling in background script for better performance. - Updated manifest to include localhost permissions for local development. - Added new styles for input fields in the settings panel. - Improved model fetching logic for various AI providers, including Google AI. - Adjusted Popup component to accommodate new settings functionality.
1 parent 1b11f6d commit 77868e3

File tree

14 files changed

+614
-274
lines changed

14 files changed

+614
-274
lines changed

build.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ export async function copyStaticFiles() {
3737

3838
// Copy static files
3939
await copyFile("src/static/styles/styles.css", "public/content/styles.css");
40-
await copyFile("src/static/popup/index.html", "public/popup/index.html");
41-
await copyFile("src/static/popup/popup.js", "public/popup/popup.js");
4240
await copyFile("src/static/manifest/manifest.json", "public/manifest.json");
4341
}
4442

@@ -67,6 +65,16 @@ async function buildExtension() {
6765
},
6866
});
6967

68+
// Build popup
69+
await build({
70+
entrypoints: ["./src/popup/index.tsx"],
71+
outdir: "./public/popup",
72+
minify: !isDev,
73+
define: {
74+
"import.meta.env.DEV": JSON.stringify(isDev),
75+
},
76+
});
77+
7078
// Copy static files
7179
await copyStaticFiles();
7280

src/background/index.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,104 @@ chrome.runtime.onInstalled.addListener(() => {
33
console.log("YouTube Summary Extension installed");
44
});
55

6-
// Listen for messages from content script
6+
// Handle regular (non-streaming) messages
77
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
88
if (request.type === "GET_SETTINGS") {
99
chrome.storage.sync.get(["apiKey", "selectedProvider"], (result) => {
1010
sendResponse(result);
1111
});
1212
return true; // Will respond asynchronously
1313
}
14+
15+
if (request.type === "PROXY_REQUEST") {
16+
const { url, options } = request;
17+
console.log("Proxying request to:", url);
18+
19+
// Check if this is a streaming request
20+
const isStreaming =
21+
options?.body && JSON.parse(options.body).stream === true;
22+
23+
if (isStreaming) {
24+
// For streaming requests, tell the content script to open a port
25+
sendResponse({ type: "USE_PORT" });
26+
return true;
27+
}
28+
29+
// Handle regular (non-streaming) requests
30+
fetch(url, options)
31+
.then(async (response) => {
32+
const data = await response.json();
33+
sendResponse({ ok: response.ok, data });
34+
})
35+
.catch((error) => {
36+
console.error("Proxy request failed:", error);
37+
sendResponse({ ok: false, error: error.message });
38+
});
39+
40+
return true; // Will respond asynchronously
41+
}
42+
});
43+
44+
// Handle streaming connections
45+
chrome.runtime.onConnect.addListener((port) => {
46+
if (port.name === "proxy-stream") {
47+
port.onMessage.addListener(async (request) => {
48+
const { url, options } = request;
49+
50+
try {
51+
const response = await fetch(url, options);
52+
if (!response.ok) {
53+
// For streaming responses, don't try to parse JSON on error
54+
port.postMessage({
55+
error: `Request failed with status ${response.status}`,
56+
});
57+
port.disconnect();
58+
return;
59+
}
60+
61+
const reader = response.body?.getReader();
62+
if (!reader) {
63+
port.postMessage({ error: "No response body" });
64+
port.disconnect();
65+
return;
66+
}
67+
68+
// Stream the response chunks
69+
while (true) {
70+
const { value, done } = await reader.read();
71+
if (done) break;
72+
73+
const chunk = new TextDecoder().decode(value);
74+
const lines = chunk
75+
.split("\n")
76+
.filter(
77+
(line) => line.trim() !== "" && line.trim() !== "data: [DONE]"
78+
);
79+
80+
for (const line of lines) {
81+
try {
82+
const trimmedLine = line.replace(/^data: /, "").trim();
83+
if (!trimmedLine) continue;
84+
85+
const parsed = JSON.parse(trimmedLine);
86+
if (parsed.choices?.[0]?.delta?.content) {
87+
port.postMessage({ chunk: parsed.choices[0].delta.content });
88+
}
89+
} catch (e) {
90+
console.warn("Failed to parse streaming response line:", e);
91+
}
92+
}
93+
}
94+
95+
// Signal stream completion
96+
port.postMessage({ done: true });
97+
port.disconnect();
98+
} catch (error: any) {
99+
// Type assertion for error
100+
console.error("Streaming request failed:", error);
101+
port.postMessage({ error: error?.message || "Unknown error occurred" });
102+
port.disconnect();
103+
}
104+
});
105+
}
14106
});

src/content/components/ChatPanel.tsx

Lines changed: 0 additions & 110 deletions
This file was deleted.

src/content/components/SummaryButton.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,18 @@ export const SummaryButton: React.FC = () => {
9797
throw new Error("Could not extract subtitles from this video");
9898
}
9999

100-
if (!selectedProvider || !apiKey) {
101-
throw new Error(
102-
"Please configure your API key and provider in settings"
103-
);
100+
if (!selectedProvider) {
101+
throw new Error("Please select an AI provider in settings");
102+
}
103+
104+
if (!selectedProvider.isLocal && !apiKey) {
105+
throw new Error("Please configure your API key in settings");
104106
}
105107

106108
const providerWithModel = {
107109
...selectedProvider,
108110
model: selectedModel || selectedProvider.model,
109-
apiKey,
111+
apiKey: apiKey || "",
110112
};
111113

112114
for await (const token of generateSummary(subtitles, providerWithModel)) {
@@ -159,10 +161,14 @@ export const SummaryButton: React.FC = () => {
159161
<div className="button-container">
160162
<button
161163
onClick={handleSummarize}
162-
disabled={isLoading || !selectedProvider || !apiKey}
164+
disabled={
165+
isLoading ||
166+
!selectedProvider ||
167+
(!selectedProvider.isLocal && !apiKey)
168+
}
163169
className="summarize-button"
164170
>
165-
{isLoading ? "Summarizing..." : "Summarize Video"}
171+
{isLoading ? "Summarizing..." : "Summarize"}
166172
</button>
167173
<button
168174
onClick={() => setShowSettings(true)}

0 commit comments

Comments
 (0)