Skip to content

Commit d1e4905

Browse files
authored
Merge pull request #361 from ujiro99/copilot/update-ai-prompt-icon
デフォルトAI-promptコマンドのGeminiアイコンURLをSingle Source of Truthに統一
2 parents 2ec1054 + 7d9b812 commit d1e4905

6 files changed

Lines changed: 153 additions & 168 deletions

File tree

packages/extension/scripts/vite.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from "fs"
12
import path from "path"
23
import { defineConfig } from "vite"
34
import packageJson from "../package.json"
@@ -8,6 +9,10 @@ export default defineConfig({
89
define: {
910
__APP_NAME__: JSON.stringify(packageJson.name),
1011
__APP_VERSION__: JSON.stringify(packageJson.version),
12+
__AI_SERVICES_JSON__: fs.readFileSync(
13+
path.resolve(__dirname, "../../hub/public/data/ai-services.json"),
14+
"utf-8",
15+
),
1116
},
1217
resolve: {
1318
alias: {
Lines changed: 7 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,20 @@
1-
import { SelectorType } from "@/const"
21
import { HUB_URL } from "@/const"
32
import { Storage, LOCAL_STORAGE_KEY } from "@/services/storage"
4-
import type { Caches } from "@/types"
3+
import type { Caches, AiService } from "@/types"
4+
import {
5+
normalizeServices,
6+
AI_SERVICES_FALLBACK,
7+
getAiServicesFallback,
8+
} from "@/services/aiPromptFallback"
59

6-
/**
7-
* Defines selectors and configuration for a supported AI service.
8-
*/
9-
export type AiService = {
10-
id: string
11-
name: string
12-
url: string
13-
faviconUrl: string
14-
inputSelectors: string[]
15-
submitSelectors: string[]
16-
selectorType: SelectorType
17-
}
10+
export { getAiServicesFallback }
1811

1912
/** External endpoint URL for AI service config data. */
2013
const AI_SERVICES_URL = `${HUB_URL}/data/ai-services.json`
2114

2215
/** Today's date string "YYYY-MM-DD" used as cache TTL key. */
2316
const todayStr = (): string => new Date().toISOString().slice(0, 10)
2417

25-
/**
26-
* Normalize raw JSON data fetched from the external endpoint into AiService[].
27-
* - Items that are missing required fields (id, url, inputSelectors, submitSelectors) are silently skipped.
28-
* - The external JSON may omit `selectorType`, defaulting to `CSS`.
29-
*/
30-
const normalizeServices = (raw: unknown[]): AiService[] => {
31-
const results: AiService[] = []
32-
for (const item of raw) {
33-
const s = item as Partial<AiService>
34-
if (
35-
!s.id ||
36-
!s.url ||
37-
!Array.isArray(s.inputSelectors) ||
38-
!Array.isArray(s.submitSelectors) ||
39-
s.inputSelectors.length === 0 ||
40-
s.submitSelectors.length === 0
41-
) {
42-
console.warn("Skipping invalid AI service entry:", s)
43-
continue
44-
}
45-
results.push({
46-
id: s.id,
47-
name: s.name ?? s.id,
48-
url: s.url,
49-
faviconUrl: s.faviconUrl ?? "",
50-
inputSelectors: s.inputSelectors,
51-
submitSelectors: s.submitSelectors,
52-
selectorType: s.selectorType ?? SelectorType.css,
53-
})
54-
}
55-
return results
56-
}
57-
58-
/**
59-
* List of supported AI services with their DOM selectors.
60-
* Built from the hub's ai-services.json at compile time.
61-
* Used as fallback when the external fetch fails and no cache is available.
62-
* Selector arrays are tried in order, using the first one that matches.
63-
*/
64-
const AI_SERVICES_FALLBACK: AiService[] =
65-
normalizeServices(__AI_SERVICES_JSON__)
66-
6718
/**
6819
* Retrieve AI service definitions.
6920
* Strategy:
@@ -126,9 +77,3 @@ export const findAiService = async (
12677
const services = await getAiServices()
12778
return services.find((s) => s.id === id)
12879
}
129-
130-
/**
131-
* Return the list of AI services synchronously using only the hardcoded fallback.
132-
* Used in UI contexts where async is not available (e.g. option section rendering).
133-
*/
134-
export const getAiServicesFallback = (): AiService[] => AI_SERVICES_FALLBACK
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { SelectorType } from "@/const"
2+
import type { AiService } from "@/types"
3+
4+
/**
5+
* Normalize raw JSON data fetched from the external endpoint into AiService[].
6+
* Items missing required fields are silently skipped.
7+
*/
8+
export const normalizeServices = (raw: unknown[]): AiService[] => {
9+
const results: AiService[] = []
10+
for (const item of raw) {
11+
const s = item as Partial<AiService>
12+
if (
13+
!s.id ||
14+
!s.url ||
15+
!Array.isArray(s.inputSelectors) ||
16+
!Array.isArray(s.submitSelectors) ||
17+
s.inputSelectors.length === 0 ||
18+
s.submitSelectors.length === 0
19+
) {
20+
console.warn("Skipping invalid AI service entry:", s)
21+
continue
22+
}
23+
results.push({
24+
id: s.id,
25+
name: s.name ?? s.id,
26+
url: s.url,
27+
faviconUrl: s.faviconUrl ?? "",
28+
inputSelectors: s.inputSelectors,
29+
submitSelectors: s.submitSelectors,
30+
selectorType: s.selectorType ?? SelectorType.css,
31+
})
32+
}
33+
return results
34+
}
35+
36+
/**
37+
* List of supported AI services built from the hub's ai-services.json at compile time.
38+
* Used as fallback when the external fetch fails and no cache is available.
39+
*/
40+
export const AI_SERVICES_FALLBACK: AiService[] =
41+
normalizeServices(__AI_SERVICES_JSON__)
42+
43+
/**
44+
* Return the list of AI services synchronously using only the hardcoded fallback.
45+
* Safe to use in Node.js / non-browser contexts (no chrome dependency).
46+
*/
47+
export const getAiServicesFallback = (): AiService[] => AI_SERVICES_FALLBACK

packages/extension/src/services/option/defaultSettings.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest"
22
import { DefaultCommands, getDefaultCommands } from "./defaultSettings"
33
import { isLinkCommand } from "@/lib/utils"
44
import { INSERT, toInsertTemplate } from "@/services/pageAction"
5+
import { getAiServicesFallback } from "@/services/aiPrompt"
56

67
const SYM_SELECTED_TEXT = toInsertTemplate(INSERT.SELECTED_TEXT)
78
const SYM_URL = toInsertTemplate(INSERT.URL)
@@ -227,6 +228,26 @@ describe("getDefaultCommands", () => {
227228
}
228229
})
229230

231+
it("DS-23: all AI_PROMPT commands with serviceId 'gemini' should use the current Gemini icon URL", () => {
232+
const geminiService = getAiServicesFallback().find((s) => s.id === "gemini")
233+
expect(geminiService, "Gemini service must exist in ai-services.json").toBeDefined()
234+
const expectedIconUrl = geminiService!.faviconUrl
235+
for (const locale of ALL_LOCALES) {
236+
const commands = getDefaultCommands(locale)
237+
const geminiAiCmds = commands.filter(
238+
(c) =>
239+
(c as any).openMode === "aiPrompt" &&
240+
(c as any).aiPromptOption?.serviceId === "gemini",
241+
)
242+
for (const cmd of geminiAiCmds) {
243+
expect(
244+
(cmd as any).iconUrl,
245+
`Gemini AI prompt command "${(cmd as any).title}" in locale "${locale}" should use the current Gemini icon URL`,
246+
).toBe(expectedIconUrl)
247+
}
248+
}
249+
})
250+
230251
it("DS-22: page summary and YouTube summary commands should use {{Url}} not {{SelectedText}}", () => {
231252
for (const locale of ALL_LOCALES) {
232253
const commands = getDefaultCommands(locale)

0 commit comments

Comments
 (0)