From ac139b79a76877bad2e9fe71d7b43d6919ae3c26 Mon Sep 17 00:00:00 2001 From: azu Date: Thu, 15 Jan 2026 15:44:02 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Claude=20Code=E9=80=A3=E6=90=BA?= =?UTF-8?q?=E3=81=AB=E3=82=BF=E3=82=B0=E6=8F=90=E6=A1=88=E3=81=A8=E9=96=A2?= =?UTF-8?q?=E9=80=A3=E3=82=A2=E3=82=A4=E3=83=86=E3=83=A0=E9=80=A3=E6=90=BA?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prompt関数にrelatedItemsとavailableTagsを渡すように変更 - Claude Codeの出力をJSON形式(comment, tags)で解析 - 関連URL追加時に自動で再推論を実行 - 推論結果にタグを表示し、挿入時にタグもマージ - ClaudeCodePreviewにタグ表示エリアを追加 Co-Authored-By: Claude Opus 4.5 --- css/index.css | 8 +++++ src/browser/Action/ServiceAction.js | 28 ++++++++++++++--- src/browser/App.js | 25 +++++++++++++-- src/browser/Store/ServiceStore.js | 36 ++++++++++++++-------- src/browser/component/ClaudeCodeButton.js | 14 +++++---- src/browser/component/ClaudeCodePreview.js | 7 +++-- 6 files changed, 91 insertions(+), 27 deletions(-) diff --git a/css/index.css b/css/index.css index 14514da..f3da6e3 100644 --- a/css/index.css +++ b/css/index.css @@ -337,3 +337,11 @@ .ClaudeCodePreview--error .ClaudeCodePreview-content { color: #c62828; } + +.ClaudeCodePreview-tags { + margin-top: 8px; + padding-top: 8px; + border-top: 1px dashed #ccc; + font-size: 12px; + color: #666; +} diff --git a/src/browser/Action/ServiceAction.js b/src/browser/Action/ServiceAction.js index 50d4cfd..fd97e31 100644 --- a/src/browser/Action/ServiceAction.js +++ b/src/browser/Action/ServiceAction.js @@ -195,7 +195,7 @@ export default class ServiceAction extends Action { } // Claude Code関連アクション - runClaudeCode(url, title, config) { + runClaudeCode(url, title, config, relatedItems = [], availableTags = []) { if (!config?.enabled) return; if (!fs.existsSync(config.cliPath)) return; if (!fs.existsSync(config.workDir)) { @@ -207,7 +207,7 @@ export default class ServiceAction extends Action { const prompt = typeof config.prompt === "function" - ? config.prompt({ url, title }) + ? config.prompt({ url, title, relatedItems, availableTags }) : `${config.prompt}\n\nURL: ${url}\nTitle: ${title}`; const args = []; @@ -234,9 +234,27 @@ export default class ServiceAction extends Action { claudeProcess.on("close", (code) => { if (code === 0 && stdout) { - const match = stdout.match(/```(?:markdown)?\s*([\s\S]*?)```/); - const result = match ? match[1].trim() : stdout.trim(); - this.dispatch(keys.claudeCodeComplete, { url, result }); + // JSONコードブロックを抽出 + const jsonMatch = stdout.match(/```(?:json)?\s*([\s\S]*?)```/); + let parsedResult = { comment: "", tags: [] }; + + if (jsonMatch) { + try { + parsedResult = JSON.parse(jsonMatch[1].trim()); + } catch (e) { + // フォールバック: 従来のmarkdown形式 + const markdownMatch = stdout.match(/```(?:markdown)?\s*([\s\S]*?)```/); + parsedResult.comment = markdownMatch ? markdownMatch[1].trim() : stdout.trim(); + } + } else { + parsedResult.comment = stdout.trim(); + } + + this.dispatch(keys.claudeCodeComplete, { + url, + comment: parsedResult.comment, + tags: Array.isArray(parsedResult.tags) ? parsedResult.tags : [] + }); } else { this.dispatch(keys.claudeCodeError, { url, error: stderr || `Exit code: ${code}` }); } diff --git a/src/browser/App.js b/src/browser/App.js index e5568a8..7f8aac9 100644 --- a/src/browser/App.js +++ b/src/browser/App.js @@ -187,6 +187,25 @@ class App extends React.Component { }; const finishEditing = (relatedItem, value) => { ServiceAction.finishEditingRelatedItem(relatedItem, value); + + // 関連URL追加時に再推論 + if ( + this._claudeCodeConfig?.enabled && + this.state.claudeCode.status !== "loading" && + this.state.URL?.startsWith("http") + ) { + // 少し遅延を入れてstateが更新されてから実行 + setTimeout(() => { + const updatedRelatedItems = appContext.ServiceStore.state.relatedItems; + ServiceAction.runClaudeCode( + this.state.URL, + this.state.title, + this._claudeCodeConfig, + updatedRelatedItems, + this.state.tags + ); + }, 100); + } }; const addItem = () => { ServiceAction.addRelatedItem(); @@ -194,8 +213,8 @@ class App extends React.Component { const submitPostLink = this.postLink.bind(this); // Claude Code関連 - const runClaudeCode = (url, title, config) => { - ServiceAction.runClaudeCode(url, title, config); + const runClaudeCode = (url, title, config, relatedItems = [], availableTags = []) => { + ServiceAction.runClaudeCode(url, title, config, relatedItems, availableTags); }; const insertClaudeCodeResult = () => { ServiceAction.insertClaudeCodeResult(); @@ -224,6 +243,8 @@ class App extends React.Component { insertResult={insertClaudeCodeResult} clearResult={clearClaudeCodeResult} claudeCodeConfig={this._claudeCodeConfig} + relatedItems={this.state.relatedItems} + availableTags={this.state.tags} /> diff --git a/src/browser/Store/ServiceStore.js b/src/browser/Store/ServiceStore.js index 9920950..d2b0baa 100644 --- a/src/browser/Store/ServiceStore.js +++ b/src/browser/Store/ServiceStore.js @@ -23,7 +23,8 @@ export default class ServiceStore extends Store { claudeCode: { status: "idle", // idle | loading | complete | error url: null, - result: null, + comment: null, + tags: [], error: null } }; @@ -86,7 +87,8 @@ export default class ServiceStore extends Store { claudeCode: { status: "idle", url: null, - result: null, + comment: null, + tags: [], error: null } }); @@ -137,18 +139,20 @@ export default class ServiceStore extends Store { claudeCode: { status: "loading", url, - result: null, + comment: null, + tags: [], error: null } }); }); - this.register(keys.claudeCodeComplete, ({ url, result }) => { + this.register(keys.claudeCodeComplete, ({ url, comment, tags }) => { this.setState({ claudeCode: { status: "complete", url, - result, + comment, + tags: tags || [], error: null } }); @@ -159,7 +163,8 @@ export default class ServiceStore extends Store { claudeCode: { status: "error", url, - result: null, + comment: null, + tags: [], error } }); @@ -170,22 +175,29 @@ export default class ServiceStore extends Store { claudeCode: { status: "idle", url: null, - result: null, + comment: null, + tags: [], error: null } }); }); this.register(keys.claudeCodeInsert, () => { - const { claudeCode } = this.state; - if (claudeCode.result) { - // 結果でコメントを入れ替え + const { claudeCode, selectedTags } = this.state; + if (claudeCode.comment) { + // タグをマージ(重複排除) + const newTags = claudeCode.tags || []; + const mergedTags = [...new Set([...selectedTags, ...newTags])]; + + // 結果でコメントを入れ替え、タグも追加 this.setState({ - comment: claudeCode.result, + comment: claudeCode.comment, + selectedTags: mergedTags, claudeCode: { status: "idle", url: null, - result: null, + comment: null, + tags: [], error: null } }); diff --git a/src/browser/component/ClaudeCodeButton.js b/src/browser/component/ClaudeCodeButton.js index 73ba7e9..41a09b7 100644 --- a/src/browser/component/ClaudeCodeButton.js +++ b/src/browser/component/ClaudeCodeButton.js @@ -9,7 +9,9 @@ export default function ClaudeCodeButton({ runClaudeCode, insertResult, clearResult, - claudeCodeConfig + claudeCodeConfig, + relatedItems = [], + availableTags = [] }) { const prevUrlRef = useRef(url); const debounceTimerRef = useRef(null); @@ -29,7 +31,7 @@ export default function ClaudeCodeButton({ // 1秒後に実行(入力中の連続変更を避ける) debounceTimerRef.current = setTimeout(() => { - runClaudeCode(url, title, claudeCodeConfig); + runClaudeCode(url, title, claudeCodeConfig, relatedItems, availableTags); }, 1000); } @@ -40,7 +42,7 @@ export default function ClaudeCodeButton({ clearTimeout(debounceTimerRef.current); } }; - }, [url, title, claudeCodeConfig, runClaudeCode]); + }, [url, title, claudeCodeConfig, runClaudeCode, relatedItems, availableTags]); const handleClick = useCallback(() => { if (claudeCode.status === "complete") { @@ -49,10 +51,10 @@ export default function ClaudeCodeButton({ } else if (claudeCode.status === "idle" || claudeCode.status === "error") { // アイドルまたはエラー状態の場合は実行 if (url && url.startsWith("http")) { - runClaudeCode(url, title, claudeCodeConfig); + runClaudeCode(url, title, claudeCodeConfig, relatedItems, availableTags); } } - }, [claudeCode.status, url, title, claudeCodeConfig, runClaudeCode, insertResult]); + }, [claudeCode.status, url, title, claudeCodeConfig, runClaudeCode, insertResult, relatedItems, availableTags]); // 設定が無効またはCLIが設定されていない場合は表示しない if (!claudeCodeConfig?.enabled) { @@ -70,7 +72,7 @@ export default function ClaudeCodeButton({ ); case "complete": return ( - + AI ✓ ); diff --git a/src/browser/component/ClaudeCodePreview.js b/src/browser/component/ClaudeCodePreview.js index 75eb1f3..548567c 100644 --- a/src/browser/component/ClaudeCodePreview.js +++ b/src/browser/component/ClaudeCodePreview.js @@ -21,7 +21,7 @@ export default function ClaudeCodePreview({ claudeCode, insertResult, clearResul ); } - if (claudeCode.status === "complete" && claudeCode.result) { + if (claudeCode.status === "complete" && claudeCode.comment) { return (
@@ -32,8 +32,11 @@ export default function ClaudeCodePreview({ claudeCode, insertResult, clearResul
- {claudeCode.result} + {claudeCode.comment}
+ {claudeCode.tags && claudeCode.tags.length > 0 && ( +
Tags: {claudeCode.tags.join(", ")}
+ )}
); } From 7dbd3d33f9dce9b1e22822967bce9462cb49823a Mon Sep 17 00:00:00 2001 From: azu Date: Thu, 15 Jan 2026 15:47:05 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20Claude=20CLI=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E3=82=92--output-format=20json=E3=81=A7=E3=83=91=E3=83=BC?= =?UTF-8?q?=E3=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit コードブロックの正規表現マッチからCLIのJSON出力形式に変更 Co-Authored-By: Claude Opus 4.5 --- src/browser/Action/ServiceAction.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/browser/Action/ServiceAction.js b/src/browser/Action/ServiceAction.js index fd97e31..b50b217 100644 --- a/src/browser/Action/ServiceAction.js +++ b/src/browser/Action/ServiceAction.js @@ -214,6 +214,7 @@ export default class ServiceAction extends Action { if (config.mcpConfig) { args.push("--mcp-config", JSON.stringify(config.mcpConfig)); } + args.push("--output-format", "json"); args.push("--print", "--dangerously-skip-permissions", prompt); const claudeProcess = spawn(config.cliPath, args, { @@ -234,25 +235,28 @@ export default class ServiceAction extends Action { claudeProcess.on("close", (code) => { if (code === 0 && stdout) { - // JSONコードブロックを抽出 - const jsonMatch = stdout.match(/```(?:json)?\s*([\s\S]*?)```/); let parsedResult = { comment: "", tags: [] }; - if (jsonMatch) { + try { + // CLIのJSON出力をパース + const cliOutput = JSON.parse(stdout); + const resultText = cliOutput.result || ""; + + // resultの中身をJSONとしてパース try { - parsedResult = JSON.parse(jsonMatch[1].trim()); - } catch (e) { - // フォールバック: 従来のmarkdown形式 - const markdownMatch = stdout.match(/```(?:markdown)?\s*([\s\S]*?)```/); - parsedResult.comment = markdownMatch ? markdownMatch[1].trim() : stdout.trim(); + parsedResult = JSON.parse(resultText); + } catch { + // フォールバック: resultをそのままcommentとして使用 + parsedResult.comment = resultText; } - } else { + } catch { + // CLIのJSON出力パース失敗時のフォールバック parsedResult.comment = stdout.trim(); } this.dispatch(keys.claudeCodeComplete, { url, - comment: parsedResult.comment, + comment: parsedResult.comment || "", tags: Array.isArray(parsedResult.tags) ? parsedResult.tags : [] }); } else { From dc9086c12bd1503bef9fadb9b3c41aae66cdcb70 Mon Sep 17 00:00:00 2001 From: azu Date: Thu, 15 Jan 2026 15:48:15 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Revert=20"refactor:=20Claude=20CLI=E5=87=BA?= =?UTF-8?q?=E5=8A=9B=E3=82=92--output-format=20json=E3=81=A7=E3=83=91?= =?UTF-8?q?=E3=83=BC=E3=82=B9"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7dbd3d33f9dce9b1e22822967bce9462cb49823a. --- src/browser/Action/ServiceAction.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/browser/Action/ServiceAction.js b/src/browser/Action/ServiceAction.js index b50b217..fd97e31 100644 --- a/src/browser/Action/ServiceAction.js +++ b/src/browser/Action/ServiceAction.js @@ -214,7 +214,6 @@ export default class ServiceAction extends Action { if (config.mcpConfig) { args.push("--mcp-config", JSON.stringify(config.mcpConfig)); } - args.push("--output-format", "json"); args.push("--print", "--dangerously-skip-permissions", prompt); const claudeProcess = spawn(config.cliPath, args, { @@ -235,28 +234,25 @@ export default class ServiceAction extends Action { claudeProcess.on("close", (code) => { if (code === 0 && stdout) { + // JSONコードブロックを抽出 + const jsonMatch = stdout.match(/```(?:json)?\s*([\s\S]*?)```/); let parsedResult = { comment: "", tags: [] }; - try { - // CLIのJSON出力をパース - const cliOutput = JSON.parse(stdout); - const resultText = cliOutput.result || ""; - - // resultの中身をJSONとしてパース + if (jsonMatch) { try { - parsedResult = JSON.parse(resultText); - } catch { - // フォールバック: resultをそのままcommentとして使用 - parsedResult.comment = resultText; + parsedResult = JSON.parse(jsonMatch[1].trim()); + } catch (e) { + // フォールバック: 従来のmarkdown形式 + const markdownMatch = stdout.match(/```(?:markdown)?\s*([\s\S]*?)```/); + parsedResult.comment = markdownMatch ? markdownMatch[1].trim() : stdout.trim(); } - } catch { - // CLIのJSON出力パース失敗時のフォールバック + } else { parsedResult.comment = stdout.trim(); } this.dispatch(keys.claudeCodeComplete, { url, - comment: parsedResult.comment || "", + comment: parsedResult.comment, tags: Array.isArray(parsedResult.tags) ? parsedResult.tags : [] }); } else {