@wowyuarm/dsh-agent-team 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/README.zh.md +5 -5
- package/cordis.patch.yml +26 -12
- package/package.json +50 -47
- package/packages/agent-team/lib/attachments.js +63 -4
- package/packages/agent-team/lib/compaction-engine.js +18 -0
- package/packages/agent-team/lib/context-continuity-host.js +198 -4
- package/packages/agent-team/lib/context-judge.js +150 -0
- package/packages/agent-team/lib/index.js +482 -97
- package/packages/agent-team/lib/invariant.js +36 -12
- package/packages/agent-team/lib/ledger.js +2 -1
- package/packages/agent-team/lib/member-runtime.js +2 -1
- package/packages/agent-team/lib/recovery.js +53 -4
- package/packages/agent-team/lib/typert.host.js +138 -44
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +75 -35
- package/packages/agent-team/lib/types/attachments.d.ts +32 -2
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/compaction-engine.d.ts +19 -0
- package/packages/agent-team/lib/types/compaction-engine.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +131 -4
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-judge.d.ts +82 -0
- package/packages/agent-team/lib/types/context-judge.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +259 -17
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/invariant.d.ts +27 -5
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +19 -6
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +28 -2
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +9 -0
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/lib/client.js +1006 -374
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/{HumanSettingsSection.d.ts → TeamSettingsSection.d.ts} +12 -4
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.js +195 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +7 -11
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +13 -3
- package/packages/client-agent-team/lib/types/client/context-judge.d.ts +64 -0
- package/packages/client-agent-team/lib/types/client/context-judge.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/context-judge.js +78 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +75 -11
- package/packages/client-agent-team/lib/types/client/judge-form.d.ts +99 -0
- package/packages/client-agent-team/lib/types/client/judge-form.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/judge-form.js +208 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts +52 -6
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +55 -6
- package/packages/tool-agent-team/README.md +6 -3
- package/packages/tool-agent-team/README.zh.md +6 -3
- package/packages/tool-agent-team/lib/context-tools.js +84 -112
- package/packages/tool-agent-team/lib/types/context-tools.d.ts +29 -13
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/agent-team/lib/pressure-policy.js +0 -235
- package/packages/agent-team/lib/types/pressure-policy.d.ts +0 -134
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +0 -118
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Team-owned context pressure policy.
|
|
3
|
-
*
|
|
4
|
-
* The Team preset mounts `compaction-basic` with `auto: false`; this
|
|
5
|
-
* coordinator owns both policy entry points instead — the proactive handoff
|
|
6
|
-
* notice near the effective handoff budget and the forced CompactionEngine
|
|
7
|
-
* reduction before any request at or above the effective hard limit — while
|
|
8
|
-
* reusing the public CompactionEngine implementation. The hard-limit
|
|
9
|
-
* translation lives in exactly one method; no caller re-derives it.
|
|
10
|
-
*
|
|
11
|
-
* Fail-closed rule: at the hard limit, a compaction that no-ops, throws, is
|
|
12
|
-
* cancelled, or does not advance the durable surface must block the model
|
|
13
|
-
* request (reject the step) rather than knowingly submit over the Team limit.
|
|
14
|
-
* A blocked Member keeps its log and reports a recoverable error.
|
|
15
|
-
* @module @wowyuarm/dsh-agent-team/pressure-policy
|
|
16
|
-
*/
|
|
17
|
-
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
18
|
-
import type { CompactionEngine } from '@deepseek-ai/dsh-compaction';
|
|
19
|
-
import type { AgentTeamMemberId } from './types.ts';
|
|
20
|
-
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
21
|
-
import { type SessionEventFold } from './session-event-cursor.ts';
|
|
22
|
-
/**
|
|
23
|
-
* Stable summary of the one-shot rollover pressure notice. It is a durable
|
|
24
|
-
* marker: the notice's own presence in a Session's log is what latches the
|
|
25
|
-
* policy, so the wording is read back and must not drift.
|
|
26
|
-
*/
|
|
27
|
-
export declare const CONTEXT_PRESSURE_NOTICE_SUMMARY = "Context pressure: prepare a handoff";
|
|
28
|
-
/**
|
|
29
|
-
* Whether this Session's own span already carries the pressure notice: either
|
|
30
|
-
* surfaced as a `user/message` or still queued in a durable
|
|
31
|
-
* `agent/inbox/spliced` insert. Expressed as a fold rather than a scan because
|
|
32
|
-
* the answer is monotone — once delivered it stays delivered for the
|
|
33
|
-
* generation — so the cursor only ever has to read events it has not seen.
|
|
34
|
-
*/
|
|
35
|
-
export declare const PRESSURE_NOTICE_FOLD: SessionEventFold<boolean, SessionEvent>;
|
|
36
|
-
/**
|
|
37
|
-
* One pressure-notice text. The wording is the engine's — the same sentence a
|
|
38
|
-
* fresh-context handoff is asked for through — so the notice and the rollover
|
|
39
|
-
* tool cannot drift apart; Team supplies only its own labels and input shape.
|
|
40
|
-
*/
|
|
41
|
-
export declare function contextPressureNoticeText(input: {
|
|
42
|
-
readonly usageTokens: number;
|
|
43
|
-
readonly handoffAt: number;
|
|
44
|
-
readonly hardLimit: number;
|
|
45
|
-
readonly activeClaims: readonly string[];
|
|
46
|
-
readonly runningJobs: readonly string[];
|
|
47
|
-
}): string;
|
|
48
|
-
export interface PressurePolicyOptions {
|
|
49
|
-
/** Resolve the live Agent of one Member. */
|
|
50
|
-
readonly agentForMember: (memberId: AgentTeamMemberId) => Agent | undefined;
|
|
51
|
-
/** Resolve the durable Member of one live Agent. */
|
|
52
|
-
readonly memberForAgent: (agent: Agent) => {
|
|
53
|
-
readonly memberId: AgentTeamMemberId;
|
|
54
|
-
readonly sessionId: Agent['id'];
|
|
55
|
-
} | undefined;
|
|
56
|
-
/** Resolve the Member-scoped compaction engine. */
|
|
57
|
-
readonly compactionForAgent: (agent: Agent) => CompactionEngine | undefined;
|
|
58
|
-
/** Effective budgets for one Member's current route; undefined means the route window is unknown. */
|
|
59
|
-
readonly limitsForAgent: (agent: Agent) => Promise<{
|
|
60
|
-
readonly usageTokens: number;
|
|
61
|
-
readonly hardLimit: number;
|
|
62
|
-
readonly handoffAt: number;
|
|
63
|
-
} | undefined> | {
|
|
64
|
-
readonly usageTokens: number;
|
|
65
|
-
readonly hardLimit: number;
|
|
66
|
-
readonly handoffAt: number;
|
|
67
|
-
} | undefined;
|
|
68
|
-
/** The model-visible active-Claim labels for one Member's notice. */
|
|
69
|
-
readonly activeClaimLabels: (memberId: AgentTeamMemberId) => readonly string[];
|
|
70
|
-
/** The model-visible running-job labels for one Member's notice. */
|
|
71
|
-
readonly runningJobLabels: (memberId: AgentTeamMemberId) => readonly string[];
|
|
72
|
-
/** Report a Member failure with a recoverable diagnostic. */
|
|
73
|
-
readonly failed: (memberId: AgentTeamMemberId, sessionId: Agent['id'], diagnostic: string) => void;
|
|
74
|
-
/** Log one coordinator diagnostic. */
|
|
75
|
-
readonly log: (message: string) => void;
|
|
76
|
-
}
|
|
77
|
-
export declare class PressurePolicyCoordinator {
|
|
78
|
-
private readonly options;
|
|
79
|
-
/**
|
|
80
|
-
* Retry budget per agent for the current provider-overflow sequence.
|
|
81
|
-
* Process-only by design: a restart re-earns one sequence per chain.
|
|
82
|
-
*/
|
|
83
|
-
private readonly overflowRetries;
|
|
84
|
-
/**
|
|
85
|
-
* Whether the one-shot notice was already delivered, folded incrementally per
|
|
86
|
-
* Member. The scan below is a monotone "has this ever happened" fold over the
|
|
87
|
-
* Session's own events, so a cursor can replace re-scanning the whole log on
|
|
88
|
-
* every step; identity guarding falls back to a cold fold when the Member's
|
|
89
|
-
* Session changed under the entry.
|
|
90
|
-
*/
|
|
91
|
-
private readonly noticeSeen;
|
|
92
|
-
private disposed;
|
|
93
|
-
constructor(options: PressurePolicyOptions);
|
|
94
|
-
/**
|
|
95
|
-
* The one-shot pressure notice is durable Session evidence, not process
|
|
96
|
-
* state: a `CONTEXT_PRESSURE_NOTICE_SUMMARY` notice already surfaced as a
|
|
97
|
-
* `user/message`, or still queued in a durable `agent/inbox/spliced`
|
|
98
|
-
* insert (a steered notice surfaces only at the next step boundary, and a
|
|
99
|
-
* Host restart replays the splice before surfacing), marks the current
|
|
100
|
-
* generation as already notified. A resume or restart stays quiet; a
|
|
101
|
-
* rollover starts a fresh Session whose own event span has no notice yet,
|
|
102
|
-
* which is exactly the documented re-arm.
|
|
103
|
-
*/
|
|
104
|
-
private noticeDelivered;
|
|
105
|
-
dispose(): void;
|
|
106
|
-
/** A successful assistant response ends any open overflow-recovery sequence. */
|
|
107
|
-
onAssistantMessage(agent: Agent): void;
|
|
108
|
-
/**
|
|
109
|
-
* Pre-step policy for one Member agent: below the handoff budget nothing
|
|
110
|
-
* happens; at the handoff budget one structured notice per generation is
|
|
111
|
-
* steered into the running turn; at the hard limit the request is forced
|
|
112
|
-
* through compaction first and fails closed when that cannot be proven.
|
|
113
|
-
* Returns the decision for the step: `continue` forwards, `reject` blocks.
|
|
114
|
-
*/
|
|
115
|
-
onPreStep(agent: Agent, signal: AbortSignal): Promise<{
|
|
116
|
-
readonly kind: 'continue' | 'reject' | 'notice';
|
|
117
|
-
}>;
|
|
118
|
-
/**
|
|
119
|
-
* Provider-overflow recovery: one bounded compact-and-retry sequence per
|
|
120
|
-
* open failure chain. Returns whether the request may retry once.
|
|
121
|
-
*/
|
|
122
|
-
onRequestError(agent: Agent, failure: {
|
|
123
|
-
readonly code?: string;
|
|
124
|
-
}, signal: AbortSignal): Promise<boolean>;
|
|
125
|
-
/**
|
|
126
|
-
* The one Team hard-limit translation: force a CompactionEngine reduction
|
|
127
|
-
* in the current Agent/Session and prove it advanced the durable surface
|
|
128
|
-
* (or measurably reduced pressure) before continuing. Background jobs are
|
|
129
|
-
* untouched — compaction never cancels or discards them.
|
|
130
|
-
* @returns whether the request may proceed.
|
|
131
|
-
*/
|
|
132
|
-
private enforceHardLimit;
|
|
133
|
-
}
|
|
134
|
-
//# sourceMappingURL=pressure-policy.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pressure-policy.d.ts","sourceRoot":"","sources":["../../src/pressure-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"HumanSettingsSection.d.ts","sourceRoot":"","sources":["../../../src/client/HumanSettingsSection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAElF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAmBnE,MAAM,WAAW,4BAA4B;IAC3C,mFAAmF;IACnF,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,sFAAsF;IACtF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACvD,gGAAgG;IAChG,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACzD,gEAAgE;IAChE,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAC/C,+EAA+E;IAC/E,WAAW,EAAE,qBAAqB,CAAA;CACnC;AAED,MAAM,MAAM,yBAAyB,GACjC,YAAY,CAAC,kBAAkB,CAAC,GAChC,WAAW,CAAC,MAAM,CAAC,GACnB,4BAA4B,CAAA;AAsBhC,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,+BAkKpE"}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useState } from 'react';
|
|
3
|
-
import { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
-
import { useHumanIdentity } from "./human-identity.js";
|
|
5
|
-
import { EnvironmentCheck } from "./EnvironmentCheck.js";
|
|
6
|
-
import { useAvatarImage } from "./avatar-image.js";
|
|
7
|
-
import css from './human-settings.module.css';
|
|
8
|
-
/**
|
|
9
|
-
* The Human's own settings page: display name, avatar, the local environment
|
|
10
|
-
* check, and the version footnote.
|
|
11
|
-
*
|
|
12
|
-
* The page owns no durable fact and no copy of one. Name, avatar, and version
|
|
13
|
-
* all arrive from the shared identity projection, so a save here moves the
|
|
14
|
-
* message rows and member refs at the same moment; a failed write keeps the
|
|
15
|
-
* typed name in the field and reports the Host's own reason. The environment
|
|
16
|
-
* check is a separate projection, read only, and belongs to the installation
|
|
17
|
-
* rather than to the Human.
|
|
18
|
-
*/
|
|
19
|
-
/** Host-side avatar ceiling (`ATTACHMENT_MAX_BYTES`): the settings page refuses larger files before the round trip. */
|
|
20
|
-
const AVATAR_MAX_BYTES = 10 * 1024 * 1024;
|
|
21
|
-
/**
|
|
22
|
-
* Run one durable write and reduce every failure to the message the page
|
|
23
|
-
* reports. A rejected Remote call (a dropped carrier, a refused write) is a
|
|
24
|
-
* failure like any other: without this the field would sit on "saving" forever
|
|
25
|
-
* and report nothing.
|
|
26
|
-
*/
|
|
27
|
-
async function failureOf(action) {
|
|
28
|
-
try {
|
|
29
|
-
return await action();
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
return error instanceof Error ? error.message : String(error);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/** First visible character of a display name, or the neutral `H` before one is known. */
|
|
36
|
-
function avatarInitial(name) {
|
|
37
|
-
const trimmed = (name ?? '').replace(/^@/, '').trim();
|
|
38
|
-
return trimmed === '' ? 'H' : trimmed.slice(0, 1).toUpperCase();
|
|
39
|
-
}
|
|
40
|
-
export function HumanSettingsSection(props) {
|
|
41
|
-
const { t, identity } = props;
|
|
42
|
-
const profile = useHumanIdentity(identity);
|
|
43
|
-
// Undefined means "follow the Host value": the field re-syncs whenever the
|
|
44
|
-
// profile changes underneath, without a background read clobbering a name
|
|
45
|
-
// the reader is still editing.
|
|
46
|
-
const [draft, setDraft] = useState(undefined);
|
|
47
|
-
const [saving, setSaving] = useState(false);
|
|
48
|
-
const [uploading, setUploading] = useState(false);
|
|
49
|
-
const [notice, setNotice] = useState(null);
|
|
50
|
-
const filePicker = useRef(null);
|
|
51
|
-
const name = draft ?? profile.name ?? '';
|
|
52
|
-
const dirty = draft !== undefined && draft.trim() !== (profile.name ?? '');
|
|
53
|
-
const emptyName = draft !== undefined && draft.trim() === '';
|
|
54
|
-
const hasAvatar = profile.avatarRef !== undefined;
|
|
55
|
-
// The circle answers to decoded bytes, not to a stored reference: the Host
|
|
56
|
-
// accepts any `image/…` payload, so a file this browser cannot read has to
|
|
57
|
-
// fall back to the initial exactly as a removed one does.
|
|
58
|
-
const avatar = useAvatarImage(profile.avatarUrl);
|
|
59
|
-
const submitName = async () => {
|
|
60
|
-
if (!dirty || emptyName || saving)
|
|
61
|
-
return;
|
|
62
|
-
setSaving(true);
|
|
63
|
-
setNotice(null);
|
|
64
|
-
const failure = await failureOf(() => props.saveName(name.trim()));
|
|
65
|
-
setSaving(false);
|
|
66
|
-
if (failure !== undefined) {
|
|
67
|
-
setNotice(t('humanSettingsNameFailed', { message: failure }));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
setDraft(undefined);
|
|
71
|
-
};
|
|
72
|
-
const pickAvatar = async (file) => {
|
|
73
|
-
if (file === undefined)
|
|
74
|
-
return;
|
|
75
|
-
setNotice(null);
|
|
76
|
-
if (!file.type.startsWith('image/')) {
|
|
77
|
-
setNotice(t('humanSettingsAvatarNotImage'));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (file.size > AVATAR_MAX_BYTES) {
|
|
81
|
-
setNotice(t('humanSettingsAvatarTooLarge'));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
setUploading(true);
|
|
85
|
-
const failure = await failureOf(() => props.uploadAvatar(file));
|
|
86
|
-
setUploading(false);
|
|
87
|
-
if (failure !== undefined)
|
|
88
|
-
setNotice(t('humanSettingsAvatarFailed', { message: failure }));
|
|
89
|
-
};
|
|
90
|
-
const removeAvatar = async () => {
|
|
91
|
-
setNotice(null);
|
|
92
|
-
const failure = await failureOf(() => props.removeAvatar());
|
|
93
|
-
if (failure !== undefined)
|
|
94
|
-
setNotice(t('humanSettingsAvatarFailed', { message: failure }));
|
|
95
|
-
};
|
|
96
|
-
// The page says what it is before it says what happened: the settings nav
|
|
97
|
-
// lists it beside the Harness's own pages, so the title alone leaves "whose
|
|
98
|
-
// profile is this, and where does it apply?" unanswered — and every state,
|
|
99
|
-
// including a failed read, has to answer it.
|
|
100
|
-
const pageHeader = _jsxs(_Fragment, { children: [_jsx("h2", { className: css.heading, children: t('humanSettingsTitle') }), _jsx("p", { className: css.intro, children: t('humanSettingsIntro') })] });
|
|
101
|
-
if (profile.status === 'loading' && profile.name === undefined) {
|
|
102
|
-
return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "status", children: t('humanSettingsLoading') })] });
|
|
103
|
-
}
|
|
104
|
-
if (profile.name === undefined) {
|
|
105
|
-
return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "alert", children: t('humanSettingsUnavailable', { message: profile.error ?? '' }) }), _jsx("div", { className: css.stateAction, children: _jsx(Button, { variant: "outline", onClick: () => { void identity.refresh(); }, children: t('retry') }) })] });
|
|
106
|
-
}
|
|
107
|
-
return (_jsxs("div", { className: css.section, children: [pageHeader, _jsxs("div", { className: css.rows, children: [_jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsName') }), _jsx("div", { className: css.desc, children: t('humanSettingsNameHint') })] }), _jsxs("form", { className: css.controls, onSubmit: (event) => {
|
|
108
|
-
event.preventDefault();
|
|
109
|
-
void submitName();
|
|
110
|
-
}, children: [_jsx(Input, { className: css.nameInput, "aria-label": t('humanSettingsName'), "aria-invalid": emptyName || undefined, value: name, disabled: saving, onChange: (event) => { setDraft(event.target.value); } }), _jsx(Button, { type: "submit", variant: "primary", disabled: saving || !dirty || emptyName, children: saving ? t('humanSettingsSaving') : t('humanSettingsSave') })] })] }), _jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsAvatar') }), _jsx("div", { className: css.desc, children: t('humanSettingsAvatarHint') })] }), _jsxs("div", { className: css.controls, children: [avatar.src === undefined
|
|
111
|
-
? _jsx("span", { className: `${css.identity} ${css.identityFallback}`, "data-avatar": "initial", "aria-hidden": "true", children: avatarInitial(profile.name) })
|
|
112
|
-
: _jsx("img", { className: `${css.identity} ${css.identityImage}`, "data-avatar": "image", src: avatar.src, alt: "", onError: avatar.failed }), _jsx("input", { ref: filePicker, type: "file", accept: "image/*", tabIndex: -1, "aria-hidden": "true", hidden: true, onChange: (event) => {
|
|
113
|
-
void pickAvatar(event.target.files?.[0]);
|
|
114
|
-
event.target.value = '';
|
|
115
|
-
} }), _jsx(Button, { variant: "outline", disabled: uploading, onClick: () => { filePicker.current?.click(); }, children: uploading ? t('humanSettingsUploading') : hasAvatar ? t('humanSettingsReplace') : t('humanSettingsUpload') }), hasAvatar && _jsx(Button, { disabled: uploading, onClick: () => { void removeAvatar(); }, children: t('humanSettingsRemoveAvatar') })] })] })] }), _jsx(EnvironmentCheck, { t: t, environment: props.environment }), _jsxs("div", { className: css.footnote, children: [_jsx("span", { children: t('humanSettingsVersion', { version: profile.version ?? '' }) }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("a", { className: css.link, href: profile.repoUrl ?? '', target: "_blank", rel: "noreferrer", children: "GitHub" }), profile.updateAvailable && profile.latestVersion !== undefined
|
|
116
|
-
? _jsx("a", { className: css.link, href: `${profile.repoUrl ?? ''}/releases`, target: "_blank", rel: "noreferrer", children: t('humanSettingsUpdateAvailable', { version: profile.latestVersion }) })
|
|
117
|
-
: null] }), emptyName && _jsx("p", { className: css.notice, children: t('humanSettingsNameEmpty') }), notice === null ? null : _jsx("p", { className: css.notice, role: "alert", children: notice })] }));
|
|
118
|
-
}
|