feat(providers): add OpenAI/Claude connectivity test in edit sheet

- Introduce useConnectivityTest hook that drives per-key and bulk tests
  via apiCallApi against the chat-completions / messages endpoints, with
  signature-based status reset
- OpenAI form: each API key entry header now shows a status icon plus a
  per-key Test button, and the entries section gains a Test all button
- Claude form: enable the Test model field and add a Test button with
  inline status; falls back to the persisted apiKey when the edit form
  field is left blank
- Add ConnectivityStatusIcon, related styles (spin keyframes, buttons,
  error banner), and i18n keys (en/zh-CN/zh-TW/ru) for the test labels
  and validation messages
This commit is contained in:
LTbinglingfeng
2026-05-25 01:02:47 +08:00
parent 47ba6ab91c
commit bf299cffdf
8 changed files with 754 additions and 84 deletions
@@ -207,6 +207,125 @@
color: var(--muted-foreground);
}
.entryCardHeaderRight {
display: flex;
align-items: center;
gap: 6px;
}
.entriesToolbar {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
margin-bottom: 2px;
}
.connectivityRow {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.connectivityBtn {
display: inline-flex;
align-items: center;
gap: 6px;
height: 28px;
padding: 0 12px;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: background-color $transition-fast, border-color $transition-fast;
&:hover:not(:disabled) {
border-color: var(--primary-color);
color: var(--primary-color);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.connectivityBtnGhost {
display: inline-flex;
align-items: center;
gap: 6px;
height: 24px;
padding: 0 8px;
border-radius: var(--radius-md);
border: 1px solid transparent;
background: transparent;
color: var(--text-secondary);
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: background-color $transition-fast, color $transition-fast;
&:hover:not(:disabled) {
background: var(--bg-tertiary);
color: var(--text-primary);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.connectivityError {
border: 1px solid var(--destructive-30);
background: var(--destructive-10);
color: var(--destructive-color);
padding: 8px 10px;
border-radius: var(--radius-md);
font-size: 11px;
line-height: 1.4;
word-break: break-word;
}
.connectivityHintSuccess {
font-size: 11px;
color: var(--primary-color);
}
.statusIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
}
.statusIconLoading {
color: var(--text-secondary);
animation: spin 0.8s linear infinite;
}
.statusIconSuccess {
color: var(--primary-color);
}
.statusIconError {
color: var(--destructive-color);
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.entriesList {
display: flex;
flex-direction: column;