Spaces:
Running
Running
File size: 5,908 Bytes
90f0300 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import { useEffect, useRef } from 'react';
import { Check, ChevronLeft, Loader2, RefreshCw, ShieldCheck, X } from 'lucide-react';
import { FeishuLogoIcon } from '../FeishuLogoIcon.jsx';
export function DocsPanel({ open, docs, busy, error, onClose, onConnect, onDisconnect, onOpenHome, onOpenAuth, onRefresh }) {
const closeButtonRef = useRef(null);
useEffect(() => {
if (open) {
closeButtonRef.current?.focus();
}
}, [open]);
if (!open) {
return null;
}
const cliInstalled = Boolean(docs?.cliInstalled);
const skillsInstalled = Boolean(docs?.skillsInstalled);
const configured = Boolean(docs?.configured);
const connected = Boolean(docs?.connected);
const authorizationReady = connected && Boolean(docs?.authorizationReady);
const missingScopes = Array.isArray(docs?.missingScopes) ? docs.missingScopes : [];
const needsExtraAuth = connected && (!authorizationReady || missingScopes.length > 0);
const slidesAuthorized = connected && Boolean(docs?.slidesAuthorized);
const sheetsAuthorized = connected && Boolean(docs?.sheetsAuthorized);
const authPending = docs?.authPending;
const setupItems = [
{ id: 'cli', label: 'lark-cli', ok: cliInstalled },
{ id: 'skills', label: '官方 skills', ok: skillsInstalled },
{ id: 'config', label: 'App 凭证', ok: configured },
{ id: 'auth', label: '用户授权', ok: connected },
{ id: 'slides', label: 'PPT 权限', ok: slidesAuthorized },
{ id: 'sheets', label: '表格权限', ok: sheetsAuthorized }
];
const subtitle = connected
? needsExtraAuth
? '待补权限'
: ''
: authPending?.status === 'polling'
? '等待授权'
: configured
? '未连接'
: '未配置';
const summary = authPending?.status === 'polling'
? '授权页已打开,完成后回到这里刷新状态。'
: connected
? needsExtraAuth
? '飞书账号已连接,但部分文档权限还没授权。补充授权后,Codex 可完整操作飞书文档、PPT、表格和云空间文件。'
: 'Codex 已可操作飞书文档、PPT、表格和云空间文件。'
: !cliInstalled
? '本机还没有检测到 lark-cli。'
: !skillsInstalled
? '官方文档 skills 还没有安装完整。'
: configured
? '连接飞书账号后,Codex 才能以你的身份操作文档、PPT 和表格。'
: '请先在后端配置飞书 App ID 和 Secret。';
const canConnect = cliInstalled && skillsInstalled && configured;
return (
<section className="docs-panel" role="dialog" aria-modal="true" aria-label="飞书文档">
<header className="docs-panel-header">
<button ref={closeButtonRef} className="icon-button" type="button" onClick={onClose} aria-label="关闭文档">
<ChevronLeft size={22} />
</button>
<div className="docs-panel-title">
<strong>飞书文档</strong>
{subtitle ? <span>{subtitle}</span> : null}
</div>
<button className="icon-button" type="button" onClick={onClose} aria-label="关闭文档">
<X size={20} />
</button>
</header>
<div className="docs-panel-body">
<div className="docs-status-state">
<div className="docs-status-icon">
<FeishuLogoIcon size={58} />
</div>
<h2>飞书文档</h2>
<p>{summary}</p>
{error ? <div className="docs-panel-error">{error}</div> : null}
{authPending?.verificationUrl && (!connected || needsExtraAuth) ? (
<div className="docs-auth-box">
<span>授权码 {authPending.userCode || '已生成'}</span>
<button type="button" onClick={() => onOpenAuth(authPending.verificationUrl)}>
打开授权页
</button>
</div>
) : null}
<div className="docs-check-list">
{setupItems.map((item) => (
<div key={item.id} className={item.ok ? 'is-ok' : ''}>
{item.ok ? <Check size={15} /> : <X size={15} />}
<span>{item.label}</span>
</div>
))}
</div>
{needsExtraAuth && missingScopes.length ? (
<div className="docs-scope-hint">
缺少 {missingScopes.slice(0, 4).join('、')}
</div>
) : null}
<div className="docs-panel-actions">
{connected ? (
<>
<button type="button" onClick={needsExtraAuth ? onConnect : onOpenHome} disabled={needsExtraAuth && busy}>
{needsExtraAuth ? (
busy ? <Loader2 className="spin" size={16} /> : <ShieldCheck size={16} />
) : (
<FeishuLogoIcon size={18} />
)}
{needsExtraAuth ? '补充授权' : '打开飞书'}
</button>
<button type="button" onClick={onDisconnect} disabled={busy}>
{busy ? <Loader2 className="spin" size={16} /> : <X size={16} />}
断开
</button>
<button type="button" onClick={onRefresh} disabled={busy}>
<RefreshCw size={16} />
刷新
</button>
</>
) : (
<>
<button type="button" onClick={onConnect} disabled={!canConnect || busy}>
{busy ? <Loader2 className="spin" size={16} /> : <ShieldCheck size={16} />}
连接飞书
</button>
<button type="button" onClick={onRefresh} disabled={busy}>
<RefreshCw size={16} />
刷新
</button>
</>
)}
</div>
</div>
</div>
</section>
);
}
|