tijiao
This commit is contained in:
@@ -1,42 +1,285 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--bg-color: #0d1117;
|
||||||
--foreground: #171717;
|
--bg-gradient: radial-gradient(circle at top right, #1a2333, #0d1117);
|
||||||
}
|
--text-primary: #e6edf3;
|
||||||
|
--text-secondary: #8b949e;
|
||||||
|
--accent-color: #58a6ff;
|
||||||
|
--accent-hover: #3182ce;
|
||||||
|
--panel-bg: rgba(22, 27, 34, 0.65);
|
||||||
|
--panel-border: rgba(48, 54, 61, 0.5);
|
||||||
|
--input-bg: rgba(13, 17, 23, 0.7);
|
||||||
|
--input-border: #30363d;
|
||||||
|
--input-focus: #58a6ff;
|
||||||
|
--success-color: #238636;
|
||||||
|
--error-color: #f85149;
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
--glass-blur: blur(12px);
|
||||||
:root {
|
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
--transition-speed: 0.2s;
|
||||||
body {
|
|
||||||
max-width: 100vw;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: var(--foreground);
|
|
||||||
background: var(--background);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
body {
|
||||||
color: inherit;
|
font-family:
|
||||||
text-decoration: none;
|
"Inter",
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
Helvetica,
|
||||||
|
Arial,
|
||||||
|
sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
background-image: var(--bg-gradient);
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 100vh;
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
main {
|
||||||
html {
|
max-width: 1000px;
|
||||||
color-scheme: dark;
|
margin: 0 auto;
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, #58a6ff 0%, #bc8cff 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism Panel Container */
|
||||||
|
.glass-panel {
|
||||||
|
background: var(--panel-bg);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
-webkit-backdrop-filter: var(--glass-blur);
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
box-shadow: var(--glass-shadow);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
animation: fadeIn 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.glass-panel {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--panel-border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Controls */
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input[type="text"],
|
||||||
|
.form-group input[type="number"],
|
||||||
|
.form-group select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: var(--input-bg);
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus,
|
||||||
|
.form-group select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--input-focus);
|
||||||
|
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group select option {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input[type="checkbox"] {
|
||||||
|
appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid var(--input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
transition: all var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input[type="checkbox"]:checked {
|
||||||
|
background: var(--accent-color);
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input[type="checkbox"]:checked::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 6px;
|
||||||
|
width: 5px;
|
||||||
|
height: 10px;
|
||||||
|
border: solid white;
|
||||||
|
border-width: 0 2px 2px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-text {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--panel-border);
|
||||||
|
background: rgba(13, 17, 23, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
flex: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-speed);
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn.active {
|
||||||
|
color: var(--accent-color);
|
||||||
|
border-bottom-color: var(--accent-color);
|
||||||
|
background: rgba(88, 166, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Preview */
|
||||||
|
.code-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-container pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family:
|
||||||
|
"ui-monospace", "SFMono-Regular", "Menlo", "Monaco", "Consolas", monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #c9d1d9;
|
||||||
|
line-height: 1.6;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
background: rgba(48, 54, 61, 0.8);
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-speed);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn:hover {
|
||||||
|
background: var(--input-border);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn.copied {
|
||||||
|
background: var(--success-color);
|
||||||
|
border-color: var(--success-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,14 @@
|
|||||||
import Image from "next/image";
|
import ScriptGenerator from '@/components/ScriptGenerator';
|
||||||
import styles from "./page.module.css";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<main>
|
||||||
<main className={styles.main}>
|
<h1>OpenClaw 自动化脚本生成器</h1>
|
||||||
<Image
|
<p className="subtitle">
|
||||||
className={styles.logo}
|
为您的 OpenClaw 快速生成可跨平台的静默安装与配置脚本。
|
||||||
src="/next.svg"
|
</p>
|
||||||
alt="Next.js logo"
|
|
||||||
width={100}
|
<ScriptGenerator />
|
||||||
height={20}
|
</main>
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<div className={styles.intro}>
|
|
||||||
<h1>To get started, edit the page.tsx file.</h1>
|
|
||||||
<p>
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</a>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={styles.ctas}>
|
|
||||||
<a
|
|
||||||
className={styles.primary}
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className={styles.logo}
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className={styles.secondary}
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
201
src/components/ScriptGenerator.tsx
Normal file
201
src/components/ScriptGenerator.tsx
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { generateScript, OpenClawConfig } from '../utils/generateScripts';
|
||||||
|
|
||||||
|
export default function ScriptGenerator() {
|
||||||
|
const [config, setConfig] = useState<OpenClawConfig>({
|
||||||
|
workspace: '',
|
||||||
|
mode: 'local',
|
||||||
|
flow: 'quickstart',
|
||||||
|
nodeManager: 'npm',
|
||||||
|
installDaemon: true,
|
||||||
|
gatewayPort: '',
|
||||||
|
gatewayBind: '',
|
||||||
|
gatewayToken: '',
|
||||||
|
aiProvider: 'skip',
|
||||||
|
apiKey: '',
|
||||||
|
defaultModel: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState<'unix' | 'windows'>('unix');
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
|
const { name, value, type } = e.target;
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentScript = generateScript(config, activeTab);
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(currentScript);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy text: ', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="glass-panel">
|
||||||
|
|
||||||
|
{/* Configuration Form */}
|
||||||
|
<div className="form-section">
|
||||||
|
<h2 style={{ fontSize: '1.5rem', marginBottom: '1rem' }}>参数配置</h2>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="workspace">工作区路径 (Workspace Path)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="workspace"
|
||||||
|
name="workspace"
|
||||||
|
value={config.workspace}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="例如: /opt/openclaw 或 C:\openclaw"
|
||||||
|
/>
|
||||||
|
<span className="form-text">OpenClaw 存储其节点数据的目录路径。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label htmlFor="mode">运行模式 (Mode)</label>
|
||||||
|
<select name="mode" id="mode" value={config.mode} onChange={handleChange}>
|
||||||
|
<option value="local">本地模式 (Local)</option>
|
||||||
|
<option value="remote">中继模式 (Remote)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label htmlFor="flow">向导流程 (Flow)</label>
|
||||||
|
<select name="flow" id="flow" value={config.flow} onChange={handleChange}>
|
||||||
|
<option value="quickstart">快速开始 (Quickstart)</option>
|
||||||
|
<option value="advanced">高级配置 (Advanced)</option>
|
||||||
|
<option value="manual">手动 (Manual)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="nodeManager">Node 包管理器</label>
|
||||||
|
<select name="nodeManager" id="nodeManager" value={config.nodeManager} onChange={handleChange}>
|
||||||
|
<option value="npm">NPM</option>
|
||||||
|
<option value="pnpm">PNPM</option>
|
||||||
|
<option value="bun">BUN</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group" style={{ marginTop: '0.5rem' }}>
|
||||||
|
<label className="checkbox-group">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="installDaemon"
|
||||||
|
checked={config.installDaemon}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
自动安装后台守护进程 (Daemon)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style={{ fontSize: '1.1rem', marginTop: '1rem', borderBottom: '1px solid var(--panel-border)', paddingBottom: '0.5rem' }}>AI 模型配置</h3>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="aiProvider">AI 服务提供商 (AI Provider)</label>
|
||||||
|
<select name="aiProvider" id="aiProvider" value={config.aiProvider} onChange={handleChange}>
|
||||||
|
<option value="skip">跳过交互式认证 (默认)</option>
|
||||||
|
<option value="openai">OpenAI</option>
|
||||||
|
<option value="anthropic">Anthropic</option>
|
||||||
|
<option value="custom">自定义 (Custom)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{config.aiProvider !== 'skip' && (
|
||||||
|
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
||||||
|
<div className="form-group" style={{ flex: '1 1 100%' }}>
|
||||||
|
<label htmlFor="apiKey">API 密钥 (API Key)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="apiKey"
|
||||||
|
id="apiKey"
|
||||||
|
placeholder="在此输入您的 API Key"
|
||||||
|
value={config.apiKey}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group" style={{ flex: '1 1 100%' }}>
|
||||||
|
<label htmlFor="defaultModel">默认模型 (Default Model)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="defaultModel"
|
||||||
|
id="defaultModel"
|
||||||
|
placeholder="例如: gpt-4o, claude-3-5-sonnet-latest"
|
||||||
|
value={config.defaultModel}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h3 style={{ fontSize: '1.1rem', marginTop: '1rem', borderBottom: '1px solid var(--panel-border)', paddingBottom: '0.5rem' }}>网关设置 (可选)</h3>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label htmlFor="gatewayPort">网关端口 (Port)</label>
|
||||||
|
<input type="number" name="gatewayPort" id="gatewayPort" placeholder="8080" value={config.gatewayPort} onChange={handleChange} />
|
||||||
|
</div>
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label htmlFor="gatewayBind">网关绑定地址 (Bind)</label>
|
||||||
|
<input type="text" name="gatewayBind" id="gatewayBind" placeholder="0.0.0.0" value={config.gatewayBind} onChange={handleChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="gatewayToken">网关令牌 (Token)</label>
|
||||||
|
<input type="text" name="gatewayToken" id="gatewayToken" placeholder="留空则由系统自动生成" value={config.gatewayToken} onChange={handleChange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Script Preview */}
|
||||||
|
<div className="preview-section">
|
||||||
|
<div className="tabs">
|
||||||
|
<button
|
||||||
|
className={`tab-btn ${activeTab === 'unix' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('unix')}
|
||||||
|
>
|
||||||
|
Linux / Mac (.sh)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tab-btn ${activeTab === 'windows' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('windows')}
|
||||||
|
>
|
||||||
|
Windows (.ps1)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="code-container">
|
||||||
|
<button className={`copy-btn ${copied ? 'copied' : ''}`} onClick={handleCopy}>
|
||||||
|
{copied ? (
|
||||||
|
<>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||||
|
已复制!
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||||
|
复制脚本
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<pre>
|
||||||
|
<code>{currentScript}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
97
src/utils/generateScripts.ts
Normal file
97
src/utils/generateScripts.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
export interface OpenClawConfig {
|
||||||
|
workspace: string;
|
||||||
|
mode: 'local' | 'remote';
|
||||||
|
flow: 'quickstart' | 'advanced' | 'manual';
|
||||||
|
nodeManager: 'npm' | 'pnpm' | 'bun';
|
||||||
|
installDaemon: boolean;
|
||||||
|
gatewayPort: string;
|
||||||
|
gatewayBind: string;
|
||||||
|
gatewayToken: string;
|
||||||
|
aiProvider: 'openai' | 'anthropic' | 'custom' | 'skip';
|
||||||
|
apiKey: string;
|
||||||
|
defaultModel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateScript = (config: OpenClawConfig, platform: 'unix' | 'windows') => {
|
||||||
|
const args: string[] = ['--non-interactive'];
|
||||||
|
|
||||||
|
if (config.workspace) args.push(`--workspace "${config.workspace}"`);
|
||||||
|
if (config.mode) args.push(`--mode ${config.mode}`);
|
||||||
|
if (config.flow) args.push(`--flow ${config.flow}`);
|
||||||
|
if (config.nodeManager) args.push(`--node-manager ${config.nodeManager}`);
|
||||||
|
|
||||||
|
if (config.installDaemon) {
|
||||||
|
args.push('--install-daemon');
|
||||||
|
} else {
|
||||||
|
args.push('--skip-daemon');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.gatewayPort) args.push(`--gateway-port ${config.gatewayPort}`);
|
||||||
|
if (config.gatewayBind) args.push(`--gateway-bind ${config.gatewayBind}`);
|
||||||
|
if (config.gatewayToken) args.push(`--gateway-token "${config.gatewayToken}"`);
|
||||||
|
|
||||||
|
if (config.aiProvider === 'skip') {
|
||||||
|
args.push('--auth-choice skip');
|
||||||
|
} else {
|
||||||
|
args.push(`--auth-choice ${config.aiProvider}`);
|
||||||
|
if (config.apiKey) {
|
||||||
|
if (config.aiProvider === 'openai') args.push(`--openai-api-key "${config.apiKey}"`);
|
||||||
|
else if (config.aiProvider === 'anthropic') args.push(`--anthropic-api-key "${config.apiKey}"`);
|
||||||
|
else if (config.aiProvider === 'custom') args.push(`--custom-api-key "${config.apiKey}"`);
|
||||||
|
}
|
||||||
|
if (config.defaultModel) {
|
||||||
|
args.push(`--default-model "${config.defaultModel}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinedArgs = args.join(' ');
|
||||||
|
|
||||||
|
if (platform === 'unix') {
|
||||||
|
return `#!/bin/bash
|
||||||
|
|
||||||
|
# 将代码包裹在函数中,避免直接复制粘贴运行失败时导致终端退出
|
||||||
|
install_openclaw() {
|
||||||
|
# 检查当前系统环境是否安装了所选的包管理器
|
||||||
|
if ! command -v ${config.nodeManager} &> /dev/null
|
||||||
|
then
|
||||||
|
echo "错误: 未在您的系统中检测到 \`${config.nodeManager}\` 环境。"
|
||||||
|
echo "请先安装 Node.js (https://nodejs.org) 或相应的包管理工具后再运行此脚本。"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "正在使用 ${config.nodeManager} 全局安装 OpenClaw 最新版..."
|
||||||
|
${config.nodeManager === 'npm' ? 'npm install -g' : config.nodeManager === 'pnpm' ? 'pnpm add -g' : 'bun install -g'} openclaw@latest
|
||||||
|
|
||||||
|
echo "正在运行 OpenClaw 自动化配置向导..."
|
||||||
|
openclaw onboard ${joinedArgs}
|
||||||
|
|
||||||
|
echo "安装与配置完成!"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_openclaw
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return `# Windows PowerShell 自动化脚本 (建议以管理员身份运行)
|
||||||
|
|
||||||
|
# 将代码包裹在函数中,避免直接执行失败时导致终端异常
|
||||||
|
function Install-OpenClaw {
|
||||||
|
# 检查当前系统环境是否安装了所选的包管理器
|
||||||
|
if (!(Get-Command ${config.nodeManager} -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "错误: 未在您的系统中检测到 \`${config.nodeManager}\` 环境。" -ForegroundColor Red
|
||||||
|
Write-Host "请先下载并安装 Node.js (https://nodejs.org) 或相应的包管理工具后再运行此脚本。" -ForegroundColor Yellow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "正在使用 ${config.nodeManager} 全局安装 OpenClaw 最新版..."
|
||||||
|
${config.nodeManager === 'npm' ? 'npm install -g' : config.nodeManager === 'pnpm' ? 'pnpm add -g' : 'bun install -g'} openclaw@latest
|
||||||
|
|
||||||
|
Write-Host "正在运行 OpenClaw 自动化配置向导..."
|
||||||
|
openclaw onboard ${joinedArgs}
|
||||||
|
|
||||||
|
Write-Host "安装与配置完成!"
|
||||||
|
}
|
||||||
|
|
||||||
|
Install-OpenClaw
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user