Data groups: a provider's models read only their own group's data
Every connection is in a data group. Its models are handed, and can find, only that group's memories, notes, skills, knowledge, reports and personality -- by search and by id. A chat stays in the group it was started in: switching its model, the endpoint fallback, the crowd, friends, bases and the @ menu all stay inside it, and a chat whose model has moved is refused rather than sent. A group may name its own embedder and image reviewer. data.manage lets a person make personal groups, remap connections for themselves and move their own records. Also: a search no longer mixes two embedders of the same width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,11 @@
|
||||
<label class="tabs__tab" for="tab-memory">{{ icon("sparkle", "icon--sm") }} Memory</label>
|
||||
{% endif %}
|
||||
|
||||
{% if several_groups or may_manage_groups %}
|
||||
<input class="visually-hidden" type="radio" name="settings-tab" id="tab-data">
|
||||
<label class="tabs__tab" for="tab-data">{{ icon("shield", "icon--sm") }} {{ t("Data") }}</label>
|
||||
{% endif %}
|
||||
|
||||
<input class="visually-hidden" type="radio" name="settings-tab" id="tab-security">
|
||||
<label class="tabs__tab" for="tab-security">{{ icon("key", "icon--sm") }} Security</label>
|
||||
</div>
|
||||
@@ -388,6 +393,9 @@
|
||||
<input class="input" name="content" value="{{ memory.content }}"
|
||||
maxlength="{{ memory_limit }}" style="flex: 1"
|
||||
aria-label="{{ t('What this memory says') }}">
|
||||
{% if several_groups %}
|
||||
<span class="badge" title="{{ t('Data group') }}">{{ group_names.get(memory.data_group_id or 'default', '') }}</span>
|
||||
{% endif %}
|
||||
<button class="btn btn--sm" type="submit">{{ t("Save") }}</button>
|
||||
<button class="btn btn--sm btn--danger" type="submit"
|
||||
formaction="/api/library/memories/{{ memory.id }}/delete"
|
||||
@@ -416,6 +424,14 @@
|
||||
maxlength="{{ memory_limit }}"
|
||||
aria-label="{{ t('Something worth remembering') }}"
|
||||
placeholder="{{ t('Prefers metric units and a 24-hour clock.') }}">
|
||||
{% if several_groups %}
|
||||
<select class="select" name="data_group_id" aria-label="{{ t('Data group') }}"
|
||||
style="flex: none">
|
||||
{% for group in data_groups %}
|
||||
<option value="{{ group.id }}">{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
<button class="btn btn--primary" type="submit">{{ t("Remember") }}</button>
|
||||
</form>
|
||||
<p class="field__hint">
|
||||
@@ -439,7 +455,9 @@
|
||||
{% for personality in personalities %}
|
||||
<li class="model-list__item">
|
||||
<div style="min-width: 0">
|
||||
<strong>{{ personality.model_key }}</strong>
|
||||
{% set key = split_key(personality.model_key) %}
|
||||
<strong>{{ key[0] }}</strong>
|
||||
{% if several_groups %}<span class="badge">{{ group_names.get(key[1], key[1]) }}</span>{% endif %}
|
||||
{% if personality.author == "model" %}
|
||||
<span class="badge badge--leaf">{{ t("its own words") }}</span>
|
||||
{% endif %}
|
||||
@@ -484,7 +502,9 @@
|
||||
{% for impression in impressions %}
|
||||
<li class="model-list__item">
|
||||
<div style="min-width: 0">
|
||||
<strong>{{ impression.model_key }}</strong>
|
||||
{% set key = split_key(impression.model_key) %}
|
||||
<strong>{{ key[0] }}</strong>
|
||||
{% if several_groups %}<span class="badge">{{ group_names.get(key[1], key[1]) }}</span>{% endif %}
|
||||
<div class="text-sm">{{ impression.content }}</div>
|
||||
</div>
|
||||
<form method="post"
|
||||
@@ -504,6 +524,88 @@
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{# --- Data groups --- #}
|
||||
{#
|
||||
Which provider may read which of this person's data. Shown to anybody
|
||||
once there is more than one group, because "which provider can read
|
||||
my notes?" is a question anybody may ask; editable only with
|
||||
`data.manage`, because the answer changes what a provider can see.
|
||||
#}
|
||||
{% if several_groups or may_manage_groups %}
|
||||
<section class="tabs__panel" data-tab="tab-data">
|
||||
<div class="card">
|
||||
<h2 class="card__title">{{ t("Your data groups") }}</h2>
|
||||
<p class="card__lede">{{ t("A model reads only the memories, notes, skills, knowledge, reports and personality of the data group its connection is in, and a chat stays in the group it was started in. Two providers in different groups never see each other's.") }}</p>
|
||||
<ul class="model-list">
|
||||
{% for group in data_groups %}
|
||||
<li class="model-list__item">
|
||||
<div style="min-width: 0">
|
||||
<strong>{{ group.name }}</strong>
|
||||
{% if group.personal %}<span class="badge">{{ t("yours") }}</span>{% endif %}
|
||||
<div class="text-xs faint">
|
||||
{% for label, n in group_counts[group.id].items() %}{{ n }} {{ group_labels.get(label, label) }}{% if not loop.last %} · {% endif %}{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% if group.personal and may_manage_groups %}
|
||||
<form method="post" action="/api/preferences/data-groups/{{ group.id }}/delete">
|
||||
<button class="btn btn--sm btn--danger" type="submit"
|
||||
data-confirm-button="{{ t('Delete the data group “%(name)s”?', name=group.name) }}"
|
||||
aria-label="{{ t('Delete this') }}" title="{{ t('Delete this') }}">
|
||||
{{ icon("trash", "icon--sm") }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if may_manage_groups %}
|
||||
<form method="post" action="/api/preferences/data-groups/new" class="row"
|
||||
style="gap: var(--sp-2); margin-top: var(--sp-4)">
|
||||
<input class="input" name="name" required maxlength="120" style="flex: 1"
|
||||
aria-label="{{ t('New data group name') }}"
|
||||
placeholder="{{ t('New data group name') }}">
|
||||
<button class="btn" type="submit">{{ icon("plus", "icon--sm") }} {{ t("Create") }}</button>
|
||||
</form>
|
||||
<p class="field__hint">{{ t("A group of your own, for keeping one provider away from the rest of your data. Nobody else can see it.") }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2 class="card__title">{{ t("Which group each connection reads") }}</h2>
|
||||
{% if may_manage_groups %}
|
||||
<p class="card__lede">{{ t("Your choice applies to you alone. Following the instance keeps up with whatever the administrator sets. Moving a connection moves no data: from then on it reads the other group, and your chats on it that were started in the old group can no longer use it.") }}</p>
|
||||
<form method="post" action="/api/preferences/data-groups" class="form-grid">
|
||||
{% for row in group_connections %}
|
||||
<div class="field">
|
||||
<label class="field__label" for="dg-{{ row.connection.id }}">{{ row.connection.name }}</label>
|
||||
<select class="select" id="dg-{{ row.connection.id }}" name="group__{{ row.connection.id }}">
|
||||
<option value="">{{ t("Follow the instance (%(group)s)", group=group_names.get(row.instance, '')) }}</option>
|
||||
{% for group in data_groups %}
|
||||
<option value="{{ group.id }}" {{ 'selected' if row.chosen == group.id }}>{{ group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="field__hint">{{ t("Reads: %(group)s", group=group_names.get(row.in_force, '')) }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="btn-row">
|
||||
<button class="btn btn--primary" type="submit">{{ t("Save") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="card__lede">{{ t("Set by the administrator. Changing it for yourself needs the permission to manage your own data groups.") }}</p>
|
||||
<ul class="model-list">
|
||||
{% for row in group_connections %}
|
||||
<li class="model-list__item">
|
||||
<strong>{{ row.connection.name }}</strong>
|
||||
<span class="badge">{{ group_names.get(row.in_force, '') }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{# --- Security --- #}
|
||||
<section class="tabs__panel" data-tab="tab-security">
|
||||
<div class="card">
|
||||
|
||||
Reference in New Issue
Block a user