mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-21 01:39:48 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa1a967a9f | |||
| d643bad53c | |||
| 0d977787b5 | |||
| 516803feb4 |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.114.0"
|
"version": "1.114.1"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.114.1 - 2026-05-08
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- `baoyu-danger-gemini-web`: restore generated-image extraction for current Gemini Web responses where image URLs appear as `https://lh3.googleusercontent.com/gg-dl/` without the legacy generated-image markers. Adds regression coverage for the fallback response shape. (by @evilstar2016)
|
||||||
|
|
||||||
## 1.114.0 - 2026-05-05
|
## 1.114.0 - 2026-05-05
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.114.1 - 2026-05-08
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- `baoyu-danger-gemini-web`:修复当前 Gemini Web 响应中生成图 URL 以 `https://lh3.googleusercontent.com/gg-dl/` 形式出现、但不再包含旧版生成图 marker 时的图片提取失败问题。补充该响应形态的回归测试。 (by @evilstar2016)
|
||||||
|
|
||||||
## 1.114.0 - 2026-05-05
|
## 1.114.0 - 2026-05-05
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { collect_generated_image_urls_from_response_parts } from "./client.ts";
|
||||||
|
|
||||||
|
test("response part fallback finds generated images when legacy generated markers are absent", () => {
|
||||||
|
const generatedUrl = "https://lh3.googleusercontent.com/gg-dl/example-generated-image";
|
||||||
|
const initialCandidate = ["rcid-1", ["image generated successfully"]];
|
||||||
|
const imageCandidate = [
|
||||||
|
"rcid-1",
|
||||||
|
["image generated successfully"],
|
||||||
|
{ nestedPayload: [{ media: generatedUrl }] },
|
||||||
|
];
|
||||||
|
const responseJson = [
|
||||||
|
["wrb.fr", null, JSON.stringify([null, [], null, null, [initialCandidate]])],
|
||||||
|
["wrb.fr", null, JSON.stringify([null, [], null, null, [imageCandidate]])],
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.equal(initialCandidate[12], undefined);
|
||||||
|
assert.equal(
|
||||||
|
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(String(initialCandidate[1]?.[0])),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.deepEqual(collect_generated_image_urls_from_response_parts(responseJson, 0, 0), [generatedUrl]);
|
||||||
|
});
|
||||||
@@ -37,6 +37,8 @@ type InitOptions = {
|
|||||||
|
|
||||||
type RequestKwargs = RequestInit & { timeout_ms?: number };
|
type RequestKwargs = RequestInit & { timeout_ms?: number };
|
||||||
|
|
||||||
|
const GENERATED_IMAGE_URL_PREFIX = 'https://lh3.googleusercontent.com/gg-dl/';
|
||||||
|
|
||||||
function normalize_headers(h?: HeadersInit): Record<string, string> {
|
function normalize_headers(h?: HeadersInit): Record<string, string> {
|
||||||
if (!h) return {};
|
if (!h) return {};
|
||||||
if (Array.isArray(h)) return Object.fromEntries(h.map(([k, v]) => [k, v]));
|
if (Array.isArray(h)) return Object.fromEntries(h.map(([k, v]) => [k, v]));
|
||||||
@@ -78,6 +80,59 @@ function collect_strings(root: unknown, accept: (s: string) => boolean, limit: n
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collect_generated_image_urls(root: unknown, limit: number = 4): string[] {
|
||||||
|
return collect_strings(root, (s) => s.startsWith(GENERATED_IMAGE_URL_PREFIX), limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_response_part_body(part: unknown): unknown[] | null {
|
||||||
|
if (!Array.isArray(part)) return null;
|
||||||
|
const part_body = get_nested_value<string | null>(part, [2], null);
|
||||||
|
if (!part_body) return null;
|
||||||
|
try {
|
||||||
|
const part_json = JSON.parse(part_body) as unknown;
|
||||||
|
return Array.isArray(part_json) ? part_json : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_generated_image_part(
|
||||||
|
response_json: unknown[],
|
||||||
|
body_index: number,
|
||||||
|
candidate_index: number,
|
||||||
|
limit: number = 4,
|
||||||
|
): { body: unknown[]; urls: string[] } | null {
|
||||||
|
for (let part_index = body_index; part_index < response_json.length; part_index++) {
|
||||||
|
const part_json = parse_response_part_body(response_json[part_index]);
|
||||||
|
if (!part_json) continue;
|
||||||
|
const cand = get_nested_value<unknown>(part_json, [4, candidate_index], null);
|
||||||
|
if (!cand) continue;
|
||||||
|
const urls = collect_generated_image_urls(cand, limit);
|
||||||
|
if (urls.length > 0) return { body: part_json, urls };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collect_generated_image_urls_from_response_parts(
|
||||||
|
response_json: unknown[],
|
||||||
|
body_index: number,
|
||||||
|
candidate_index: number,
|
||||||
|
limit: number = 4,
|
||||||
|
): string[] {
|
||||||
|
return find_generated_image_part(response_json, body_index, candidate_index, limit)?.urls ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function push_generated_images(
|
||||||
|
generated_images: GeneratedImage[],
|
||||||
|
urls: string[],
|
||||||
|
proxy: string | null,
|
||||||
|
cookies: Record<string, string>,
|
||||||
|
): void {
|
||||||
|
for (const url of urls) {
|
||||||
|
generated_images.push(new GeneratedImage(url, '[Generated Image]', '', proxy, cookies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class GeminiClient extends GemMixin {
|
export class GeminiClient extends GemMixin {
|
||||||
public cookies: Record<string, string> = {};
|
public cookies: Record<string, string> = {};
|
||||||
public proxy: string | null = null;
|
public proxy: string | null = null;
|
||||||
@@ -404,24 +459,8 @@ export class GeminiClient extends GemMixin {
|
|||||||
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(text);
|
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(text);
|
||||||
|
|
||||||
if (wants_generated) {
|
if (wants_generated) {
|
||||||
let img_body: unknown[] | null = null;
|
const image_part = find_generated_image_part(response_json as unknown[], body_index, candidate_index, 1);
|
||||||
for (let part_index = body_index; part_index < (response_json as unknown[]).length; part_index++) {
|
const img_body = image_part?.body ?? null;
|
||||||
const part = (response_json as unknown[])[part_index];
|
|
||||||
if (!Array.isArray(part)) continue;
|
|
||||||
const part_body = get_nested_value<string | null>(part, [2], null);
|
|
||||||
if (!part_body) continue;
|
|
||||||
try {
|
|
||||||
const part_json = JSON.parse(part_body) as unknown[];
|
|
||||||
const cand = get_nested_value<unknown>(part_json, [4, candidate_index], null);
|
|
||||||
if (!cand) continue;
|
|
||||||
|
|
||||||
const urls = collect_strings(cand, (s) => s.startsWith('https://lh3.googleusercontent.com/gg-dl/'), 1);
|
|
||||||
if (urls.length > 0) {
|
|
||||||
img_body = part_json;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!img_body) {
|
if (!img_body) {
|
||||||
throw new ImageGenerationError(
|
throw new ImageGenerationError(
|
||||||
@@ -452,13 +491,22 @@ export class GeminiClient extends GemMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (generated_images.length === 0) {
|
if (generated_images.length === 0) {
|
||||||
const urls = collect_strings(img_candidate, (s) => s.startsWith('https://lh3.googleusercontent.com/gg-dl/'), 4);
|
push_generated_images(generated_images, collect_generated_image_urls(img_candidate), this.proxy, this.cookies);
|
||||||
for (const url of urls) {
|
|
||||||
generated_images.push(new GeneratedImage(url, '[Generated Image]', '', this.proxy, this.cookies));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: unconditionally scan all response parts for generated image URLs.
|
||||||
|
// The `wants_generated` detection above relies on `candidate[12][7][0]` and an old
|
||||||
|
// `googleusercontent.com/image_generation_content/` URL pattern, both of which no
|
||||||
|
// longer appear in the current Gemini Web API response format.
|
||||||
|
// When Gemini does generate images, their URLs now start with
|
||||||
|
// `https://lh3.googleusercontent.com/gg-dl/` and are present somewhere in the
|
||||||
|
// response parts — this fallback finds them so images are not silently dropped.
|
||||||
|
if (generated_images.length === 0) {
|
||||||
|
const urls = collect_generated_image_urls_from_response_parts(response_json as unknown[], body_index, candidate_index);
|
||||||
|
push_generated_images(generated_images, urls, this.proxy, this.cookies);
|
||||||
|
}
|
||||||
|
|
||||||
out.push(new Candidate({ rcid, text, thoughts, web_images, generated_images }));
|
out.push(new Candidate({ rcid, text, thoughts, web_images, generated_images }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user