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
1 change: 1 addition & 0 deletions common/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function promiseAllLimit<T>(
if (cancel?.()) {
rejected = true;
console.log('CANCELLING!');
reject(new Error('Promise chain cancelled'));
return;
}

Expand Down
25 changes: 25 additions & 0 deletions resources/style/controls/input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
}
}

.split-input-wrapper,
.input {
height: 28px;
background: var(--input-color);
Expand Down Expand Up @@ -101,3 +102,27 @@ textarea.input {
}
}
}

.split-input-wrapper {
display: flex;
flex-grow: 1;
flex-wrap: wrap;
overflow-x: hidden;
align-items: center;
user-select: text;
padding-left: 0.25rem;

> * {
user-select: text;
padding: 0;
margin: 0;
line-height: 1.75em;
}

input {
flex-grow: 1;
border: none;
background: transparent;
box-sizing: content-box;
}
}
2 changes: 2 additions & 0 deletions resources/style/controls/notifications.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@
min-height: 1rem;
max-height: 5rem;
margin: 0.25rem;
word-break: break-word;
}

button {
color: white;
min-width: auto;

&:hover {
background: rgba(255, 255, 255, 0.15);
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/containers/ContentView/menu-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export const FileViewerMenuItems = ({ file }: { file: ClientFile }) => {
text="Open Tag Selector"
icon={IconSet.TAG}
/>
<MenuItem
onClick={fileStore.tagSelectedFilesUsingTaggingService}
text="Auto Tag Selected Using Tagging Service"
icon={IconSet.TAG_ADD}
disabled={fileStore.isTaggingWithService}
/>
<MenuItem
onClick={fileStore.readTagsFromSelectedFiles}
text="Import Tags From Selected Files Metadata"
Expand Down
23 changes: 23 additions & 0 deletions src/frontend/containers/HelpCenter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,25 @@ const PAGE_DATA: () => IPageData[] = () => [
</>
),
},
{
title: 'Automatic Tagging',
content: (
<>
<p>
You can set an endpoint to a locally hosted AI tagging service or any custom tagging
implementation, allowing the app to send requests and automatically tag files with the
service response. You can also configure the number of concurrent requests made to the
service simultaneously. For more information, see the "Background Processes" section
in the settings window.
</p>
<p>
To automatically tag selected files, use the
{' "Tagging... > Auto Tag Selected Using Tagging Service" '}
option in the file context menu.
</p>
</>
),
},
{
title: 'Tag Import/Export',
content: (
Expand All @@ -326,6 +345,10 @@ const PAGE_DATA: () => IPageData[] = () => [
<br />
Note that only the images shown in the gallery are affected by these operations!
</p>
<p>
You can also import/export tags from selected files through the "Tagging" options in
the file context menu.
</p>
</>
),
},
Expand Down
109 changes: 106 additions & 3 deletions src/frontend/containers/Settings/BackgroundProcesses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { IconSet, Toggle } from 'widgets';
import { Callout } from 'widgets/notifications';
import { useStore } from '../../contexts/StoreContext';
import FileInput from 'src/frontend/components/FileInput';
import { useGalleryInputKeydownHandler } from 'src/frontend/hooks/useHandleInputKeydown';
import UiStore from 'src/frontend/stores/UiStore';

export const BackgroundProcesses = observer(() => {
const { uiStore, locationStore } = useStore();
Expand Down Expand Up @@ -36,9 +38,7 @@ export const BackgroundProcesses = observer(() => {

return (
<>
<Toggle checked={isRunInBackground} onChange={toggleRunInBackground}>
Run in background
</Toggle>
<TaggingServiceConfig />
<h3>Browser Extension</h3>
<Callout icon={IconSet.INFO}>
You need to install the browser extension before either in the{' '}
Expand All @@ -63,6 +63,11 @@ export const BackgroundProcesses = observer(() => {
>
Run browser extension
</Toggle>
<br />
<br />
<Toggle checked={isRunInBackground} onChange={toggleRunInBackground}>
Run in background
</Toggle>
<div className="filepicker">
<FileInput
className="btn-minimal filepicker-input"
Expand All @@ -77,6 +82,104 @@ export const BackgroundProcesses = observer(() => {
<h4 className="filepicker-label">Download Directory</h4>
<div className="filepicker-path">{uiStore.importDirectory || 'Not set'}</div>
</div>
<br />
<br />
</>
);
});

const TaggingServiceConfig = observer(() => {
const { taggingServiceURL, setTaggingServiceURL } = useStore().uiStore;
const prehost = 'http://localhost';

const posthost = taggingServiceURL.startsWith(prehost)
? taggingServiceURL.slice(prehost.length)
: '';

const handleKeyDown = useGalleryInputKeydownHandler();

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
//use URL for validations
let newPosthost = e.target.value.replace(prehost, '');
const url = new URL(newPosthost, prehost);
//Remove any hostname if present when pasting the full URL.
newPosthost = (url.pathname + url.search + url.hash).replace('/', '');
if (newPosthost && !newPosthost.startsWith(':') && !newPosthost.startsWith('/')) {
newPosthost = '/' + newPosthost;
}
setTaggingServiceURL(prehost + newPosthost);
};

// Custom and minimalistic implementation inspired/based on cmeka's implementation: https://github.com/cmeka/OneFolder/commit/b0d7e12
return (
<>
<h3>Local Tagging Service API URL</h3>
<Callout icon={IconSet.INFO}>
A tagging service such as{' '}
<ExternalLink url="https://github.com/cmeka/media-tag-service">
media-tag-service
</ExternalLink>{' '}
or any custom tagging endpoint must be running.
</Callout>
<Callout icon={IconSet.INFO}>
<div style={{ overflowY: 'auto', height: '105px' }}>
{'The endpoint must accept a JSON request with the format:'}
<pre>{'{ "file": "<absolute_path>" }'}</pre>
{'and respond with a JSON in the format:'}
<pre>
{'{'}
<br />
{' "tags": ['}
<br />
{' { "name": "<tag1_name>" },'}
<br />
{' { "name": "<tag2_name>" },'}
<br />
{' ... etc.'}
<br />
{' ]'}
<br />
{'}'}
</pre>
</div>
</Callout>

<div className="split-input-wrapper input">
<span style={{ color: 'var(--text-color-muted)' }}>{prehost}</span>
<input
type="text"
className="flex-1 border p-1"
value={posthost}
onKeyDown={handleKeyDown}
onChange={handleChange}
/>
</div>
<br />
<TaggingServiceParallelRequests />
<br />
<br />
</>
);
});

const TaggingServiceParallelRequests = observer(() => {
const { uiStore } = useStore();

const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const value = Number(event.target.value);
uiStore.setTaggingServiceParallelRequests(value);
};

return (
<label>
Number of Tagging Requests in Parallel
<select value={uiStore.taggingServiceParallelRequests} onChange={handleChange}>
{[...Array(UiStore.MAX_TAGGING_SERVICE_PARALLEL_REQUESTS)].map((_, i) => (
<option key={i + 1} value={i + 1}>
{i + 1}
</option>
))}
</select>
</label>
);
});
4 changes: 4 additions & 0 deletions src/frontend/entities/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ export class ClientFile {
makeObservable(this);
}

get isAutoSaveEnabled(): boolean {
return this.autoSave;
}

/**
* Gets his tags and all inherithed tags from parent and implied tags from his tags.
*/
Expand Down
Loading
Loading