Skip to content

Commit 26a16f0

Browse files
Update AI Overhaul plugin for 0.1.1a14
1 parent e2bd2c1 commit 26a16f0

17 files changed

+9236
-0
lines changed

plugins/AIOverhaul/AIButton.js

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
(function(){
2+
// =============================================================================
3+
// Unified Integration for AI Button + Task Dashboard
4+
// - Injects MinimalAIButton into MainNavBar.UtilityItems
5+
// - Registers /plugins/ai-tasks route mounting TaskDashboard
6+
// - Adds SettingsToolsSection entry linking to the dashboard
7+
// - Adds simple "AI" nav utility link (in case button not visible)
8+
// - All logging gated by window.AIDebug
9+
// =============================================================================
10+
(function () {
11+
var _a, _b, _c;
12+
const g = window;
13+
const PluginApi = g.PluginApi;
14+
if (!PluginApi) {
15+
console.warn('[AIIntegration] PluginApi not ready');
16+
return;
17+
}
18+
const React = PluginApi.React;
19+
const debug = !!g.AIDebug;
20+
const dlog = (...a) => { if (debug)
21+
console.log('[AIIntegration]', ...a); };
22+
// Helper to safely get components
23+
const Button = ((_b = (_a = PluginApi.libraries) === null || _a === void 0 ? void 0 : _a.Bootstrap) === null || _b === void 0 ? void 0 : _b.Button) || ((p) => React.createElement('button', p, p.children));
24+
const { Link, NavLink } = ((_c = PluginApi.libraries) === null || _c === void 0 ? void 0 : _c.ReactRouterDOM) || {};
25+
function getMinimalButton() { return g.MinimalAIButton || g.AIButton; }
26+
function getTaskDashboard() { return g.TaskDashboard || g.AITaskDashboard; }
27+
function getPluginSettings() { return g.AIPluginSettings; }
28+
// Main nav utility items: inject AI button + nav link
29+
try {
30+
PluginApi.patch.before('MainNavBar.UtilityItems', function (props) {
31+
const MinimalAIButton = getMinimalButton();
32+
const children = [props.children];
33+
if (MinimalAIButton) {
34+
children.push(React.createElement('div', { key: 'ai-btn-wrap', style: { marginRight: 8, display: 'flex', alignItems: 'center' } }, React.createElement(MinimalAIButton)));
35+
}
36+
return [{ children }];
37+
});
38+
dlog('Patched MainNavBar.UtilityItems');
39+
}
40+
catch (e) {
41+
if (debug)
42+
console.warn('[AIIntegration] main nav patch failed', e);
43+
}
44+
// Register dashboard route
45+
try {
46+
PluginApi.register.route('/plugins/ai-tasks', () => {
47+
const Dash = getTaskDashboard();
48+
return Dash ? React.createElement(Dash, {}) : React.createElement('div', { style: { padding: 16 } }, 'Loading AI Tasks...');
49+
});
50+
dlog('Registered /plugins/ai-tasks route');
51+
}
52+
catch (e) {
53+
if (debug)
54+
console.warn('[AIIntegration] route register failed', e);
55+
}
56+
// Register settings route (event-driven, no polling)
57+
try {
58+
const SettingsWrapper = () => {
59+
const [Comp, setComp] = React.useState(() => getPluginSettings());
60+
React.useEffect(() => {
61+
if (Comp)
62+
return; // already there
63+
const handler = () => {
64+
const found = getPluginSettings();
65+
if (found) {
66+
if (debug)
67+
console.debug('[AIIntegration] AIPluginSettingsReady event captured');
68+
setComp(() => found);
69+
}
70+
};
71+
window.addEventListener('AIPluginSettingsReady', handler);
72+
// one immediate async attempt (in case script loaded right after)
73+
setTimeout(handler, 0);
74+
return () => window.removeEventListener('AIPluginSettingsReady', handler);
75+
}, [Comp]);
76+
const C = Comp;
77+
return C ? React.createElement(C, {}) : React.createElement('div', { style: { padding: 16 } }, 'Loading AI Overhaul Settings...');
78+
};
79+
PluginApi.register.route('/plugins/ai-settings', () => React.createElement(SettingsWrapper));
80+
dlog('Registered /plugins/ai-settings route (event)');
81+
}
82+
catch (e) {
83+
if (debug)
84+
console.warn('[AIIntegration] settings route register failed', e);
85+
}
86+
// Settings tools entry
87+
try {
88+
PluginApi.patch.before('SettingsToolsSection', function (props) {
89+
var _a;
90+
const Setting = (_a = PluginApi.components) === null || _a === void 0 ? void 0 : _a.Setting;
91+
if (!Setting)
92+
return props;
93+
return [{ children: (React.createElement(React.Fragment, null,
94+
props.children,
95+
React.createElement(Setting, { heading: Link ? React.createElement(Link, { to: "/plugins/ai-tasks" },
96+
React.createElement(Button, null, "AI Tasks")) : React.createElement(Button, { onClick: () => (location.href = '/plugins/ai-tasks') }, 'AI Tasks') }),
97+
React.createElement(Setting, { heading: Link ? React.createElement(Link, { to: "/plugins/ai-settings" },
98+
React.createElement(Button, null, "AI Overhaul Settings")) : React.createElement(Button, { onClick: () => (location.href = '/plugins/ai-settings') }, 'AI Overhaul Settings') }))) }];
99+
});
100+
dlog('Patched SettingsToolsSection');
101+
}
102+
catch (e) {
103+
if (debug)
104+
console.warn('[AIIntegration] settings tools patch failed', e);
105+
}
106+
if (debug)
107+
console.log('[AIIntegration] Unified integration loaded');
108+
})();
109+
})();
110+

plugins/AIOverhaul/AIOverhaul.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: AIOverhaul
2+
description: AI Overhaul for Stash
3+
version: 0.5.0
4+
ui:
5+
javascript:
6+
- BackendBase.js
7+
- BackendHealth.js
8+
- PageContext.js
9+
- RecommendationUtils.js
10+
- AIButton.js
11+
- TaskDashboard.js
12+
- PluginSettings.js # ensure settings component registers before integration
13+
- RecommendedScenes.js
14+
- SimilarScenes.js
15+
- SimilarTabIntegration.js
16+
- InteractionTracker.js
17+
- AIButtonIntegration.js # integration last after components
18+
css:
19+
- css/AIOverhaul.css
20+
- css/recommendedscenes.css
21+
- css/SimilarScenes.css
22+
csp:
23+
connect-src:
24+
- http://localhost:4153
25+
- ws://localhost:4153
26+
- https://localhost:4153
27+
interface: raw
28+
exec:
29+
- python
30+
- "{pluginDir}/plugin_setup.py"
31+
tasks:
32+
- name: Setup AI Overhaul Plugin settings
33+
description: Use to set automatically set AI Overhaul Plugin settings
34+
defaultArgs:
35+
mode: plugin_setup
36+
settings:
37+
backend_base_url:
38+
displayName: Backend Base URL Override
39+
type: STRING
40+
capture_events:
41+
displayName: Capture Interaction Events
42+
type: BOOLEAN

plugins/AIOverhaul/BackendBase.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
(function(){
2+
// Shared helper to determine the backend base URL used by the frontend.
3+
// Exposes a default export and also attaches to window.AIDefaultBackendBase for
4+
// non-module consumers in the minimal build.
5+
defaultBackendBase;
6+
const PLUGIN_NAME = 'AIOverhaul';
7+
// Local default to keep the UI functional before plugin config loads.
8+
const DEFAULT_BACKEND_BASE = 'http://localhost:4153';
9+
const CONFIG_QUERY = `query AIOverhaulPluginConfig($ids: [ID!]) {
10+
configuration {
11+
plugins(include: $ids)
12+
}
13+
}`;
14+
let configLoaded = false;
15+
let configLoading = false;
16+
function getOrigin() {
17+
try {
18+
if (typeof location !== 'undefined' && location.origin) {
19+
return location.origin.replace(/\/$/, '');
20+
}
21+
}
22+
catch { }
23+
return '';
24+
}
25+
function normalizeBase(raw) {
26+
if (typeof raw !== 'string')
27+
return null;
28+
const trimmed = raw.trim();
29+
if (!trimmed)
30+
return '';
31+
const cleaned = trimmed.replace(/\/$/, '');
32+
const origin = getOrigin();
33+
if (origin && cleaned === origin) {
34+
return '';
35+
}
36+
return cleaned;
37+
}
38+
function interpretBool(raw) {
39+
if (typeof raw === 'boolean')
40+
return raw;
41+
if (typeof raw === 'number')
42+
return raw !== 0;
43+
if (typeof raw === 'string') {
44+
const lowered = raw.trim().toLowerCase();
45+
if (!lowered)
46+
return false;
47+
if (['1', 'true', 'yes', 'on'].includes(lowered))
48+
return true;
49+
if (['0', 'false', 'no', 'off'].includes(lowered))
50+
return false;
51+
}
52+
return null;
53+
}
54+
function applyPluginConfig(base, captureEvents) {
55+
if (base !== undefined) {
56+
const normalized = normalizeBase(base);
57+
if (normalized !== null) {
58+
const value = normalized || '';
59+
try {
60+
window.AI_BACKEND_URL = value;
61+
window.dispatchEvent(new CustomEvent('AIBackendBaseUpdated', { detail: value }));
62+
}
63+
catch { }
64+
}
65+
}
66+
if (captureEvents !== undefined && captureEvents !== null) {
67+
const normalized = !!captureEvents;
68+
try {
69+
window.__AI_INTERACTIONS_ENABLED__ = normalized;
70+
}
71+
catch { }
72+
try {
73+
const tracker = window.stashAIInteractionTracker;
74+
if (tracker) {
75+
if (typeof tracker.setEnabled === 'function')
76+
tracker.setEnabled(normalized);
77+
else if (typeof tracker.configure === 'function')
78+
tracker.configure({ enabled: normalized });
79+
}
80+
}
81+
catch { }
82+
}
83+
}
84+
async function loadPluginConfig() {
85+
var _a, _b, _c, _d, _e, _f;
86+
if (configLoaded || configLoading)
87+
return;
88+
configLoading = true;
89+
try {
90+
const resp = await fetch('/graphql', {
91+
method: 'POST',
92+
headers: { 'content-type': 'application/json' },
93+
credentials: 'same-origin',
94+
body: JSON.stringify({ query: CONFIG_QUERY, variables: { ids: [PLUGIN_NAME] } }),
95+
});
96+
if (!resp.ok)
97+
return;
98+
const payload = await resp.json().catch(() => null);
99+
const plugins = (_b = (_a = payload === null || payload === void 0 ? void 0 : payload.data) === null || _a === void 0 ? void 0 : _a.configuration) === null || _b === void 0 ? void 0 : _b.plugins;
100+
if (plugins && typeof plugins === 'object') {
101+
const entry = plugins[PLUGIN_NAME];
102+
if (entry && typeof entry === 'object') {
103+
const backendBase = (_d = (_c = entry.backend_base_url) !== null && _c !== void 0 ? _c : entry.backendBaseUrl) !== null && _d !== void 0 ? _d : entry.backendBaseURL;
104+
const captureEvents = (_f = (_e = entry.capture_events) !== null && _e !== void 0 ? _e : entry.captureEvents) !== null && _f !== void 0 ? _f : entry.captureEventsEnabled;
105+
applyPluginConfig(backendBase, interpretBool(captureEvents));
106+
}
107+
}
108+
}
109+
catch { }
110+
finally {
111+
configLoaded = true;
112+
configLoading = false;
113+
}
114+
}
115+
function defaultBackendBase() {
116+
try {
117+
if (!configLoaded)
118+
loadPluginConfig();
119+
}
120+
catch { }
121+
if (typeof window.AI_BACKEND_URL === 'string') {
122+
const explicit = normalizeBase(window.AI_BACKEND_URL);
123+
if (explicit !== null && explicit !== undefined) {
124+
return explicit;
125+
}
126+
return '';
127+
}
128+
return DEFAULT_BACKEND_BASE;
129+
}
130+
// Also attach as a global so files that are executed before this module can still
131+
// use the shared function when available.
132+
try {
133+
window.AIDefaultBackendBase = defaultBackendBase;
134+
defaultBackendBase.loadPluginConfig = loadPluginConfig;
135+
defaultBackendBase.applyPluginConfig = applyPluginConfig;
136+
}
137+
catch { }
138+
})();
139+

0 commit comments

Comments
 (0)