Skip to content
91 changes: 36 additions & 55 deletions components/animate-ui/components/code-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@
import * as React from 'react';
import { useTheme } from 'next-themes';

import { cn } from '@/lib/utils';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
TabsContents,
type TabsProps,
} from '@/components/animate-ui/components/tabs';
import { cn, replaceTabIndex } from '@/lib/utils';
import { Tabs, TabsContent, TabsList, TabsTrigger, TabsContents, type TabsProps } from '@/components/animate-ui/components/tabs';
import { CopyButton } from '@/components/animate-ui/buttons/copy';

type CodeTabsProps = {
codes: Record<string, string>;
type CodeItem = {
name: string;
lang?: string;
code: string;
};

type CodeTabsProps = {
codes: CodeItem[];
themes?: {
light: string;
dark: string;
Expand All @@ -25,12 +23,16 @@ type CodeTabsProps = {
onCopy?: (content: string) => void;
} & Omit<TabsProps, 'children'>;

/**
* Note: 在 MDX 文档中使用该组件时需要包裹一层 Wrapper,从内部传入 Props,以确保正确渲染代码块中的缩进。
*
* Refs: #99
*/
function CodeTabs({
codes,
lang = 'bash',
themes = {
light: 'github-light',
dark: 'github-dark',
dark: 'github-dark'
},
className,
defaultValue,
Expand All @@ -42,49 +44,46 @@ function CodeTabs({
}: CodeTabsProps) {
const { resolvedTheme } = useTheme();

const [highlightedCodes, setHighlightedCodes] = React.useState<Record<
string,
string
> | null>(null);
const [selectedCode, setSelectedCode] = React.useState<string>(
value ?? defaultValue ?? Object.keys(codes)[0] ?? '',
);
const [highlightedCodes, setHighlightedCodes] = React.useState<Record<string, string> | null>(null);
const [selectedCode, setSelectedCode] = React.useState<string>(value ?? defaultValue ?? codes[0]?.name ?? '');

React.useEffect(() => {
async function loadHighlightedCode() {
try {
const { codeToHtml } = await import('shiki');
const newHighlightedCodes: Record<string, string> = {};

for (const [command, val] of Object.entries(codes)) {
const highlighted = await codeToHtml(val, {
lang,
for (const codeItem of codes) {
const highlighted = await codeToHtml(codeItem.code, {
lang: codeItem.lang || codeItem.name.toLowerCase(),
themes: {
light: themes.light,
dark: themes.dark,
dark: themes.dark
},
defaultColor: resolvedTheme === 'dark' ? 'dark' : 'light',
defaultColor: resolvedTheme === 'dark' ? 'dark' : 'light'
});

newHighlightedCodes[command] = highlighted;
newHighlightedCodes[codeItem.name] = replaceTabIndex(highlighted);
}

setHighlightedCodes(newHighlightedCodes);
} catch (error) {
console.error('Error highlighting codes', error);
setHighlightedCodes(codes);
// 降级处理:创建简单的文本映射
const fallbackCodes: Record<string, string> = {};
codes.forEach((item) => {
fallbackCodes[item.name] = item.code;
});
setHighlightedCodes(fallbackCodes);
}
}
loadHighlightedCode();
}, [resolvedTheme, lang, themes.light, themes.dark, codes]);
}, [resolvedTheme, themes.light, themes.dark, codes]);

return (
<Tabs
data-slot="install-tabs"
className={cn(
'w-full gap-0 bg-muted/50 rounded-xl border overflow-hidden',
className,
)}
className={cn('w-full gap-0 bg-muted/50 rounded-xl border overflow-hidden', className)}
{...props}
value={selectedCode}
onValueChange={(val) => {
Expand All @@ -100,44 +99,26 @@ function CodeTabs({
<div className="flex gap-x-3 h-full">
{highlightedCodes &&
Object.keys(highlightedCodes).map((code) => (
<TabsTrigger
key={code}
value={code}
className="text-muted-foreground data-[state=active]:text-current px-0"
>
<TabsTrigger key={code} value={code} className="text-muted-foreground data-[state=active]:text-current px-0">
{code}
</TabsTrigger>
))}
</div>

{copyButton && highlightedCodes && (
<CopyButton
content={codes[selectedCode]}
size="sm"
variant="ghost"
className="-me-2 bg-transparent hover:bg-black/5 dark:hover:bg-white/10"
onCopy={onCopy}
/>
<CopyButton content={codes.find((item) => item.name === selectedCode)?.code || ''} size="sm" variant="ghost" className="-me-2 bg-transparent hover:bg-black/5 dark:hover:bg-white/10" onCopy={onCopy} />
)}
</TabsList>
<TabsContents data-slot="install-tabs-contents">
{highlightedCodes &&
Object.entries(highlightedCodes).map(([code, val]) => (
<TabsContent
data-slot="install-tabs-content"
key={code}
className="w-full text-sm flex items-center p-4 overflow-auto"
value={code}
>
<div
className="[&>pre,_&_code]:!bg-transparent [&>pre,_&_code]:[background:transparent_!important] [&>pre,_&_code]:border-none [&_code]:!text-[13px]"
dangerouslySetInnerHTML={{ __html: val }}
/>
<TabsContent data-slot="install-tabs-content" key={code} className="w-full text-sm flex items-center p-4 overflow-auto" value={code}>
<div className="[&>pre,_&_code]:!bg-transparent [&>pre,_&_code]:[background:transparent_!important] [&>pre,_&_code]:border-none [&_code]:!text-[13px]" dangerouslySetInnerHTML={{ __html: val }} />
</TabsContent>
))}
</TabsContents>
</Tabs>
);
}

export { CodeTabs, type CodeTabsProps };
export { CodeTabs, type CodeTabsProps, type CodeItem };
168 changes: 96 additions & 72 deletions content/AI/Fuclaude/Fuclaude.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,92 +43,116 @@ import { CodeTabs } from '@/components/animate-ui/components/code-tabs'

<br></br>

<CodeTabs
codes={{
Curl: `curl 'http://YOURDOMAIN/manage-api/auth/oauth_token' \\
-H 'Content-Type: application/json' \\
-d '{
"session_key": SESSION_KEY,
"unique_name": UNIQUE_NAME
}'`,
Python: `import requests

domain = 'YOURDOMAIN' # 替换为你的域名
session_key = 'SESSION_KEY' # 替换为你的 Session Token
unique_name = 'UNIQUE_NAME' # 替换为你的独一无二的名称
<CodeTabsBlocks.OAuth />

## 部署使用

### 手动部署

前往 <Link href='https://github.com/wozulong/fuclaude/' target='_blank' className='text-blue-500'>【Fuclaude】- Github</Link> 查看最新版本的 Fuclaude。

在仓库的 Release 页面下载最新版本的 Fuclaude,解压后即可使用。

### Docker-compose 部署

<br></br>

<CodeTabsBlocks.Compose />

export const CodeTabsBlocks = {
OAuth: () => (
<CodeTabs
codes={[
{
name: 'Curl',
lang: 'bash',
code: `curl 'http://YOURDOMAIN/manage-api/auth/oauth_token' \\
-H 'Content-Type: application/json' \\
-d '{
"session_key": SESSION_KEY,
"unique_name": UNIQUE_NAME
}'`
},
{
name: 'Python',
lang: 'python',
code: `import requests

domain = "YOURDOMAIN" # 替换为你的域名
session_key = "SESSION_KEY" # 替换为你的 Session Token
unique_name = "UNIQUE_NAME" # 替换为你的独一无二的名称

response = requests.post(
url=f"http://{domain}/manage-api/auth/oauth_token",
headers={'Content-Type': 'application/json'},
json={
'session_key': session_key,
'unique_name': unique_name
})

login_url = response.json().get('login_url')`,
Javascript: `const domain = 'YOURDOMAIN'; // 替换为你的域名
const sessionKey = 'SESSION_KEY'; // 替换为你的 Session Token
const uniqueName = 'UNIQUE_NAME'; // 替换为你的独一无二的名称
headers={"Content-Type": "application/json"},
json={"session_key": session_key, "unique_name": unique_name},
)

login_url = response.json().get("login_url")`
},
{
name: 'JavaScript',
lang: 'javascript',
code: `const domain = 'YOURDOMAIN'; // 替换为你的域名
const sessionKey = 'SESSION_KEY'; // 替换为你的 Session Token
const uniqueName = 'UNIQUE_NAME'; // 替换为你的独一无二的名称

const url = \`http://\${domain}/manage-api/auth/oauth_token\`;

const data = {
session_key: sessionKey,
unique_name: uniqueName
session_key: sessionKey,
unique_name: uniqueName
};

fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
.then((response) => response.json())
.then((data) => {
const loginUrl = data.login_url;
// 你可以在这里处理 loginUrl
console.log(loginUrl);
})
.catch(error => {
})
.catch((error) => {
console.error('Error:', error);
});`
}}
/>

## 部署使用

### 手动部署

前往 <Link href='https://github.com/wozulong/fuclaude/' target='_blank' className='text-blue-500'>【Fuclaude】- Github</Link> 查看最新版本的 Fuclaude。

在仓库的 Release 页面下载最新版本的 Fuclaude,解压后即可使用。

### Docker-compose 部署

<br></br>

<CodeTabs
codes={{
'Docker-compose': `version: '3'
});
`
}
]}
/>
),
Compose: () => (
<CodeTabs
codes={[
{
name: 'Docker-compose',
lang: 'yaml',
code: `version: '3'
services:
fuclaude:
image: pengzhile/fuclaude
ports:
- '127.0.0.1:8181:8181'
environment:
- TZ=Asia/Shanghai # 时区(不需要改动)
- FUCLAUDE_BIND=0.0.0.0:8181 # 绑定的端口号(不需要改动)(改动需要把映射也一起改动)
- FUCLAUDE_TIMEOUT=600 # 超时时间(不需要改动)
- FUCLAUDE_PROXY_URL= # 代理URL socks5://username:password@ip:port(http也一样格式)
- FUCLAUDE_REAL_LOGOUT=false # 是否执行真实的登出操作(true/false)(不需要改动)
- FUCLAUDE_SITE_PASSWORD= # 访问站点的密码(如果需要)
- FUCLAUDE_COOKIE_SECRET=1234567890abcdefghijklmnopqrstuv # Cookie加密的密钥(不需要改动)
- FUCLAUDE_OPENAI_BASE_URL=https://api.openai.com/v1 # 内容审核,使用OAI的基础URL
- FUCLAUDE_OPENAI_API_KEY=sk-xxx # 内容审核,使用OAI的密钥
- FUCLAUDE_MODERATION_ENABLED=false # 启用或禁用内容审核(true/false)
- FUCLAUDE_SIGNUP_ENABLED=false # 启用或禁用用户注册(true/false)
- FUCLAUDE_SHOW_SESSION_KEY=false # 显示或隐藏会话密钥(true/false)
restart: unless-stopped`
}}
/>
image: pengzhile/fuclaude
ports:
- '127.0.0.1:8181:8181'
environment:
- TZ=Asia/Shanghai # 时区(不需要改动)
- FUCLAUDE_BIND=0.0.0.0:8181 # 绑定的端口号(不需要改动)(改动需要把映射也一起改动)
- FUCLAUDE_TIMEOUT=600 # 超时时间(不需要改动)
- FUCLAUDE_PROXY_URL= # 代理URL socks5://username:password@ip:port(http也一样格式)
- FUCLAUDE_REAL_LOGOUT=false # 是否执行真实的登出操作(true/false)(不需要改动)
- FUCLAUDE_SITE_PASSWORD= # 访问站点的密码(如果需要)
- FUCLAUDE_COOKIE_SECRET=1234567890abcdefghijklmnopqrstuv # Cookie加密的密钥(不需要改动)
- FUCLAUDE_OPENAI_BASE_URL=https://api.openai.com/v1 # 内容审核,使用OAI的基础URL
- FUCLAUDE_OPENAI_API_KEY=sk-xxx # 内容审核,使用OAI的密钥
- FUCLAUDE_MODERATION_ENABLED=false # 启用或禁用内容审核(true/false)
- FUCLAUDE_SIGNUP_ENABLED=false # 启用或禁用用户注册(true/false)
- FUCLAUDE_SHOW_SESSION_KEY=false # 显示或隐藏会话密钥(true/false)
restart: unless-stopped`
}
]}
/>
)
};
Loading