179 lines
7.5 KiB
JavaScript
179 lines
7.5 KiB
JavaScript
import React, { useState } from 'react';
|
|
|
|
const OPERATORS = [
|
|
{ value: 'lt', label: '< (less than)' },
|
|
{ value: 'lte', label: '≤ (less than or equal)' },
|
|
{ value: 'eq', label: '= (equal to)' },
|
|
{ value: 'gte', label: '≥ (greater than or equal)' },
|
|
{ value: 'gt', label: '> (greater than)' },
|
|
];
|
|
|
|
const ACTIONS = [
|
|
{ value: 'seek_fulfillment', label: 'Seek Fulfillment' },
|
|
{ value: 'alert', label: 'Trigger Alert' },
|
|
{ value: 'change_state', label: 'Change Character State' },
|
|
{ value: 'generate_event', label: 'Generate Roleplay Event' },
|
|
{ value: 'activate_rule', label: 'Activate Other Rule' },
|
|
];
|
|
|
|
export default function BrainRuleEditor({ brainRules, needs, onAdd, onUpdate, onDelete }) {
|
|
const [editingId, setEditingId] = useState(null);
|
|
const [editForm, setEditForm] = useState({});
|
|
|
|
const startEdit = (rule) => {
|
|
setEditingId(rule.id);
|
|
const condition = typeof rule.condition === 'string' ? JSON.parse(rule.condition) : rule.condition;
|
|
const action = typeof rule.action === 'string' ? JSON.parse(rule.action) : rule.action;
|
|
setEditForm({ ...rule, condition, action });
|
|
};
|
|
|
|
const cancelEdit = () => {
|
|
setEditingId(null);
|
|
setEditForm({});
|
|
};
|
|
|
|
const saveEdit = () => {
|
|
onUpdate(editingId, {
|
|
condition: editForm.condition,
|
|
action: editForm.action,
|
|
priority: editForm.priority,
|
|
enabled: editForm.enabled,
|
|
});
|
|
setEditingId(null);
|
|
};
|
|
|
|
const handleAdd = () => {
|
|
const newRule = {
|
|
condition: { need: needs?.[0]?.name || '', operator: 'lt', value: 30 },
|
|
action: { type: 'seek_fulfillment', target: '' },
|
|
priority: 0,
|
|
enabled: true,
|
|
};
|
|
onAdd(newRule);
|
|
};
|
|
|
|
return (
|
|
<div className="brain-rule-editor">
|
|
<div className="editor-toolbar">
|
|
<h3>Brain Rules</h3>
|
|
<button className="btn-primary" onClick={handleAdd}>Add Rule</button>
|
|
</div>
|
|
|
|
{(!brainRules || brainRules.length === 0) ? (
|
|
<p className="empty-state">No brain rules defined. Rules define how your character reacts to changing needs.</p>
|
|
) : (
|
|
<div className="brain-rules-list">
|
|
{brainRules.map((rule) => {
|
|
const condition = typeof rule.condition === 'string' ? JSON.parse(rule.condition) : rule.condition;
|
|
const action = typeof rule.action === 'string' ? JSON.parse(rule.action) : rule.action;
|
|
|
|
return (
|
|
<div key={rule.id} className={`brain-rule-card ${rule.enabled ? '' : 'disabled-rule'}`}>
|
|
{editingId === rule.id ? (
|
|
<div className="rule-edit-form">
|
|
<div className="form-row">
|
|
<label>Condition: Need</label>
|
|
<select
|
|
value={editForm.condition?.need || ''}
|
|
onChange={(e) => setEditForm({ ...editForm, condition: { ...editForm.condition, need: e.target.value } })}
|
|
>
|
|
{needs?.map((n) => (
|
|
<option key={n.id} value={n.name}>{n.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Operator</label>
|
|
<select
|
|
value={editForm.condition?.operator || 'lt'}
|
|
onChange={(e) => setEditForm({ ...editForm, condition: { ...editForm.condition, operator: e.target.value } })}
|
|
>
|
|
{OPERATORS.map((o) => (
|
|
<option key={o.value} value={o.value}>{o.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Threshold Value</label>
|
|
<input
|
|
type="number"
|
|
value={editForm.condition?.value || 0}
|
|
onChange={(e) => setEditForm({ ...editForm, condition: { ...editForm.condition, value: +e.target.value } })}
|
|
/>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Action Type</label>
|
|
<select
|
|
value={editForm.action?.type || 'seek_fulfillment'}
|
|
onChange={(e) => setEditForm({ ...editForm, action: { ...editForm.action, type: e.target.value } })}
|
|
>
|
|
{ACTIONS.map((a) => (
|
|
<option key={a.value} value={a.value}>{a.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Action Target</label>
|
|
<input
|
|
value={editForm.action?.target || ''}
|
|
onChange={(e) => setEditForm({ ...editForm, action: { ...editForm.action, target: e.target.value } })}
|
|
placeholder="Target need or behavior"
|
|
/>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Priority</label>
|
|
<input
|
|
type="number"
|
|
value={editForm.priority || 0}
|
|
onChange={(e) => setEditForm({ ...editForm, priority: +e.target.value })}
|
|
/>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Enabled</label>
|
|
<input
|
|
type="checkbox"
|
|
checked={editForm.enabled}
|
|
onChange={(e) => setEditForm({ ...editForm, enabled: e.target.checked })}
|
|
/>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button className="btn-primary" onClick={saveEdit}>Save</button>
|
|
<button className="btn-secondary" onClick={cancelEdit}>Cancel</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="rule-header">
|
|
<div className="rule-condition">
|
|
<span className="condition-text">
|
|
IF <strong>{condition?.need}</strong> {OPERATORS.find(o => o.value === condition?.operator)?.label || condition?.operator} <strong>{condition?.value}</strong>
|
|
</span>
|
|
</div>
|
|
<div className="rule-action-type">
|
|
<span className="action-text">
|
|
THEN <strong>{ACTIONS.find(a => a.value === action?.type)?.label || action?.type}</strong>
|
|
{action?.target ? `: ${action.target}` : ''}
|
|
</span>
|
|
</div>
|
|
<span className={`rule-status ${rule.enabled ? 'active' : 'inactive'}`}>
|
|
{rule.enabled ? 'Active' : 'Disabled'}
|
|
</span>
|
|
</div>
|
|
<div className="rule-meta">
|
|
<span>Priority: {rule.priority}</span>
|
|
</div>
|
|
<div className="rule-actions">
|
|
<button className="btn-small" onClick={() => startEdit(rule)}>Edit</button>
|
|
<button className="btn-danger-small" onClick={() => onDelete(rule.id)}>Delete</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|