ccswitch-shared-ui.local

shared ui code

Ten skrypt nie powinien być instalowany bezpośrednio. Jest to biblioteka dla innych skyptów do włączenia dyrektywą meta // @require https://update.greasyfork.org/scripts/582598/1850862/ccswitch-shared-uilocal.js

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// Shared local development helpers for CC Switch import userscripts.
// Load this file before any local business entry via Tampermonkey @require.
// @name         ccswitch-shared-ui.local
// @version      0.0.1
// @author       irisWirisW (https://github.com/irisWirisW)
// @license      AGPL-3.0-or-later
(() => {
  "use strict";

  if (window.CCSwitchSharedUI) return;

  const DEFAULT_APP = "codex";
  const SHARED_DIALOG_STYLE_ID = "ccs-shared-dialog-style";
  const SHARED_DIALOG_CSS = `
    .ccs-shared-modal {
      --ccs-overlay: rgba(2, 6, 23, 0.6);
      --ccs-panel: rgba(15, 23, 42, 0.86);
      --ccs-panel-strong: rgba(30, 41, 59, 0.78);
      --ccs-panel-border: rgba(148, 163, 184, 0.22);
      --ccs-text: #e5eefb;
      --ccs-muted: #94a3b8;
      --ccs-input-bg: rgba(15, 23, 42, 0.72);
      --ccs-input-border: rgba(148, 163, 184, 0.26);
      --ccs-primary: #38bdf8;
      --ccs-primary-strong: #0284c7;
      --ccs-focus: rgba(56, 189, 248, 0.18);
      position: fixed;
      inset: 0;
      background: var(--ccs-overlay);
      display: none;
      align-items: center;
      justify-content: center;
      z-index: 99999;
      padding: 18px;
      backdrop-filter: blur(10px);
      -webkit-backdrop-filter: blur(10px);
      color-scheme: dark;
    }
    .ccs-shared-modal.open {
      display: flex;
    }
    .ccs-shared-panel {
      width: min(600px, 100%);
      max-height: calc(100vh - 36px);
      overflow: auto;
      border-radius: 18px;
      background:
        radial-gradient(circle at 16% 8%, rgba(56, 189, 248, 0.18), transparent 30%),
        radial-gradient(circle at 82% 0%, rgba(45, 212, 191, 0.12), transparent 26%),
        linear-gradient(135deg, var(--ccs-panel-strong), var(--ccs-panel));
      border: 1px solid var(--ccs-panel-border);
      box-shadow:
        0 24px 70px rgba(0, 0, 0, 0.42),
        0 1px 0 rgba(255, 255, 255, 0.12) inset;
      color: var(--ccs-text);
      padding: 18px;
      font-size: 13px;
      backdrop-filter: blur(22px) saturate(150%);
      -webkit-backdrop-filter: blur(22px) saturate(150%);
    }
    .ccs-shared-head {
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 16px;
      margin-bottom: 14px;
    }
    .ccs-shared-kicker {
      width: fit-content;
      border-radius: 999px;
      border: 1px solid rgba(56, 189, 248, 0.24);
      background: rgba(8, 47, 73, 0.46);
      color: #7dd3fc;
      font-size: 11px;
      font-weight: 700;
      line-height: 1;
      padding: 5px 8px;
      margin-bottom: 8px;
    }
    .ccs-shared-close {
      width: 32px;
      height: 32px;
      flex: 0 0 32px;
      border-radius: 999px;
      border: 1px solid rgba(148, 163, 184, 0.24);
      background: rgba(15, 23, 42, 0.48);
      color: #cbd5e1;
      font-size: 20px;
      line-height: 1;
      cursor: pointer;
      box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset;
    }
    .ccs-shared-title {
      font-size: 20px;
      font-weight: 700;
      line-height: 1.25;
      color: var(--ccs-text);
    }
    .ccs-shared-message {
      color: #cbd5e1;
      font-size: 12px;
      line-height: 1.5;
      margin: 0;
      padding: 10px 12px;
      border: 1px solid rgba(148, 163, 184, 0.16);
      border-radius: 12px;
      background: rgba(15, 23, 42, 0.46);
    }
    .ccs-shared-hint {
      margin-top: 7px;
      font-size: 12px;
      color: #fde68a;
      line-height: 1.4;
      display: none;
      padding: 8px 10px;
      border-radius: 10px;
      background: rgba(120, 53, 15, 0.24);
      border: 1px solid rgba(245, 158, 11, 0.22);
    }
    .ccs-shared-panel label {
      display: block;
      color: #cbd5e1;
      font-size: 12px;
      font-weight: 700;
      margin: 0 0 6px;
    }
    .ccs-shared-overview {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(220px, 1fr);
      gap: 12px 18px;
      align-items: stretch;
      margin-bottom: 14px;
    }
    .ccs-shared-primary-fields,
    .ccs-shared-notes {
      min-width: 0;
      display: grid;
      gap: 12px;
      align-content: start;
    }
    .ccs-shared-grid {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      gap: 12px;
    }
    .ccs-shared-field {
      min-width: 0;
    }
    .ccs-shared-field-wide {
      grid-column: 1 / -1;
    }
    .ccs-shared-panel input,
    .ccs-shared-panel select {
      width: 100%;
      display: block;
      box-sizing: border-box;
      min-height: 38px;
      border-radius: 11px;
      border: 1px solid var(--ccs-input-border);
      background-color: var(--ccs-input-bg);
      color: var(--ccs-text);
      padding: 8px 11px;
      font-size: 13px;
      outline: none;
      box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset;
      transition:
        border-color 140ms ease,
        box-shadow 140ms ease,
        background 140ms ease;
    }
    .ccs-shared-panel input:focus,
    .ccs-shared-panel select:focus {
      border-color: rgba(56, 189, 248, 0.72);
      background-color: rgba(15, 23, 42, 0.9);
      box-shadow:
        0 0 0 4px var(--ccs-focus),
        0 1px 0 rgba(255, 255, 255, 0.1) inset;
    }
    .ccs-shared-panel select {
      appearance: none;
      -webkit-appearance: none;
      padding-right: 34px;
      background-image:
        linear-gradient(45deg, transparent 50%, rgba(203, 213, 225, 0.86) 50%),
        linear-gradient(135deg, rgba(203, 213, 225, 0.86) 50%, transparent 50%);
      background-position:
        calc(100% - 18px) calc(50% - 1px),
        calc(100% - 13px) calc(50% - 1px);
      background-size: 6px 6px, 6px 6px;
      background-repeat: no-repeat;
    }
    .ccs-shared-panel select:focus {
      background-image:
        linear-gradient(45deg, transparent 50%, rgba(224, 242, 254, 0.96) 50%),
        linear-gradient(135deg, rgba(224, 242, 254, 0.96) 50%, transparent 50%);
    }
    .ccs-shared-panel option {
      background: #0f172a;
      color: var(--ccs-text);
    }
    .ccs-shared-actions {
      margin-top: 16px;
      display: flex;
      justify-content: flex-end;
      gap: 8px;
      flex-wrap: wrap;
    }
    .ccs-shared-actions button {
      min-height: 34px;
      border-radius: 999px;
      border: 1px solid rgba(148, 163, 184, 0.24);
      background: rgba(15, 23, 42, 0.52);
      color: #cbd5e1;
      font-size: 12px;
      font-weight: 700;
      padding: 7px 13px;
      cursor: pointer;
      box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset;
    }
    .ccs-shared-actions .ccs-shared-primary {
      background: linear-gradient(135deg, var(--ccs-primary), var(--ccs-primary-strong));
      border-color: rgba(56, 189, 248, 0.3);
      color: #f8fafc;
      box-shadow:
        0 12px 26px rgba(14, 165, 233, 0.22),
        0 1px 0 rgba(255, 255, 255, 0.18) inset;
    }
    .ccs-shared-actions .ccs-shared-primary:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
    .ccs-shared-close:hover,
    .ccs-shared-actions button:hover {
      filter: brightness(1.08);
    }
    @media (max-width: 560px) {
      .ccs-shared-overview,
      .ccs-shared-grid {
        grid-template-columns: 1fr;
      }
      .ccs-shared-panel {
        border-radius: 16px;
        padding: 15px;
      }
      .ccs-shared-actions button {
        flex: 1 1 auto;
      }
    }
  `;

  function injectStyleOnce(styleId, cssText) {
    const existing = document.getElementById(styleId);
    if (existing) return existing;

    const style = document.createElement("style");
    style.id = styleId;
    style.textContent = cssText;
    document.head.appendChild(style);
    return style;
  }

  function injectSharedDialogStyles() {
    return injectStyleOnce(SHARED_DIALOG_STYLE_ID, SHARED_DIALOG_CSS);
  }

  function inferName(endpoint) {
    try {
      return new URL(endpoint).host;
    } catch {
      return "custom";
    }
  }

  function getDefaultNameByApp(app, endpoint, defaultApp = DEFAULT_APP) {
    if ((app || defaultApp) === "codex") return "custom";
    return inferName(endpoint);
  }

  function updateCodexNameHint(target, options) {
    if (!target) return;

    const {
      app,
      name,
      defaultApp = DEFAULT_APP,
      prefixText = "",
      okText = "当前已设置为 custom。",
      warnText = "当前不是 custom,可能导致 thread 出错。",
    } = options || {};

    if ((app || defaultApp) !== "codex") {
      target.style.display = "none";
      target.textContent = "";
      return;
    }

    const normalizedName = (name || "").trim();
    const statusText = normalizedName === "custom" ? okText : warnText;
    target.style.display = "block";
    target.textContent = `${prefixText}${statusText}`;
  }

  function bindAutoSelect(input) {
    if (!input) return;

    const selectAll = () => {
      input.focus();
      input.select();
    };

    input.addEventListener("focus", () => setTimeout(selectAll, 0));
    input.addEventListener("click", selectAll);
    input.addEventListener("mouseup", (event) => event.preventDefault());
  }

  function buildDialogShell(options) {
    const {
      panelClass = "",
      titleId,
      kicker,
      title,
      primaryHtml = "",
      notesHtml = "",
      gridHtml = "",
      actionsHtml = "",
    } = options || {};

    return `
      <div class="${panelClass} ccs-shared-panel" role="dialog" aria-modal="true" aria-labelledby="${titleId}">
        <div class="ccs-shared-head">
          <div>
            <div class="ccs-shared-kicker">${kicker}</div>
            <div class="ccs-shared-title" id="${titleId}">${title}</div>
          </div>
          <button type="button" class="ccs-shared-close" aria-label="关闭">×</button>
        </div>
        <div class="ccs-shared-overview">
          <div class="ccs-shared-primary-fields">${primaryHtml}</div>
          <div class="ccs-shared-notes">${notesHtml}</div>
        </div>
        <div class="ccs-shared-grid">${gridHtml}</div>
        <div class="ccs-shared-actions">${actionsHtml}</div>
      </div>
    `;
  }

  window.CCSwitchSharedUI = {
    DEFAULT_APP,
    injectStyleOnce,
    injectSharedDialogStyles,
    inferName,
    getDefaultNameByApp,
    updateCodexNameHint,
    bindAutoSelect,
    buildDialogShell,
  };
})();