dsh-connect-minimaxcode 0.6.8 → 0.7.0

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/lib/client.js CHANGED
@@ -83,6 +83,26 @@
83
83
  usageRanking: '用量排名前 {percent}%',
84
84
  usageBestDay: '最活跃的一天 {date}',
85
85
  planNoSubscription: '未订阅 M Plan(仅积分包),因此没有限额窗口',
86
+ displayTitle: '显示',
87
+ showPill: '在对话输入框显示积分余额',
88
+ diagnosticsTitle: '诊断信息',
89
+ diagnosticsHint: '这一栏是只读的,可整段复制进 issue,它不会改变任何设置。',
90
+ diagnosticsPluginVersion: '插件版本',
91
+ diagnosticsRegion: '区域',
92
+ diagnosticsHosts: '上游地址',
93
+ diagnosticsAuthSource: '登录来源',
94
+ diagnosticsCatalogSource: '模型目录来源',
95
+ diagnosticsCapability: '宿主能力',
96
+ diagnosticsLastError: '最近一次上游错误',
97
+ failureCredentials: '登录凭据被拒绝。请在插件页重新登录 MiniMax Code。',
98
+ failureParameters: '请求的参数被上游拒绝。请调整提示词或选项后重试。',
99
+ failureSubscription: '账户没有可用的订阅套餐,因此调用被拒绝。请确认 M Plan 订阅是否生效。',
100
+ failureQuota: '账户的额度已用尽。请等待额度恢复,或查看套餐的限额窗口。',
101
+ failureRateLimited: '上游正在限流。请稍后重试。',
102
+ failureLocal: '请求没有发出,因此没有产生任何消耗。',
103
+ failureUpstream: '上游拒绝了这次请求。请把下面的原文一并反馈,以便定位。',
104
+ failureCharged: '本次请求可能已被上游计费,请勿立即重试。',
105
+ failureUpstreamDetail: '上游原文',
86
106
  planGeneric: 'M Plan 订阅',
87
107
  planExpires: '{date} 到期',
88
108
  generateTitle: '生成',
@@ -169,6 +189,26 @@
169
189
  usageRanking: 'Top {percent}% by usage',
170
190
  usageBestDay: 'Busiest day {date}',
171
191
  planNoSubscription: 'No M Plan subscription (credits only), so there are no usage windows',
192
+ displayTitle: 'Display',
193
+ showPill: 'Show the credit balance in the composer',
194
+ diagnosticsTitle: 'Diagnostics',
195
+ diagnosticsHint: 'This section is read-only. Copy it into an issue as it stands; it changes nothing.',
196
+ diagnosticsPluginVersion: 'Plugin version',
197
+ diagnosticsRegion: 'Region',
198
+ diagnosticsHosts: 'Upstream hosts',
199
+ diagnosticsAuthSource: 'Signed in via',
200
+ diagnosticsCatalogSource: 'Catalog source',
201
+ diagnosticsCapability: 'Host capabilities',
202
+ diagnosticsLastError: 'Last upstream error',
203
+ failureCredentials: 'The credential was refused. Sign in to MiniMax Code again on the plugin page.',
204
+ failureParameters: 'The upstream rejected the request parameters. Adjust the prompt or the options and try again.',
205
+ failureSubscription: 'The account holds no plan, so the call was refused. Check whether the M Plan subscription is active.',
206
+ failureQuota: "The allowance is spent. Wait for it to recover, or check the plan's limit windows.",
207
+ failureRateLimited: 'The upstream is throttling. Try again a little later.',
208
+ failureLocal: 'The request never left the plugin, so nothing was spent.',
209
+ failureUpstream: 'The upstream refused the request. Please report the original words below so it can be traced.',
210
+ failureCharged: 'This request may already have been charged for upstream; do not retry it at once.',
211
+ failureUpstreamDetail: 'Upstream words',
172
212
  planGeneric: 'M Plan subscription',
173
213
  planExpires: 'until {date}',
174
214
  generateTitle: 'Generate',
@@ -406,6 +446,97 @@
406
446
  return null;
407
447
  return h('div', { style: mutedStyle, 'data-minimax-plan-notice': true }, t('planNoSubscription'));
408
448
  }
449
+ /**
450
+ * The sentence that says what to do about a failed generation call.
451
+ *
452
+ * The kind is the server's verdict on what the failure *is*, and each
453
+ * kind has one remedy; the upstream's own words are kept beside it
454
+ * rather than in place of it, because the words are the evidence and
455
+ * the remedy is the help.
456
+ */
457
+ function generationRemedy(t, kind) {
458
+ switch (kind) {
459
+ case 'credentials': return t('failureCredentials');
460
+ case 'parameters': return t('failureParameters');
461
+ case 'subscription': return t('failureSubscription');
462
+ case 'quota': return t('failureQuota');
463
+ case 'rate-limited': return t('failureRateLimited');
464
+ case 'local': return t('failureLocal');
465
+ default: return t('failureUpstream');
466
+ }
467
+ }
468
+ /**
469
+ * Turn a failed call into what the person is shown.
470
+ *
471
+ * Three things travel together: the remedy, whether the upstream had
472
+ * already accepted the work, and the upstream's own words. The last
473
+ * one is shown collapsed rather than inline — it can be long, and it is
474
+ * read when a report is being written, not when the failure happens.
475
+ */
476
+ function generationNote(t, failure) {
477
+ const upstream = failure.upstream?.message ?? failure.error;
478
+ return {
479
+ text: generationRemedy(t, failure.kind),
480
+ upstream,
481
+ charged: failure.charged === true,
482
+ };
483
+ }
484
+ /**
485
+ * The diagnostics section: a read-only list of facts about this
486
+ * installation.
487
+ *
488
+ * Its purpose is to be copied. Every line is a fact, nothing here
489
+ * changes a setting, and the whole thing can be pasted into a report
490
+ * as it stands — which is the whole point, because the facts a report
491
+ * needs are exactly the ones nobody can be expected to remember.
492
+ */
493
+ function diagnosticsBlock(t, diagnostics) {
494
+ if (diagnostics === undefined)
495
+ return null;
496
+ const lines = [];
497
+ const add = (label, value) => {
498
+ if (value !== undefined && value.length > 0)
499
+ lines.push(`${label}: ${value}`);
500
+ };
501
+ add(t('diagnosticsPluginVersion'), diagnostics.pluginVersion);
502
+ add(t('diagnosticsRegion'), diagnostics.region);
503
+ add(t('diagnosticsHosts'), diagnostics.hosts?.join(' '));
504
+ add(t('diagnosticsAuthSource'), diagnostics.authSource);
505
+ add(t('diagnosticsCatalogSource'), diagnostics.catalogSource);
506
+ add(t('diagnosticsCapability'), diagnostics.capability === undefined
507
+ ? undefined
508
+ : Object.entries(diagnostics.capability).map(([key, value]) => `${key}=${value}`).join(' '));
509
+ add(t('diagnosticsLastError'), diagnostics.lastUpstreamError);
510
+ if (lines.length === 0)
511
+ return null;
512
+ return h('div', { style: boxStyle, 'data-minimax-diagnostics': true }, h('div', { style: boxTitleStyle }, t('diagnosticsTitle')), h('pre', { style: upstreamTextStyle, 'data-minimax-diagnostics-text': true }, lines.join('\n')), h('div', { style: mutedStyle }, t('diagnosticsHint')));
513
+ }
514
+ /**
515
+ * A note the card shows.
516
+ *
517
+ * Most notes are one sentence — the delivery refusals name what is
518
+ * missing — while a failed call carries its evidence with it. Both are
519
+ * drawn by the same helper, so the two kinds sit in one place.
520
+ */
521
+ function noteBlock(t, note) {
522
+ if (typeof note === 'string')
523
+ return h('span', { style: mutedStyle }, note);
524
+ return generationNoteBlock(t, note);
525
+ }
526
+ /**
527
+ * The note as it is drawn: the remedy, then the upstream's words.
528
+ *
529
+ * The charged case says so in its own line: a person who was told
530
+ * only "try again" would spend again, and the upstream may already
531
+ * have taken the first one.
532
+ */
533
+ function generationNoteBlock(t, note) {
534
+ return h('div', { style: mutedStyle, 'data-minimax-generation-note': true }, h('div', null, note.text), note.charged === true
535
+ ? h('div', { style: warnStyle, 'data-minimax-generation-charged': true }, t('failureCharged'))
536
+ : null, note.upstream === undefined
537
+ ? null
538
+ : h('details', { style: mutedStyle, 'data-minimax-generation-upstream': true }, h('summary', null, t('failureUpstreamDetail')), h('pre', { style: upstreamTextStyle }, note.upstream)));
539
+ }
409
540
  /**
410
541
  * What the page card's plan box says when it has no windows to show.
411
542
  *
@@ -546,6 +677,10 @@
546
677
  const provider = (projection?.next ?? projection?.lastUsed)?.provider;
547
678
  if (provider !== undefined && provider !== MINIMAX_PROVIDER_ID)
548
679
  return null;
680
+ // The balance may be hidden: a display choice, applied at once.
681
+ const [status] = useStatus();
682
+ if (status?.showPill === false)
683
+ return null;
549
684
  return h(CreditPill, props);
550
685
  }
551
686
  /**
@@ -610,7 +745,7 @@
610
745
  }, []);
611
746
  const [prompt, setPrompt] = React.useState('');
612
747
  const [busy, setBusy] = React.useState(false);
613
- const [note, setNote] = React.useState('');
748
+ const [note, setNote] = React.useState(undefined);
614
749
  /**
615
750
  * The session's own send path.
616
751
  *
@@ -686,7 +821,7 @@
686
821
  if (text === '')
687
822
  return;
688
823
  setBusy(true);
689
- setNote('');
824
+ setNote(undefined);
690
825
  try {
691
826
  const response = await fetch(GENERATE_PATH, {
692
827
  method: 'POST',
@@ -695,7 +830,7 @@
695
830
  });
696
831
  const body = await response.json();
697
832
  if (body.ok !== true || body.dataUrl === undefined) {
698
- setNote(body.error ?? t('generateFailed'));
833
+ setNote(generationNote(t, body));
699
834
  return;
700
835
  }
701
836
  const part = imagePart(body.dataUrl, body.mediaType ?? 'image/jpeg');
@@ -737,7 +872,7 @@
737
872
  setOpen(false);
738
873
  // The picture's own arrival is the feedback: it is in the
739
874
  // conversation now, so the composer needs no banner.
740
- setNote('');
875
+ setNote(undefined);
741
876
  return;
742
877
  }
743
878
  const drafts = draftFace();
@@ -760,7 +895,7 @@
760
895
  addAttachments(created.map(draft => draft.id));
761
896
  setPrompt('');
762
897
  setOpen(false);
763
- setNote('');
898
+ setNote(undefined);
764
899
  return;
765
900
  }
766
901
  setNote(t('generateSendFailed'));
@@ -777,7 +912,7 @@
777
912
  type: 'button',
778
913
  style: buttonStyle,
779
914
  title: t('generateToChatHint'),
780
- onClick: () => { setOpen(true); setNote(''); },
915
+ onClick: () => { setOpen(true); setNote(undefined); },
781
916
  }, t('generateToChat'));
782
917
  }
783
918
  return h('div', { style: dockStyle, 'data-minimax-generate-dock': true }, h('input', {
@@ -795,8 +930,8 @@
795
930
  type: 'button',
796
931
  style: buttonStyle,
797
932
  disabled: busy,
798
- onClick: () => { setOpen(false); setPrompt(''); setNote(''); },
799
- }, '✕'), note === '' ? null : h('span', { style: mutedStyle }, note));
933
+ onClick: () => { setOpen(false); setPrompt(''); setNote(undefined); },
934
+ }, '✕'), note === undefined ? null : noteBlock(t, note));
800
935
  }
801
936
  /** Ask the host to do something, and let the card read the result. */
802
937
  async function act(action, body) {
@@ -914,14 +1049,14 @@
914
1049
  const [resolution, setResolution] = React.useState('2K');
915
1050
  const [ratio, setRatio] = React.useState('16:9');
916
1051
  const [busy, setBusy] = React.useState(false);
917
- const [note, setNote] = React.useState('');
1052
+ const [note, setNote] = React.useState(undefined);
918
1053
  const [picture, setPicture] = React.useState(undefined);
919
1054
  const [video, setVideo] = React.useState(undefined);
920
1055
  const imageModels = options?.imageModels ?? ['MiniMax-H3-Image'];
921
1056
  /** Ask for a picture; the answer carries the bytes inline. */
922
1057
  async function makeImage() {
923
1058
  setBusy(true);
924
- setNote('');
1059
+ setNote(undefined);
925
1060
  setVideo(undefined);
926
1061
  try {
927
1062
  const response = await fetch(GENERATE_PATH, {
@@ -937,7 +1072,7 @@
937
1072
  });
938
1073
  const body = await response.json();
939
1074
  if (body.ok !== true || body.dataUrl === undefined) {
940
- setNote(body.error ?? t('generateFailed'));
1075
+ setNote(generationNote(t, body));
941
1076
  return;
942
1077
  }
943
1078
  setPicture({ dataUrl: body.dataUrl, size: body.size, saved: body.attachment !== undefined });
@@ -952,7 +1087,7 @@
952
1087
  /** Start a video task, then wait for it the way the upstream does. */
953
1088
  async function makeVideo() {
954
1089
  setBusy(true);
955
- setNote('');
1090
+ setNote(undefined);
956
1091
  setPicture(undefined);
957
1092
  try {
958
1093
  const response = await fetch(GENERATE_PATH, {
@@ -968,7 +1103,7 @@
968
1103
  });
969
1104
  const body = await response.json();
970
1105
  if (body.ok !== true || body.taskId === undefined) {
971
- setNote(body.error ?? t('generateFailed'));
1106
+ setNote(generationNote(t, body));
972
1107
  return;
973
1108
  }
974
1109
  // The task takes a minute and a half; the upstream is asked every
@@ -979,11 +1114,11 @@
979
1114
  const task = await polled.json();
980
1115
  if (task.url !== undefined) {
981
1116
  setVideo({ url: task.url, status: task.status });
982
- setNote('');
1117
+ setNote(undefined);
983
1118
  return;
984
1119
  }
985
1120
  if (task.status !== undefined && /fail|cancel/i.test(task.status)) {
986
- setNote(task.error ?? t('generateFailed'));
1121
+ setNote(generationNote(t, task));
987
1122
  return;
988
1123
  }
989
1124
  }
@@ -1022,7 +1157,7 @@
1022
1157
  style: actionButtonStyle,
1023
1158
  disabled: busy || prompt.trim() === '',
1024
1159
  onClick: () => { void (mode === 'image' ? makeImage() : makeVideo()); },
1025
- }, busy ? (mode === 'video' ? t('generateVideoRunning') : t('generateRunning')) : t('generateRun'))), note === '' ? null : h('div', { style: mutedStyle }, note), picture === undefined
1160
+ }, busy ? (mode === 'video' ? t('generateVideoRunning') : t('generateRunning')) : t('generateRun'))), note === undefined ? null : noteBlock(t, note), picture === undefined
1026
1161
  ? null
1027
1162
  : h('div', { style: generationResultStyle, 'data-minimax-generation-image': true }, h('img', { src: picture.dataUrl, alt: prompt, style: generatedImageStyle }), h('div', { style: rowStyle }, h('span', null, picture.size === undefined ? t('generateImageDone') : `${t('generateImageDone')} · ${picture.size}`), picture.saved === true ? h('span', null, t('generateSaved')) : null, h('a', { href: picture.dataUrl, download: 'minimax-h3-image.jpg', style: linkStyle }, t('generateDownload')))), video === undefined
1028
1163
  ? null
@@ -1092,7 +1227,20 @@
1092
1227
  ? h('div', { style: { ...boxStyle, ...span2Style }, 'data-minimax-usage-profile': true }, h('div', { style: boxTitleStyle }, t('accountUsage')), h('div', { style: profileGridStyle }, ...profileLines.map((line, index) => h('div', { key: `profile-${index}` }, line))))
1093
1228
  : null, login !== undefined && login.state !== 'signed-in'
1094
1229
  ? h('div', { style: span2Style }, loginBlock(t, login, refresh))
1095
- : null)),
1230
+ : null,
1231
+ // The read-only facts, last in the card: a report is made from
1232
+ // them, and they are of no use to the person using the plugin.
1233
+ diagnosticsBlock(t, status?.diagnostics),
1234
+ // A display choice: it takes effect at once, with no draft to
1235
+ // save. The other things on this page are facts to read; this
1236
+ // is the one control, and it changes only what is drawn.
1237
+ h('div', { style: { ...boxStyle, ...span2Style }, 'data-minimax-pill-toggle': true }, h('div', { style: boxTitleStyle }, t('displayTitle')), h('label', { style: { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12px' } }, h('input', {
1238
+ type: 'checkbox',
1239
+ checked: status?.showPill !== false,
1240
+ onChange: (event) => {
1241
+ void act('pillVisible', { showPill: event.target.checked });
1242
+ },
1243
+ }), h('span', null, t('showPill')))))),
1096
1244
  // The generation panel does not need the balance: it spends the
1097
1245
  // account's generation allowance, not its credits, so it stays
1098
1246
  // usable even when the balance cannot be read.
@@ -1232,6 +1380,20 @@
1232
1380
  };
1233
1381
  const planExpiryStyle = { fontSize: '13px', fontWeight: 400, lineHeight: '1.6' };
1234
1382
  const mutedStyle = { opacity: 0.7, fontSize: '12px' };
1383
+ /**
1384
+ * The upstream's own words, as they arrive.
1385
+ *
1386
+ * Kept monospaced and wrapped, because it is quoted evidence: a report
1387
+ * should carry it exactly as the upstream wrote it.
1388
+ */
1389
+ const upstreamTextStyle = {
1390
+ margin: '4px 0 0',
1391
+ whiteSpace: 'pre-wrap',
1392
+ wordBreak: 'break-word',
1393
+ fontFamily: 'var(--dsh-mono, ui-monospace, monospace)',
1394
+ fontSize: '11px',
1395
+ opacity: 0.9,
1396
+ };
1235
1397
  const actionButtonStyle = {
1236
1398
  appearance: 'none',
1237
1399
  background: 'transparent',
package/lib/index.d.ts CHANGED
@@ -719,6 +719,15 @@ interface PluginState {
719
719
  schemaVersion: number;
720
720
  /** Whether the plugin claims the daily grant on its own. */
721
721
  autoCheckin: boolean;
722
+ /**
723
+ * Whether the balance shows in the composer.
724
+ *
725
+ * A display choice rather than a setting about the account: it changes
726
+ * nothing upstream and nothing about what the plugin does, only whether
727
+ * the balance is drawn beside the composer. It therefore takes effect at
728
+ * once, with no draft to save — see the note on the settings below.
729
+ */
730
+ showPill?: boolean;
722
731
  /** The day the last claim happened, in the account's time zone. */
723
732
  lastClaimDay?: string;
724
733
  /** When the last claim happened, in milliseconds since the epoch. */
@@ -1026,6 +1035,29 @@ interface UsageStatus {
1026
1035
  /** How many requests were measured. */
1027
1036
  requests: number;
1028
1037
  }
1038
+ /**
1039
+ * The facts a report needs, gathered in one place.
1040
+ *
1041
+ * The point of this section is that it can be copied as a whole into a
1042
+ * report: every line is a fact about this installation, and nothing here
1043
+ * changes any setting — there is no control on it, by design.
1044
+ */
1045
+ interface DiagnosticsStatus {
1046
+ /** This plugin's own version. */
1047
+ pluginVersion?: string;
1048
+ /** The region this plugin talks to. */
1049
+ region?: string;
1050
+ /** The upstream hosts this plugin uses. */
1051
+ hosts?: string[];
1052
+ /** How the plugin is signed in. */
1053
+ authSource?: string;
1054
+ /** Where the model catalog came from. */
1055
+ catalogSource?: string;
1056
+ /** The most recent upstream failure, in its own words. */
1057
+ lastUpstreamError?: string;
1058
+ /** What the host offered the card, one entry per piece. */
1059
+ capability?: Record<string, string>;
1060
+ }
1029
1061
  /** The plan the account subscribes to, as the card reads it. */
1030
1062
  interface PlanStatus {
1031
1063
  /** Whether the account holds a plan. */
@@ -1137,6 +1169,10 @@ interface StatusDocument {
1137
1169
  usageProfile?: UsageProfileStatus;
1138
1170
  /** The plan the account subscribes to, when there is one. */
1139
1171
  plan?: PlanStatus;
1172
+ /** The facts a report needs, gathered in one place. */
1173
+ diagnostics?: DiagnosticsStatus;
1174
+ /** Whether the balance is drawn beside the composer. */
1175
+ showPill?: boolean;
1140
1176
  /** The choices the generation endpoints offer. */
1141
1177
  generation?: GenerationOptions;
1142
1178
  /** What the host offered the card the last time it looked. */
@@ -1178,6 +1214,10 @@ interface StatusDeps {
1178
1214
  usageProfile?: UsageProfileStatus;
1179
1215
  /** The plan the account subscribes to, when there is one. */
1180
1216
  plan?: PlanStatus;
1217
+ /** The facts a report needs, gathered in one place. */
1218
+ diagnostics?: DiagnosticsStatus;
1219
+ /** Whether the balance is drawn beside the composer. */
1220
+ showPill?: boolean;
1181
1221
  /** The choices the generation endpoints offer. */
1182
1222
  generation?: GenerationOptions;
1183
1223
  /** What the host offered the card the last time it looked. */