@yesdgq/claude-buddy 1.1.1017 → 1.1.1019

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.
@@ -79,17 +79,40 @@
79
79
  <li><strong>{{ t.feature5Title }}</strong>:{{ t.feature5Desc }}</li>
80
80
  </ul>
81
81
 
82
+ <h3 class="command-title">
83
+ <i class="fa fa-book"></i>
84
+ {{ t.installTitle }}
85
+ </h3>
86
+ <ul class="install-list">
87
+ <li
88
+ v-for="cmd in installCommands"
89
+ :key="cmd.command"
90
+ class="command-item"
91
+ :title="t.clickToCopy"
92
+ @click="copyCommand(cmd.command)"
93
+ >
94
+ <i class="fa fa-chevron-right"></i>
95
+ <code class="item-cmd">{{ cmd.command }}</code>
96
+ <span class="item-desc">{{ cmd.desc }}</span>
97
+ </li>
98
+ </ul>
99
+
82
100
  <h3 class="command-title">
83
101
  <i class="fa fa-book"></i>
84
102
  {{ t.commandUsageTitle }}
85
103
  </h3>
86
104
  <ul class="command-list">
87
- <li><i class="fa fa-chevron-right"></i><code>ccby config</code> {{ t.command1 }}</li>
88
- <li><i class="fa fa-chevron-right"></i><code>ccby</code> {{ t.command2 }}</li>
89
- <li><i class="fa fa-chevron-right"></i><code>ccby -r</code> {{ t.command3 }}</li>
90
- <li><i class="fa fa-chevron-right"></i><code>ccby -c</code> {{ t.command4 }}</li>
91
- <li><i class="fa fa-chevron-right"></i><code>ccby --version</code> {{ t.command5 }}</li>
92
- <li><i class="fa fa-chevron-right"></i><code>claude --version</code> {{ t.command6 }}</li>
105
+ <li
106
+ v-for="cmd in usageCommands"
107
+ :key="cmd.command"
108
+ class="command-item"
109
+ :title="t.clickToCopy"
110
+ @click="copyCommand(cmd.command)"
111
+ >
112
+ <i class="fa fa-chevron-right"></i>
113
+ <code class="item-cmd">{{ cmd.command }}</code>
114
+ <span class="item-desc">{{ cmd.desc }}</span>
115
+ </li>
93
116
  </ul>
94
117
  </div>
95
118
 
@@ -124,16 +147,18 @@
124
147
 
125
148
 
126
149
  <!-- AUTH 配置列表 -->
127
- <div v-if="!loading">
128
- <div v-for="(auth, index) in config.AUTH" :key="index"
129
- :class="['auth-card', { 'auth-card-current': index === config.activeIndex, 'auth-card-dragging': draggingIndex === index, 'auth-card-drag-over': dragOverIndex === index }]"
130
- @dragover="handleDragOver(index, $event)"
131
- @dragleave="handleDragLeave($event)"
132
- @drop="handleDrop(index, $event)">
133
- <div class="drag-handle"
134
- draggable="true"
135
- @dragstart="handleDragStart(index, $event)"
136
- @dragend="handleDragEnd($event)">
150
+ <div v-if="!loading" :class="{ 'auth-list-ready': authListReady }">
151
+ <div v-for="(auth, index) in config.AUTH" :key="authKeys[index] || index"
152
+ :class="['auth-card', getDragShiftClass(index), { 'auth-card-current': index === config.activeIndex, 'auth-card-dragging': draggingIndex === index, 'auth-card-drag-over': dragOverIndex === index }]"
153
+ :style="getDragShiftStyle(index)"
154
+ @dragover="handleDragOver(index, $event)"
155
+ @dragleave="handleDragLeave($event)"
156
+ @drop="handleDrop(index, $event)">
157
+ <div class="drag-handle"
158
+ draggable="true"
159
+ @dragstart="handleDragStart(index, $event)"
160
+ @drag="handleDrag($event)"
161
+ @dragend="handleDragEnd($event)">
137
162
  <i class="fa fa-ellipsis-v"></i>
138
163
  </div>
139
164
  <div class="auth-header">
@@ -217,11 +242,29 @@
217
242
  </button>
218
243
  </div>
219
244
  </div>
220
- </div>
221
-
222
- <!-- 模型管理 -->
223
- <div class="models-section" :class="{ 'is-expanded': showModelSections[index] }">
224
- <div class="models-header">
245
+ </div>
246
+
247
+ <div v-if="currentCli === 'Codex'" class="codex-default-model">
248
+ <div class="default-model-header">
249
+ <label>
250
+ <i class="fa fa-terminal"></i>
251
+ {{ t.defaultModelTitle }}
252
+ </label>
253
+ <span class="default-model-chip">Codex</span>
254
+ </div>
255
+ <div class="default-model-input-wrap">
256
+ <i class="fa fa-magic"></i>
257
+ <input
258
+ v-model="auth.DEFAULT_MODEL"
259
+ type="text"
260
+ :placeholder="t.defaultModelPlaceholder"
261
+ >
262
+ </div>
263
+ </div>
264
+
265
+ <!-- 模型管理 -->
266
+ <div v-if="currentCli !== 'Codex'" class="models-section" :class="{ 'is-expanded': showModelSections[index] }">
267
+ <div class="models-header">
225
268
  <label>
226
269
  <i class="fa fa-cogs"></i>
227
270
  {{ t.modelManagement }}
@@ -267,7 +310,7 @@
267
310
  </div>
268
311
 
269
312
  <!-- 环境变量配置 -->
270
- <div class="env-section" :class="{ 'is-expanded': showEnvSections[index] }">
313
+ <div v-if="currentCli !== 'Codex'" class="env-section" :class="{ 'is-expanded': showEnvSections[index] }">
271
314
  <div class="env-header">
272
315
  <label>
273
316
  <i class="fa fa-cogs"></i>
@@ -315,17 +358,8 @@
315
358
  </div>
316
359
  </div>
317
360
 
318
- <!-- 添加新配置按钮 -->
319
- <button
320
- @click="addAuth"
321
- class="btn btn-primary"
322
- >
323
- <i class="fa fa-plus-circle"></i>
324
- {{ t.addNewConfig }}
325
- </button>
326
-
327
- <!-- 保存按钮 -->
328
- <div class="btn-group">
361
+ <!-- 保存按钮 -->
362
+ <div class="btn-group">
329
363
  <button
330
364
  @click="saveConfig"
331
365
  :disabled="saving"
@@ -346,11 +380,20 @@
346
380
  </div>
347
381
  </div>
348
382
 
349
- <!-- Toast 容器 -->
350
- <div id="toast-container"></div>
351
-
352
- <!-- 右下角悬浮保存按钮 -->
353
- <button
383
+ <!-- Toast 容器 -->
384
+ <div id="toast-container"></div>
385
+
386
+ <!-- 右下角悬浮添加配置按钮 -->
387
+ <button
388
+ @click="addAuth"
389
+ class="fab-add"
390
+ :title="t.addNewConfig"
391
+ >
392
+ <i class="fa fa-plus"></i>
393
+ </button>
394
+
395
+ <!-- 右下角悬浮保存按钮 -->
396
+ <button
354
397
  @click="saveConfig"
355
398
  :disabled="saving"
356
399
  class="fab-save"
package/assets/config.js CHANGED
@@ -11,6 +11,8 @@ createApp({
11
11
  showTokens: [],
12
12
  showEnvSections: [],
13
13
  showModelSections: [],
14
+ authKeys: [],
15
+ authListReady: false,
14
16
  configPath: '',
15
17
  showHelp: false,
16
18
  currentCli: 'Claude',
@@ -25,9 +27,32 @@ createApp({
25
27
  { name: 'Codex', label: 'Codex', icon: 'images/gpt.png' },
26
28
  { name: 'Gemini', label: 'Gemini', icon: 'images/gemini.png' }
27
29
  ],
30
+ // 安装命令列表
31
+ installCommands: [
32
+ { command: 'npm install -g @yesdgq/claude-buddy -f', desc: '安装包(强制覆盖)' },
33
+ { command: 'npm update -g @yesdgq/claude-buddy', desc: '更新包,同步更新 claude code' },
34
+ { command: 'npm install -g @yesdgq/claude-buddy@latest', desc: '更新包到最新版,同步更新 claude code' },
35
+ { command: 'npm uninstall -g @yesdgq/claude-buddy', desc: '删除包' }
36
+ ],
37
+ // 命令使用列表
38
+ usageCommands: [
39
+ { command: 'ccby config', desc: '打开配置页面,配置您的 API 地址和访问令牌(TOKEN)' },
40
+ { command: 'ccby', desc: '启动claude code' },
41
+ { command: 'ccby -r', desc: '查看历史会话' },
42
+ { command: 'ccby -c', desc: '继续上次会话' },
43
+ { command: 'ccby --version', desc: '查看Claude-Buddy版本' },
44
+ { command: 'claude --version', desc: '查看Claude版本' }
45
+ ],
28
46
  // 拖拽相关状态
29
47
  draggingIndex: null,
30
48
  dragOverIndex: null,
49
+ dropTargetIndex: null,
50
+ draggedCardHeight: 0,
51
+ draggedCardVisualHeight: 0,
52
+ dragCardRects: [],
53
+ dragStartY: 0,
54
+ dragCurrentY: 0,
55
+ isDropping: false,
31
56
  // 版本更新信息
32
57
  updateInfo: {
33
58
  hasUpdate: false,
@@ -57,6 +82,7 @@ createApp({
57
82
  feature4Desc: 'Visual interface, configure in seconds',
58
83
  feature5Title: 'Developer First',
59
84
  feature5Desc: 'Built for developers who love efficiency',
85
+ installTitle: 'ccby Update/Install',
60
86
  commandUsageTitle: 'Command Usage',
61
87
  command1: 'Open the configuration page to set your API URL and access token',
62
88
  command2: 'Launch Claude Code',
@@ -73,6 +99,8 @@ createApp({
73
99
  deleteThisConfig: 'Delete this configuration',
74
100
  emptyConfigCannotDelete: 'Empty configuration cannot be deleted',
75
101
  modelManagement: 'Model Management',
102
+ defaultModelTitle: 'Default Model',
103
+ defaultModelPlaceholder: 'Enter default GPT model',
76
104
  collapse: 'Collapse',
77
105
  expand: 'Expand',
78
106
  modelNamePlaceholder: 'Model name',
@@ -108,9 +136,10 @@ createApp({
108
136
  baseUrlPlaceholder: 'https://api.example.com',
109
137
  tokenPlaceholder: 'Enter your API token',
110
138
  updateAvailable: 'New version available:',
111
- clickToCopy: 'Click to copy update command',
139
+ clickToCopy: 'Click to copy command',
112
140
  dismiss: 'Dismiss',
113
- updateCmdCopied: 'Update command copied!'
141
+ updateCmdCopied: 'Update command copied!',
142
+ cmdCopied: 'Command copied!'
114
143
  },
115
144
  zh: {
116
145
  helpTitle: '使用说明',
@@ -125,6 +154,7 @@ createApp({
125
154
  feature4Desc: '可视化界面,几秒完成设置',
126
155
  feature5Title: '开发者友好',
127
156
  feature5Desc: '专为追求效率的开发者打造',
157
+ installTitle: 'ccby更新/安装',
128
158
  commandUsageTitle: '命令使用',
129
159
  command1: '打开配置页面,配置您的 API 地址和访问令牌(TOKEN)',
130
160
  command2: '启动claude code',
@@ -141,6 +171,8 @@ createApp({
141
171
  deleteThisConfig: '删除此配置',
142
172
  emptyConfigCannotDelete: '空配置不能删除',
143
173
  modelManagement: '模型管理',
174
+ defaultModelTitle: '默认模型',
175
+ defaultModelPlaceholder: '输入默认gpt模型',
144
176
  collapse: '收起',
145
177
  expand: '展开',
146
178
  modelNamePlaceholder: '模型名称',
@@ -176,9 +208,10 @@ createApp({
176
208
  baseUrlPlaceholder: 'https://api.example.com',
177
209
  tokenPlaceholder: '输入您的 API 令牌',
178
210
  updateAvailable: '新版本可用:',
179
- clickToCopy: '点击复制更新命令',
211
+ clickToCopy: '点击复制命令',
180
212
  dismiss: '关闭',
181
- updateCmdCopied: '更新命令已复制!'
213
+ updateCmdCopied: '更新命令已复制!',
214
+ cmdCopied: '命令已复制!'
182
215
  }
183
216
  }
184
217
  };
@@ -300,6 +333,28 @@ createApp({
300
333
  });
301
334
  },
302
335
 
336
+ // 通用复制命令方法
337
+ copyCommand(cmd) {
338
+ navigator.clipboard.writeText(cmd).then(() => {
339
+ this.showToast(this.t.cmdCopied);
340
+ }).catch(() => {
341
+ // fallback: 创建临时textarea并选中文本
342
+ const textarea = document.createElement('textarea');
343
+ textarea.value = cmd;
344
+ textarea.style.position = 'fixed';
345
+ textarea.style.opacity = '0';
346
+ document.body.appendChild(textarea);
347
+ textarea.select();
348
+ try {
349
+ document.execCommand('copy');
350
+ this.showToast(this.t.cmdCopied);
351
+ } catch (err) {
352
+ this.showToast('Copy failed', 'error');
353
+ }
354
+ document.body.removeChild(textarea);
355
+ });
356
+ },
357
+
303
358
  // 重置服务器倒计时
304
359
  async resetTimer() {
305
360
  try {
@@ -388,6 +443,7 @@ createApp({
388
443
  cliType = this.currentCli.toLowerCase();
389
444
  }
390
445
  this.loading = true;
446
+ this.authListReady = false;
391
447
  this.error = null;
392
448
  try {
393
449
  const response = await axios.get('/api/config', {
@@ -416,6 +472,7 @@ createApp({
416
472
  name: '',
417
473
  BASE_URL: '',
418
474
  TOKEN: '',
475
+ DEFAULT_MODEL: '',
419
476
  MODELS: [],
420
477
  ENV: []
421
478
  }];
@@ -430,12 +487,16 @@ createApp({
430
487
  if (!auth.MODELS) {
431
488
  auth.MODELS = [];
432
489
  }
490
+ if (cliType === 'codex' && typeof auth.DEFAULT_MODEL !== 'string') {
491
+ auth.DEFAULT_MODEL = '';
492
+ }
433
493
  });
434
494
 
435
495
  // 先设置辅助数组,再设置 config,确保 Vue 响应式正确更新
436
496
  this.showTokens = new Array(config.AUTH.length).fill(false);
437
497
  this.showEnvSections = new Array(config.AUTH.length).fill(false);
438
498
  this.showModelSections = new Array(config.AUTH.length).fill(false);
499
+ this.authKeys = config.AUTH.map(() => this.createAuthKey());
439
500
 
440
501
  // 最后更新 config
441
502
  this.config = config;
@@ -447,6 +508,11 @@ createApp({
447
508
  this.showToast(this.t.configLoadFailed + ': ' + (err.response?.data?.error || err.message), 'error');
448
509
  } finally {
449
510
  this.loading = false;
511
+ this.$nextTick(() => {
512
+ requestAnimationFrame(() => {
513
+ this.authListReady = true;
514
+ });
515
+ });
450
516
  }
451
517
  },
452
518
 
@@ -455,6 +521,7 @@ createApp({
455
521
  this.error = null;
456
522
  this.successMessage = null;
457
523
  try {
524
+ this.normalizeCodexDefaults();
458
525
  // 创建配置副本,过滤掉空配置项
459
526
  const configToSave = {
460
527
  ...this.config,
@@ -483,17 +550,28 @@ createApp({
483
550
  }
484
551
  },
485
552
 
553
+ normalizeCodexDefaults() {
554
+ if (this.currentCli !== 'Codex') {
555
+ return;
556
+ }
557
+ this.config.AUTH.forEach(auth => {
558
+ auth.DEFAULT_MODEL = (auth.DEFAULT_MODEL || '').trim();
559
+ });
560
+ },
561
+
486
562
  addAuth() {
487
563
  this.config.AUTH.push({
488
564
  name: '',
489
565
  BASE_URL: '',
490
566
  TOKEN: '',
567
+ DEFAULT_MODEL: '',
491
568
  MODELS: [],
492
569
  ENV: []
493
570
  });
494
571
  this.showTokens.push(false);
495
572
  this.showEnvSections.push(false);
496
573
  this.showModelSections.push(false);
574
+ this.authKeys.push(this.createAuthKey());
497
575
  // 重置服务器倒计时
498
576
  this.resetTimer();
499
577
  },
@@ -517,6 +595,7 @@ createApp({
517
595
  const deletedTokenState = this.showTokens[index];
518
596
  const deletedEnvState = this.showEnvSections[index];
519
597
  const deletedModelState = this.showModelSections[index];
598
+ const deletedAuthKey = this.authKeys[index];
520
599
  const oldActiveIndex = this.config.activeIndex;
521
600
 
522
601
  // 从列表中删除
@@ -524,6 +603,7 @@ createApp({
524
603
  this.showTokens.splice(index, 1);
525
604
  this.showEnvSections.splice(index, 1);
526
605
  this.showModelSections.splice(index, 1);
606
+ this.authKeys.splice(index, 1);
527
607
 
528
608
  // 调整 activeIndex:如果删除的是当前使用的配置,或者删除的配置在当前使用配置之前
529
609
  if (index === this.config.activeIndex) {
@@ -541,17 +621,20 @@ createApp({
541
621
  name: '',
542
622
  BASE_URL: '',
543
623
  TOKEN: '',
624
+ DEFAULT_MODEL: '',
544
625
  MODELS: [],
545
626
  ENV: []
546
627
  });
547
628
  this.showTokens.push(false);
548
629
  this.showEnvSections.push(false);
549
630
  this.showModelSections.push(false);
631
+ this.authKeys.push(this.createAuthKey());
550
632
  this.config.activeIndex = 0;
551
633
  }
552
634
 
553
635
  // 立即保存配置,过滤掉空配置项(但保留至少一个配置)
554
636
  try {
637
+ this.normalizeCodexDefaults();
555
638
  const filteredAuth = this.config.AUTH.filter(auth => !this.isEmptyConfig(auth));
556
639
  const configToSave = {
557
640
  ...this.config,
@@ -572,6 +655,7 @@ createApp({
572
655
  this.showTokens.splice(index, 0, deletedTokenState);
573
656
  this.showEnvSections.splice(index, 0, deletedEnvState);
574
657
  this.showModelSections.splice(index, 0, deletedModelState);
658
+ this.authKeys.splice(index, 0, deletedAuthKey);
575
659
  this.config.activeIndex = oldActiveIndex;
576
660
  }
577
661
  });
@@ -588,6 +672,7 @@ createApp({
588
672
 
589
673
  // 立即保存配置
590
674
  try {
675
+ this.normalizeCodexDefaults();
591
676
  const configToSave = {
592
677
  ...this.config,
593
678
  AUTH: this.config.AUTH.filter(auth => !this.isEmptyConfig(auth))
@@ -650,7 +735,12 @@ createApp({
650
735
 
651
736
  // 判断是否为空配置
652
737
  isEmptyConfig(auth) {
653
- return !auth.name && !auth.BASE_URL && !auth.TOKEN && (!auth.ENV || auth.ENV.length === 0) && (!auth.MODELS || auth.MODELS.length === 0);
738
+ const hasDefaultModel = this.currentCli === 'Codex' && auth.DEFAULT_MODEL && auth.DEFAULT_MODEL.trim();
739
+ return !auth.name && !auth.BASE_URL && !auth.TOKEN && !hasDefaultModel && (!auth.ENV || auth.ENV.length === 0) && (!auth.MODELS || auth.MODELS.length === 0);
740
+ },
741
+
742
+ createAuthKey() {
743
+ return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
654
744
  },
655
745
 
656
746
  // 切换使用说明显示/隐藏
@@ -658,6 +748,41 @@ createApp({
658
748
  this.showHelp = !this.showHelp;
659
749
  },
660
750
 
751
+ getDragShiftClass(index) {
752
+ if (this.draggingIndex === null || this.dropTargetIndex === null || this.isDropping || index === this.draggingIndex) {
753
+ return '';
754
+ }
755
+
756
+ if (this.draggingIndex < this.dropTargetIndex && index > this.draggingIndex && index <= this.dropTargetIndex) {
757
+ return 'auth-card-shift-up';
758
+ }
759
+
760
+ if (this.draggingIndex > this.dropTargetIndex && index >= this.dropTargetIndex && index < this.draggingIndex) {
761
+ return 'auth-card-shift-down';
762
+ }
763
+
764
+ return '';
765
+ },
766
+
767
+ getDragShiftStyle(index) {
768
+ if (index === this.draggingIndex) {
769
+ const offset = this.dragCurrentY - this.dragStartY;
770
+ return {
771
+ '--drag-follow-offset': `${offset}px`
772
+ };
773
+ }
774
+
775
+ const shiftClass = this.getDragShiftClass(index);
776
+ if (!shiftClass || !this.draggedCardHeight) {
777
+ return {};
778
+ }
779
+
780
+ const offset = shiftClass === 'auth-card-shift-up' ? -this.draggedCardHeight : this.draggedCardHeight;
781
+ return {
782
+ '--drag-shift-offset': `${offset}px`
783
+ };
784
+ },
785
+
661
786
  // 选择 CLI 类型
662
787
  async selectCli(cliName) {
663
788
  if (cliName === 'Gemini') {
@@ -672,25 +797,105 @@ createApp({
672
797
  // 拖拽开始
673
798
  handleDragStart(index, event) {
674
799
  this.draggingIndex = index;
800
+ this.dragOverIndex = null;
801
+ this.dropTargetIndex = null;
802
+ this.isDropping = false;
803
+ const card = event.currentTarget.closest('.auth-card');
804
+ const cardStyle = window.getComputedStyle(card);
805
+ this.draggedCardHeight = card.offsetHeight + parseFloat(cardStyle.marginBottom || 0);
806
+ this.draggedCardVisualHeight = card.offsetHeight;
807
+ this.dragCardRects = Array.from(document.querySelectorAll('.auth-card')).map(cardEl => {
808
+ const rect = cardEl.getBoundingClientRect();
809
+ return {
810
+ top: rect.top,
811
+ bottom: rect.bottom,
812
+ height: rect.height
813
+ };
814
+ });
815
+ this.dragStartY = event.clientY;
816
+ this.dragCurrentY = event.clientY;
675
817
  event.dataTransfer.effectAllowed = 'move';
676
818
  // 设置拖拽时的透明度
677
819
  event.target.style.opacity = '0.5';
678
820
  },
679
821
 
822
+ handleDrag(event) {
823
+ if (this.draggingIndex === null || event.clientY === 0) {
824
+ return;
825
+ }
826
+ this.dragCurrentY = event.clientY;
827
+ this.updateDropTargetByY(event.clientY);
828
+ },
829
+
680
830
  // 拖拽结束
681
831
  handleDragEnd(event) {
832
+ if (this.isDropping) {
833
+ event.target.style.opacity = '1';
834
+ return;
835
+ }
682
836
  this.draggingIndex = null;
683
837
  this.dragOverIndex = null;
838
+ this.dropTargetIndex = null;
839
+ this.draggedCardHeight = 0;
840
+ this.draggedCardVisualHeight = 0;
841
+ this.dragCardRects = [];
842
+ this.dragStartY = 0;
843
+ this.dragCurrentY = 0;
844
+ this.isDropping = false;
684
845
  event.target.style.opacity = '1';
685
846
  },
686
847
 
687
848
  // 拖拽经过
688
849
  handleDragOver(index, event) {
689
850
  event.preventDefault();
690
- if (this.draggingIndex === null || this.draggingIndex === index) {
851
+ if (this.draggingIndex === null) {
852
+ return;
853
+ }
854
+ if (this.draggingIndex === index) {
855
+ return;
856
+ }
857
+ if (event.clientY !== 0) {
858
+ this.dragCurrentY = event.clientY;
859
+ }
860
+ this.updateDropTargetByY(event.clientY);
861
+ if (this.dragOverIndex !== index) {
862
+ this.dragOverIndex = index;
863
+ }
864
+ },
865
+
866
+ updateDropTargetByY(clientY) {
867
+ if (this.draggingIndex === null || clientY === 0) {
691
868
  return;
692
869
  }
693
- this.dragOverIndex = index;
870
+
871
+ const dragOffset = clientY - this.dragStartY;
872
+ const draggedRect = this.dragCardRects[this.draggingIndex];
873
+ if (!draggedRect) {
874
+ return;
875
+ }
876
+
877
+ const draggedTop = draggedRect.top + dragOffset;
878
+ const draggedBottom = draggedTop + this.draggedCardVisualHeight;
879
+
880
+ const crossedIndexes = this.dragCardRects
881
+ .map((rect, index) => ({ rect, index }))
882
+ .filter(({ index }) => index !== this.draggingIndex)
883
+ .filter(({ rect, index }) => {
884
+ const targetMiddle = rect.top + rect.height / 2;
885
+ return this.draggingIndex < index
886
+ ? draggedBottom > targetMiddle
887
+ : draggedTop < targetMiddle;
888
+ })
889
+ .map(({ index }) => index);
890
+
891
+ const nextTargetIndex = this.draggingIndex < (crossedIndexes[0] ?? this.draggingIndex)
892
+ ? crossedIndexes[crossedIndexes.length - 1]
893
+ : crossedIndexes[0];
894
+
895
+ const normalizedTargetIndex = nextTargetIndex === undefined ? null : nextTargetIndex;
896
+ if (this.dropTargetIndex !== normalizedTargetIndex) {
897
+ this.dropTargetIndex = normalizedTargetIndex;
898
+ }
694
899
  },
695
900
 
696
901
  // 拖拽离开
@@ -701,10 +906,13 @@ createApp({
701
906
  // 放下
702
907
  async handleDrop(index, event) {
703
908
  event.preventDefault();
704
- if (this.draggingIndex === null || this.draggingIndex === index) {
909
+ const targetIndex = this.dropTargetIndex ?? index;
910
+ if (this.draggingIndex === null || this.draggingIndex === targetIndex) {
705
911
  this.dragOverIndex = null;
912
+ this.dropTargetIndex = null;
706
913
  return;
707
914
  }
915
+ this.isDropping = true;
708
916
 
709
917
  // 保存原始数据,以便失败时恢复
710
918
  const oldAuth = [...this.config.AUTH];
@@ -712,41 +920,63 @@ createApp({
712
920
  const oldShowTokens = [...this.showTokens];
713
921
  const oldShowEnvSections = [...this.showEnvSections];
714
922
  const oldShowModelSections = [...this.showModelSections];
923
+ const oldAuthKeys = [...this.authKeys];
715
924
 
716
925
  // 移动配置项
717
926
  const [movedAuth] = this.config.AUTH.splice(this.draggingIndex, 1);
718
- this.config.AUTH.splice(index, 0, movedAuth);
927
+ this.config.AUTH.splice(targetIndex, 0, movedAuth);
719
928
 
720
929
  // 同步移动显示状态
721
930
  const [movedToken] = this.showTokens.splice(this.draggingIndex, 1);
722
- this.showTokens.splice(index, 0, movedToken);
931
+ this.showTokens.splice(targetIndex, 0, movedToken);
723
932
 
724
933
  const [movedEnv] = this.showEnvSections.splice(this.draggingIndex, 1);
725
- this.showEnvSections.splice(index, 0, movedEnv);
934
+ this.showEnvSections.splice(targetIndex, 0, movedEnv);
726
935
 
727
936
  const [movedModel] = this.showModelSections.splice(this.draggingIndex, 1);
728
- this.showModelSections.splice(index, 0, movedModel);
937
+ this.showModelSections.splice(targetIndex, 0, movedModel);
938
+
939
+ const [movedAuthKey] = this.authKeys.splice(this.draggingIndex, 1);
940
+ this.authKeys.splice(targetIndex, 0, movedAuthKey);
729
941
 
730
942
  // 调整 activeIndex
731
943
  if (this.draggingIndex === this.config.activeIndex) {
732
944
  // 拖拽的是当前使用的配置,直接移动到目标位置
733
- this.config.activeIndex = index;
734
- } else if (this.draggingIndex < this.config.activeIndex && index >= this.config.activeIndex) {
945
+ this.config.activeIndex = targetIndex;
946
+ } else if (this.draggingIndex < this.config.activeIndex && targetIndex >= this.config.activeIndex) {
735
947
  // 从前向后拖拽,经过当前使用的配置
736
948
  // activeIndex 位置的元素被向前移了一位,所以需要减 1
737
949
  this.config.activeIndex--;
738
- } else if (this.draggingIndex > this.config.activeIndex && index <= this.config.activeIndex) {
950
+ } else if (this.draggingIndex > this.config.activeIndex && targetIndex <= this.config.activeIndex) {
739
951
  // 从后向前拖拽,经过当前使用的配置
740
952
  // activeIndex 位置的元素被向后移了一位,所以需要加 1
741
953
  this.config.activeIndex++;
742
954
  }
743
955
 
744
- // 重置拖拽状态
745
- this.draggingIndex = null;
956
+ this.draggingIndex = targetIndex;
746
957
  this.dragOverIndex = null;
958
+ this.dropTargetIndex = null;
959
+ this.dragCurrentY = this.dragStartY;
960
+
961
+ // 先完成 DOM 重排,下一帧再清理拖拽状态,避免松手时出现明显回弹刷新。
962
+ requestAnimationFrame(() => {
963
+ if (!this.isDropping || this.draggingIndex !== targetIndex) {
964
+ return;
965
+ }
966
+ this.draggingIndex = null;
967
+ this.dragOverIndex = null;
968
+ this.dropTargetIndex = null;
969
+ this.draggedCardHeight = 0;
970
+ this.draggedCardVisualHeight = 0;
971
+ this.dragCardRects = [];
972
+ this.dragStartY = 0;
973
+ this.dragCurrentY = 0;
974
+ this.isDropping = false;
975
+ });
747
976
 
748
977
  // 保存配置
749
978
  try {
979
+ this.normalizeCodexDefaults();
750
980
  const configToSave = {
751
981
  ...this.config,
752
982
  AUTH: this.config.AUTH.filter(auth => !this.isEmptyConfig(auth))
@@ -766,6 +996,7 @@ createApp({
766
996
  this.showTokens = oldShowTokens;
767
997
  this.showEnvSections = oldShowEnvSections;
768
998
  this.showModelSections = oldShowModelSections;
999
+ this.authKeys = oldAuthKeys;
769
1000
  }
770
1001
  }
771
1002
  },