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
172 changes: 172 additions & 0 deletions css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,175 @@
text-shadow: -1px 1px #417cb8;
border: none;
}

/* URL入力とClaude Codeボタンの行 */
.URLInputRow {
display: flex;
align-items: center;
gap: 8px;
margin: 0.1em 0;
}

.URLInputRow .URLInput {
flex: 1;
margin: 0;
}

/* Claude Codeボタン */
.ClaudeCodeButton {
padding: 6px 8px;
font-size: 12px;
font-weight: bold;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #f5f5f5;
color: #666;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
min-width: 36px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
}

.ClaudeCodeButton svg {
vertical-align: middle;
}

.ClaudeCodeButton:hover:not(:disabled) {
background-color: #e0e0e0;
border-color: #999;
}

.ClaudeCodeButton:disabled {
cursor: not-allowed;
opacity: 0.7;
}

.ClaudeCodeButton--loading {
background-color: #fff3cd;
border-color: #ffc107;
color: #856404;
}

.ClaudeCodeButton--complete {
background-color: #d4edda;
border-color: #28a745;
color: #155724;
}

.ClaudeCodeButton--complete:hover:not(:disabled) {
background-color: #c3e6cb;
border-color: #1e7e34;
}

.ClaudeCodeButton--error {
background-color: #f8d7da;
border-color: #dc3545;
color: #721c24;
}

.ClaudeCodeButton-loading {
display: flex;
align-items: center;
gap: 4px;
}

.ClaudeCodeButton-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #856404;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

@keyframes spin {
to {
transform: rotate(360deg);
}
}

/* Claude Code Preview */
.ClaudeCodePreview {
margin: 0.5em 0;
padding: 8px 12px;
border-radius: 4px;
font-size: 13px;
}

.ClaudeCodePreview-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
font-weight: bold;
font-size: 12px;
}

.ClaudeCodePreview-hint {
font-weight: normal;
color: #666;
font-size: 11px;
}

.ClaudeCodePreview-close {
margin-left: auto;
background: none;
border: none;
font-size: 16px;
cursor: pointer;
color: #999;
padding: 0 4px;
}

.ClaudeCodePreview-close:hover {
color: #333;
}

.ClaudeCodePreview-content {
white-space: pre-wrap;
line-height: 1.5;
font-family: Monaco, "Andale Mono", monospace;
}

.ClaudeCodePreview--loading {
background-color: #fff8e1;
border: 1px solid #ffcc02;
}

.ClaudeCodePreview--loading .ClaudeCodePreview-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #f0a000;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

.ClaudeCodePreview--complete {
background-color: #e8f5e9;
border: 1px solid #4caf50;
}

.ClaudeCodePreview--complete .ClaudeCodePreview-content {
cursor: pointer;
}

.ClaudeCodePreview--complete .ClaudeCodePreview-content:hover {
background-color: rgba(76, 175, 80, 0.1);
}

.ClaudeCodePreview--error {
background-color: #ffebee;
border: 1px solid #f44336;
}

.ClaudeCodePreview--error .ClaudeCodePreview-content {
color: #c62828;
}
58 changes: 58 additions & 0 deletions src/browser/Action/ServiceAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import notie from "notie";
import { show as LoadingShow, dismiss as LoadingDismiss } from "../view-util/Loading";
import RelatedItemModel from "../models/RelatedItemModel";
import serviceInstance from "../service-instance";
import { spawn } from "child_process";
import fs from "fs";

export default class ServiceAction extends Action {
fetchTags(service) {
Expand Down Expand Up @@ -191,4 +193,60 @@ export default class ServiceAction extends Action {
resetField() {
this.dispatch(keys.resetField);
}

// Claude Code関連アクション
runClaudeCode(url, config) {
if (!config?.enabled) return;
if (!fs.existsSync(config.cliPath)) return;
if (!fs.existsSync(config.workDir)) {
this.dispatch(keys.claudeCodeError, { url, error: `WorkDir not found: ${config.workDir}` });
return;
}

this.dispatch(keys.claudeCodeStart, { url });

const args = [];
if (config.mcpConfig) {
args.push("--mcp-config", JSON.stringify(config.mcpConfig));
}
args.push("--print", "--dangerously-skip-permissions", `${config.prompt}\n\nURL: ${url}`);

const claudeProcess = spawn(config.cliPath, args, {
cwd: config.workDir,
env: {
...process.env,
PATH: "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:" + process.env.PATH
},
shell: false,
stdio: ["ignore", "pipe", "pipe"]
});

let stdout = "";
let stderr = "";

claudeProcess.stdout.on("data", (data) => (stdout += data.toString()));
claudeProcess.stderr.on("data", (data) => (stderr += data.toString()));

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 });
} else {
this.dispatch(keys.claudeCodeError, { url, error: stderr || `Exit code: ${code}` });
}
});

claudeProcess.on("error", (error) => {
this.dispatch(keys.claudeCodeError, { url, error: error.message });
});
}

clearClaudeCodeResult() {
this.dispatch(keys.claudeCodeClear);
}

insertClaudeCodeResult() {
this.dispatch(keys.claudeCodeInsert);
}
}
8 changes: 7 additions & 1 deletion src/browser/Action/ServiceActionConst.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,11 @@ export default {
removeRelatedItem: Symbol("removeRelatedItem"),
finishEditingRelatedItem: Symbol("finishEditingRelatedItem"),
enableService: Symbol("enableService"),
disableService: Symbol("disableService")
disableService: Symbol("disableService"),
// Claude Code関連
claudeCodeStart: Symbol("claudeCodeStart"),
claudeCodeComplete: Symbol("claudeCodeComplete"),
claudeCodeError: Symbol("claudeCodeError"),
claudeCodeClear: Symbol("claudeCodeClear"),
claudeCodeInsert: Symbol("claudeCodeInsert")
};
39 changes: 36 additions & 3 deletions src/browser/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import TitleInput from "./component/TitleInput";
import SubmitButton from "./component/SubmitButton";
import RelatedListBox from "./component/RelatedListBox";
import ServiceList from "./component/ServiceList";
import ClaudeCodeButton from "./component/ClaudeCodeButton";
import ClaudeCodePreview from "./component/ClaudeCodePreview";
import AppContext from "./AppContext";
import serviceManger, { waitForInitialization } from "./service-instance";
import serviceManger, { waitForInitialization, getClaudeCodeConfig } from "./service-instance";

const ipcRenderer = require("electron").ipcRenderer;
const appContext = new AppContext();
Expand All @@ -20,6 +22,7 @@ class App extends React.Component {
constructor(...args) {
super(...args);
this._TagSelect = null;
this._claudeCodeConfig = getClaudeCodeConfig();
this.state = Object.assign(
{
initialized: false
Expand Down Expand Up @@ -70,7 +73,9 @@ class App extends React.Component {
const service = serviceManger.getTagService();
if (service && state.selectedTags.length === 0 && state.comment.length === 0) {
appContext.ServiceAction.fetchContent(service, URL)
.then(({ comment, tags, relatedItems }) => {
.then((result) => {
if (!result) return;
const { comment, tags, relatedItems } = result;
if (comment) {
appContext.ServiceAction.updateComment(comment);
}
Expand Down Expand Up @@ -187,6 +192,18 @@ class App extends React.Component {
ServiceAction.addRelatedItem();
};
const submitPostLink = this.postLink.bind(this);

// Claude Code関連
const runClaudeCode = (url, config) => {
ServiceAction.runClaudeCode(url, config);
};
const insertClaudeCodeResult = () => {
ServiceAction.insertClaudeCodeResult();
};
const clearClaudeCodeResult = () => {
ServiceAction.clearClaudeCodeResult();
};

return (
<div className="App">
<ServiceList
Expand All @@ -197,20 +214,36 @@ class App extends React.Component {
login={login}
/>
<TitleInput title={this.state.title} updateTitle={updateTitle} />
<URLInput URL={this.state.URL} updateURL={updateURL} />
<div className="URLInputRow">
<URLInput URL={this.state.URL} updateURL={updateURL} />
<ClaudeCodeButton
url={this.state.URL}
claudeCode={this.state.claudeCode}
runClaudeCode={runClaudeCode}
insertResult={insertClaudeCodeResult}
clearResult={clearClaudeCodeResult}
claudeCodeConfig={this._claudeCodeConfig}
/>
</div>
<ViaURLInput URL={this.state.viaURL} updateURL={updateViaURL} />
<TagSelect
ref={(c) => (this._TagSelect = c)}
tags={this.state.tags}
selectTags={selectTags}
selectedTags={this.state.selectedTags}
/>
<ClaudeCodePreview
claudeCode={this.state.claudeCode}
insertResult={insertClaudeCodeResult}
clearResult={clearClaudeCodeResult}
/>
<Editor
value={this.state.comment}
onChange={updateComment}
onSubmit={submitPostLink}
services={services}
toggleServiceAtIndex={toggleServiceAtIndex}
onInsertClaudeCode={insertClaudeCodeResult}
/>
<RelatedListBox
relatedItems={this.state.relatedItems}
Expand Down
Loading