= ({ repositories }) => {
11 | const { t } = useTranslation();
12 |
13 | if (!repositories.length) {
14 | return ;
15 | }
16 |
17 | return (
18 |
19 |
20 | {repositories.map((repository) => (
21 |
22 |
23 |
24 | ))}
25 |
26 |
27 | );
28 | };
29 |
30 | export default RepositoryList;
--------------------------------------------------------------------------------
/web/app/components/document/AntThinking.tsx:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import rehypePlugin from './rehypePlugin';
3 |
4 | const AntThinkingElement = {
5 | Component,
6 | rehypePlugin,
7 | tag: 'antThinking',
8 | };
9 |
10 | export default AntThinkingElement;
--------------------------------------------------------------------------------
/web/app/components/document/DocumentHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { Card, Typography, Breadcrumb, Tag, Divider, Space } from 'antd';
3 | import { ClockCircleOutlined } from '@ant-design/icons';
4 | import Link from 'next/link';
5 |
6 | const { Title, Text } = Typography;
7 |
8 | interface DocumentHeaderProps {
9 | document: any;
10 | lastUpdated: string;
11 | breadcrumbItems: {title: ReactNode}[];
12 | token: any;
13 | }
14 |
15 | const DocumentHeader: React.FC = ({
16 | document,
17 | lastUpdated,
18 | breadcrumbItems,
19 | token
20 | }) => {
21 | return (
22 | <>
23 |
24 |
25 |
26 |
27 | {document && (
28 |
34 |
42 | {document.title}
43 |
44 |
45 |
49 |
50 |
51 |
52 | )}
53 | >
54 | );
55 | };
56 |
57 | export default DocumentHeader;
--------------------------------------------------------------------------------
/web/app/components/document/MarkdownElements.ts:
--------------------------------------------------------------------------------
1 | import AntThinkingElement from './AntThinking';
2 |
3 | export const markdownElements = [ AntThinkingElement];
--------------------------------------------------------------------------------
/web/app/components/document/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DocumentHeader } from './DocumentHeader';
2 | export { default as DocumentContent } from './DocumentContent';
3 | export { default as DocumentSidebar } from './DocumentSidebar';
4 | export { default as MobileDocumentDrawer } from './MobileDocumentDrawer';
5 | export { default as LoadingErrorState } from './LoadingErrorState';
6 | export { default as DocumentStyles } from './DocumentStyles';
7 | export { default as ChangelogContent } from './ChangelogContent';
8 | export { default as SourceFiles } from './SourceFiles';
9 |
10 | // 导出服务器组件
11 | export * from './ServerComponents';
12 |
13 | // 工具函数导出
14 | export * from './utils/headingUtils';
15 | export * from './utils/mermaidUtils';
--------------------------------------------------------------------------------
/web/app/components/document/rehypePlugin.ts:
--------------------------------------------------------------------------------
1 | import type { Node } from 'unist';
2 | import { visit } from 'unist-util-visit';
3 |
4 | // eslint-disable-next-line unicorn/consistent-function-scoping
5 | const rehypePlugin = () => (tree: Node) => {
6 | visit(tree, 'element', (node: any, index, parent) => {
7 | if (node.type === 'element' && node.tagName === 'p') {
8 | console.log(node);
9 | const children = node.children || [];
10 | const openTagIndex = children.findIndex(
11 | (child: any) => child.type === 'raw' && child.value === '',
12 | );
13 | const closeTagIndex = children.findIndex(
14 | (child: any) => child.type === 'raw' && child.value === '',
15 | );
16 |
17 | if (openTagIndex !== -1 && closeTagIndex !== -1 && closeTagIndex > openTagIndex) {
18 | const content = children.slice(openTagIndex + 1, closeTagIndex);
19 | const antThinkingNode = {
20 | children: content,
21 | properties: {},
22 | tagName: 'antThinking',
23 | type: 'element',
24 | };
25 |
26 | // Replace the entire paragraph with our new antThinking node
27 | parent.children.splice(index, 1, antThinkingNode);
28 | return index; // Skip processing the newly inserted node
29 | }
30 | }
31 | });
32 | };
33 |
34 | export default rehypePlugin;
--------------------------------------------------------------------------------
/web/app/components/document/thinking/remarkPlugin.ts:
--------------------------------------------------------------------------------
1 | import { toMarkdown } from 'mdast-util-to-markdown';
2 | import { SKIP, visit } from 'unist-util-visit';
3 |
4 | // 预处理函数:确保 think 标签前后有两个换行符
5 | export const normalizeThinkTags = (markdown: string) => {
6 | // 删除```mermaid里面的[]里面的(),保留[]
7 | return markdown
8 | // 处理mermaid代码块中的[]()格式
9 | // 确保 标签前后有两个换行符
10 | .replaceAll(/([^\n])\s*/g, '$1\n\n')
11 | .replaceAll(/\s*([^\n])/g, '\n\n$1')
12 | // 确保 标签前后有两个换行符
13 | .replaceAll(/([^\n])\s*<\/think>/g, '$1\n\n')
14 | .replaceAll(/<\/think>\s*([^\n])/g, '\n\n$1')
15 | // 处理可能产生的多余换行符
16 | .replaceAll(/\n{3,}/g, '\n\n')
17 | };
18 |
19 | export const remarkCaptureThink = () => {
20 | return (tree: any) => {
21 | visit(tree, 'html', (node, index, parent) => {
22 | if (node.value === '') {
23 | const startIndex = index as number;
24 | let endIndex = startIndex + 1;
25 | let hasCloseTag = false;
26 |
27 | // 查找闭合标签
28 | while (endIndex < parent.children.length) {
29 | const sibling = parent.children[endIndex];
30 | if (sibling.type === 'html' && sibling.value === '') {
31 | hasCloseTag = true;
32 | break;
33 | }
34 | endIndex++;
35 | }
36 |
37 | // 计算需要删除的节点范围
38 | const deleteCount = hasCloseTag
39 | ? endIndex - startIndex + 1
40 | : parent.children.length - startIndex;
41 |
42 | // 提取内容节点
43 | const contentNodes = parent.children.slice(
44 | startIndex + 1,
45 | hasCloseTag ? endIndex : undefined,
46 | );
47 |
48 | // 转换为 Markdown 字符串
49 | const content = contentNodes
50 | .map((n: any) => toMarkdown(n))
51 | .join('\n\n')
52 | .trim();
53 |
54 | // 创建自定义节点
55 | const thinkNode = {
56 | data: {
57 | hChildren: [{ type: 'text', value: content }],
58 | hName: 'think',
59 | },
60 | position: node.position,
61 | type: 'thinkBlock',
62 | };
63 |
64 | // 替换原始节点
65 | parent.children.splice(startIndex, deleteCount, thinkNode);
66 |
67 | // 跳过已处理的节点
68 | return [SKIP, startIndex + 1];
69 | }
70 | });
71 | };
72 | };
--------------------------------------------------------------------------------
/web/app/components/document/utils/headingUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 从Markdown文本中提取标题信息
3 | * @param markdown Markdown文本内容
4 | * @returns 提取的标题数组
5 | */
6 | export const extractHeadings = (markdown: string): {key: string, title: string, level: number, id: string}[] => {
7 | const headingRegex = /^(#{1,6})\s+(.+)$/gm;
8 | const matches = Array.from(markdown.matchAll(headingRegex));
9 |
10 | return matches.map((match, index) => {
11 | const level = match[1].length;
12 | const title = match[2];
13 | const key = `heading-${index}`;
14 | // 生成ID,用于锚点定位
15 | const id = title.toLowerCase().replace(/\s+/g, '-').replace(/[^\w\u4e00-\u9fa5-]/g, '');
16 |
17 | return { key, title, level, id };
18 | });
19 | };
20 |
21 | /**
22 | * 将平面标题列表转换为嵌套的锚点项目结构
23 | * @param headings 标题数组
24 | * @returns 组织后的锚点项目数组
25 | */
26 | export const createAnchorItems = (headings: {key: string, title: string, level: number, id: string}[]) => {
27 | if (!headings.length) return [];
28 |
29 | // 创建嵌套结构的目录
30 | const result: any[] = [];
31 | const levels: any[] = [{ children: result }];
32 |
33 | headings.forEach(heading => {
34 | const item = {
35 | key: heading.key,
36 | href: `#${heading.id}`,
37 | title: heading.title,
38 | children: [],
39 | };
40 |
41 | // 查找适当的父级
42 | while (levels.length > 1 && levels[levels.length - 1].level >= heading.level) {
43 | levels.pop();
44 | }
45 |
46 | // 将当前项添加到父级
47 | levels[levels.length - 1].children.push(item);
48 |
49 | // 将当前项添加到级别堆栈
50 | if (heading.level < 4) { // 只对 h1-h3 建立嵌套结构
51 | levels.push({ level: heading.level, children: item.children });
52 | }
53 | });
54 |
55 | return result;
56 | };
--------------------------------------------------------------------------------
/web/app/components/document/utils/mermaidUtils.ts:
--------------------------------------------------------------------------------
1 | import mermaid from 'mermaid';
2 |
3 | /**
4 | * 初始化Mermaid图表配置
5 | * @param isDarkMode 是否为暗黑模式
6 | */
7 | export const initializeMermaid = (isDarkMode: boolean) => {
8 | mermaid.initialize({
9 | startOnLoad: false, // 手动控制渲染
10 | theme: isDarkMode ? 'dark' : 'default',
11 | securityLevel: 'loose',
12 | flowchart: {
13 | htmlLabels: true,
14 | curve: 'basis',
15 | useMaxWidth: true
16 | },
17 | sequence: {
18 | showSequenceNumbers: true,
19 | actorMargin: 80,
20 | useMaxWidth: true
21 | },
22 | gantt: {
23 | axisFormat: '%Y-%m-%d',
24 | titleTopMargin: 25,
25 | barHeight: 20,
26 | barGap: 4
27 | },
28 | er: {
29 | useMaxWidth: true
30 | },
31 | journey: {
32 | useMaxWidth: true
33 | },
34 | pie: {
35 | useMaxWidth: true
36 | }
37 | });
38 | };
39 |
40 | /**
41 | * 渲染页面上的所有Mermaid图表
42 | * @returns 一个Promise,表示渲染操作的完成
43 | */
44 | export const renderMermaidDiagrams = async (): Promise => {
45 | try {
46 | // 给渲染一些时间以确保DOM已更新
47 | await new Promise(resolve => setTimeout(resolve, 300));
48 |
49 | // 查找所有mermaid图表并渲染
50 | const diagrams = document.querySelectorAll('.mermaid');
51 | if (diagrams.length > 0) {
52 | await mermaid.run({
53 | querySelector: '.mermaid',
54 | });
55 | return true;
56 | }
57 | return false;
58 | } catch (error) {
59 | console.error('渲染图表时出错:', error);
60 | return false;
61 | }
62 | };
--------------------------------------------------------------------------------
/web/app/const/urlconst.ts:
--------------------------------------------------------------------------------
1 | import pkg from '../../package.json'
2 |
3 | const homepage = pkg.homepage;
4 |
5 | export {
6 | homepage
7 | }
--------------------------------------------------------------------------------
/web/app/i18n/client.ts:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect } from 'react';
4 | import i18next from 'i18next';
5 | import { initReactI18next, useTranslation as useTranslationOrg } from 'react-i18next';
6 | import resourcesToBackend from 'i18next-resources-to-backend';
7 | import LanguageDetector from 'i18next-browser-languagedetector';
8 | import { languages, getOptions } from './settings';
9 |
10 | // 确保i18next只初始化一次
11 | const i18nextInstance = i18next
12 | .use(initReactI18next)
13 | .use(LanguageDetector)
14 | .use(resourcesToBackend((language: string, namespace: string) =>
15 | import(`../../public/locales/${language}/${namespace}.json`)));
16 |
17 | // 检查i18next是否已经初始化
18 | if (!i18next.isInitialized) {
19 | // @ts-ignore - i18next类型定义与实际使用有差异
20 | i18nextInstance.init({
21 | ...getOptions(),
22 | detection: {
23 | order: ['querystring', 'cookie', 'navigator'],
24 | lookupQuerystring: 'locale',
25 | caches: ['cookie'],
26 | cookieExpirationDate: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year
27 | },
28 | react: {
29 | useSuspense: false, // 禁用Suspense以避免hydration问题
30 | }
31 | });
32 | }
33 |
34 | export function useTranslation(ns: string | string[] = 'common', options = {}) {
35 | const ret = useTranslationOrg(ns, options);
36 | const { i18n } = ret;
37 |
38 | useEffect(() => {
39 | languages.forEach((lng) => {
40 | const namespaces = Array.isArray(ns) ? ns : [ns];
41 | namespaces.forEach((namespace) => {
42 | i18n.loadNamespaces(namespace);
43 | });
44 | });
45 | }, [i18n, ns]);
46 |
47 | return ret;
48 | }
--------------------------------------------------------------------------------
/web/app/i18n/server.ts:
--------------------------------------------------------------------------------
1 | import { createInstance } from 'i18next';
2 | import resourcesToBackend from 'i18next-resources-to-backend';
3 | import { initReactI18next } from 'react-i18next/initReactI18next';
4 | import { getOptions } from './settings';
5 |
6 | const initI18next = async (lng: string, ns: string) => {
7 | const i18nInstance = createInstance();
8 | await i18nInstance
9 | .use(initReactI18next)
10 | .use(resourcesToBackend((language: string, namespace: string) =>
11 | import(`../../public/locales/${language}/${namespace}.json`)))
12 | .init(getOptions(lng, ns));
13 |
14 | return i18nInstance;
15 | };
16 |
17 | export async function getTranslation(lng: string, ns: string, options: any = {}) {
18 | const i18nextInstance = await initI18next(lng, ns);
19 | return {
20 | t: i18nextInstance.getFixedT(lng, ns, options.keyPrefix),
21 | i18n: i18nextInstance
22 | };
23 | }
--------------------------------------------------------------------------------
/web/app/i18n/settings.ts:
--------------------------------------------------------------------------------
1 | export const fallbackLng = 'zh-CN';
2 | export const defaultNS = 'common';
3 |
4 | export const languages = [
5 | 'zh-CN', // 中文(简体)
6 | 'en-US', // 英文(美国)
7 | 'zh-TW', // 中文(繁体)
8 | 'ja', // 日语
9 | 'ko', // 韩语
10 | 'de', // 德语
11 | 'fr', // 法语
12 | 'es', // 西班牙语
13 | 'it', // 意大利语
14 | 'pt', // 葡萄牙语
15 | 'ru', // 俄语
16 | 'ar', // 阿拉伯语
17 | 'hi', // 印地语
18 | 'nl', // 荷兰语
19 | 'tr', // 土耳其语
20 | 'vi', // 越南语
21 | 'id', // 印尼语
22 | 'th' // 泰语
23 | ];
24 |
25 | export function getOptions(lng = fallbackLng, ns = defaultNS) {
26 | return {
27 | supportedLngs: languages,
28 | fallbackLng,
29 | lng,
30 | fallbackNS: defaultNS,
31 | defaultNS,
32 | ns
33 | };
34 | }
--------------------------------------------------------------------------------
/web/app/login/auth.module.css:
--------------------------------------------------------------------------------
1 | .authContainer {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | min-height: 100vh;
6 | background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
7 | padding: 20px;
8 | }
9 |
10 | .authWrapper {
11 | width: 100%;
12 | max-width: 420px;
13 | animation: fadeIn 0.5s ease-out;
14 | }
15 |
16 | .authCard {
17 | border-radius: 12px;
18 | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
19 | overflow: hidden;
20 | }
21 |
22 | .authHeader {
23 | text-align: center;
24 | margin-bottom: 24px;
25 | }
26 |
27 | .authTitle {
28 | font-size: 24px;
29 | margin-bottom: 8px;
30 | font-weight: 600;
31 | color: #1677ff;
32 | }
33 |
34 | .authSubtitle {
35 | color: #8c8c8c;
36 | font-size: 14px;
37 | }
38 |
39 | .authForm {
40 | margin-top: 16px;
41 | }
42 |
43 | .rememberForgot {
44 | display: flex;
45 | justify-content: space-between;
46 | align-items: center;
47 | }
48 |
49 | .forgotLink {
50 | color: #1677ff;
51 | font-size: 14px;
52 | }
53 |
54 | .loginButton {
55 | height: 44px;
56 | font-size: 16px;
57 | border-radius: 6px;
58 | }
59 |
60 | .registerLink {
61 | text-align: center;
62 | margin: 16px 0;
63 | color: #8c8c8c;
64 | }
65 |
66 | .socialLogin {
67 | display: flex;
68 | justify-content: center;
69 | gap: 16px;
70 | margin-top: 16px;
71 | }
72 |
73 | .socialButton {
74 | border-radius: 6px;
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | width: 120px;
79 | }
80 |
81 | .socialButton:hover {
82 | background-color: #f0f0f0;
83 | border-color: #d9d9d9;
84 | }
85 |
86 | .siteFormItemIcon {
87 | color: #bfbfbf;
88 | }
89 |
90 | /* 注册页面特定样式 */
91 | .agreement {
92 | margin-bottom: 24px;
93 | }
94 |
95 | .agreementText {
96 | color: #8c8c8c;
97 | font-size: 14px;
98 | }
99 |
100 | /* 动画效果 */
101 | @keyframes fadeIn {
102 | from {
103 | opacity: 0;
104 | transform: translateY(20px);
105 | }
106 | to {
107 | opacity: 1;
108 | transform: translateY(0);
109 | }
110 | }
--------------------------------------------------------------------------------
/web/app/page.module.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIDotNet/OpenDeepWiki/ee597a3390e5b84bbd82b9066ac68aae6104cbbb/web/app/page.module.css
--------------------------------------------------------------------------------
/web/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { getWarehouse } from './services/warehouseService';
2 | import { getBasicHomeStats } from './services/statsService';
3 | import HomeClient from './components/HomeClient';
4 | import { Suspense } from 'react';
5 | import { Spin } from 'antd';
6 |
7 | export default async function Home({ searchParams = {} }: any) {
8 | // 确保 searchParams 已经被解析
9 | const resolvedSearchParams = await searchParams;
10 |
11 | // 从 URL 参数中获取分页信息
12 | const page = Number(resolvedSearchParams?.page) || 1;
13 | const pageSize = Number(resolvedSearchParams?.pageSize) || 20;
14 | const keyword = resolvedSearchParams?.keyword || '';
15 |
16 | // 并行获取初始数据和统计数据
17 | const [response, statsData] = await Promise.all([
18 | getWarehouse(page, pageSize, keyword),
19 | getBasicHomeStats()
20 | ]);
21 |
22 | const initialRepositories = response.success ? response.data.items : [];
23 | const initialTotal = response.success ? response.data.total : 0;
24 |
25 | return (
26 | }>
27 |
35 |
36 | );
37 | }
--------------------------------------------------------------------------------
/web/app/services/chatShareMessageServce.ts:
--------------------------------------------------------------------------------
1 | import { API_URL, fetchApi } from './api';
2 |
3 | interface ChatShareMessageInput {
4 | isDeep: boolean;
5 | owner: string;
6 | name: string;
7 | message: string;
8 | branch?: string;
9 | }
10 | /**
11 | * Submit a new repository to the warehouse
12 | * 这个函数仍然需要在客户端使用
13 | */
14 | export async function createChatShareMessage(
15 | data: ChatShareMessageInput
16 | ): Promise {
17 | return await fetchApi(API_URL + '/api/ChatShareMessage', {
18 | method: 'POST',
19 | body: JSON.stringify(data),
20 | });
21 | }
22 |
23 | /**
24 | *
25 | * @param chatShareMessageId
26 | * @param page
27 | * @param pageSize
28 | * @returns
29 | */
30 | export async function getChatShareMessageList(chatShareMessageId:string,page:number,pageSize:number){
31 | return await fetchApi(API_URL +`/api/ChatShareMessage/List?chatShareMessageId=${chatShareMessageId}&page=${page}&pageSize=${pageSize}`)
32 | }
--------------------------------------------------------------------------------
/web/app/services/fetchApi.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * API响应接口
3 | */
4 | export interface ApiResponse {
5 | code: number;
6 | message?: string;
7 | data: T;
8 | }
9 |
10 | /**
11 | * 封装的fetch函数,用于API请求
12 | * @param url 请求地址
13 | * @param options 请求选项
14 | * @returns API响应
15 | */
16 | export async function fetchApi(
17 | url: string,
18 | options: RequestInit = {}
19 | ): Promise> {
20 | // 设置默认请求头
21 | const headers = {
22 | 'Content-Type': 'application/json',
23 | ...options.headers,
24 | };
25 |
26 | // 获取token(如果存在)
27 | const token = typeof window !== 'undefined' ? localStorage.getItem('userToken') : null;
28 | if (token) {
29 | headers['Authorization'] = `Bearer ${token}`;
30 | }
31 | try {
32 | // 创建 AbortController 用于超时控制
33 | const controller = new AbortController();
34 | const timeoutId = setTimeout(() => controller.abort(), 5 * 60 * 1000); // 5分钟超时
35 |
36 | const response = await fetch(url, {
37 | ...options,
38 | headers,
39 | signal: controller.signal,
40 | });
41 |
42 | clearTimeout(timeoutId); // 清除超时定时器
43 |
44 | // 如果响应不成功,抛出错误
45 | if (!response.ok) {
46 | if (response.status === 401) {
47 | // 未授权,清除token并重定向到登录页
48 | if (typeof window !== 'undefined') {
49 | localStorage.removeItem('userToken');
50 | localStorage.removeItem('refreshToken');
51 | localStorage.removeItem('userInfo');
52 | // 保存当前路径用于登录后重定向
53 | localStorage.setItem('redirectPath', window.location.pathname);
54 | window.location.href = '/login';
55 | }
56 | }
57 |
58 | const errorData = await response.json();
59 | throw new Error(errorData.message || '请求失败');
60 | }
61 |
62 | // 解析响应数据
63 | const data = await response.json();
64 | return data;
65 | } catch (error) {
66 | console.error('API请求错误:', error);
67 | return {
68 | code: 500,
69 | message: error instanceof Error ? error.message : '未知错误',
70 | data: null as unknown as T,
71 | };
72 | }
73 | }
--------------------------------------------------------------------------------
/web/app/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api';
2 | export * from './warehouseService';
3 | export * from './githubService';
--------------------------------------------------------------------------------
/web/app/services/openaiService.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Fetches available models from OpenAI API
3 | */
4 | export async function fetchOpenAIModels(endpoint: string, apiKey: string): Promise {
5 | try {
6 | const modelsEndpoint = `${endpoint.replace(/\/+$/, '')}/models`;
7 |
8 | const response = await fetch(modelsEndpoint, {
9 | method: 'GET',
10 | headers: {
11 | 'Content-Type': 'application/json',
12 | 'Authorization': `Bearer ${apiKey}`
13 | }
14 | });
15 |
16 | if (!response.ok) {
17 | const error = await response.json().catch(() => ({}));
18 | throw new Error(error.error?.message || `Failed to fetch models: ${response.status}`);
19 | }
20 |
21 | const data = await response.json();
22 | // Extract model IDs from the OpenAI API response
23 | return data.data.map((model: any) => model.id);
24 | } catch (error) {
25 | console.error('Error fetching OpenAI models:', error);
26 | throw error;
27 | }
28 | }
29 |
30 | /**
31 | * Creates a chat completion using OpenAI API
32 | */
33 | export async function createChatCompletion(
34 | endpoint: string,
35 | apiKey: string,
36 | model: string,
37 | messages: Array<{ role: string; content: string }>
38 | ) {
39 | try {
40 | const completionsEndpoint = `${endpoint.replace(/\/+$/, '')}/chat/completions`;
41 |
42 | const response = await fetch(completionsEndpoint, {
43 | method: 'POST',
44 | headers: {
45 | 'Content-Type': 'application/json',
46 | 'Authorization': `Bearer ${apiKey}`
47 | },
48 | body: JSON.stringify({
49 | model,
50 | messages,
51 | stream: false
52 | })
53 | });
54 |
55 | if (!response.ok) {
56 | const error = await response.json().catch(() => ({}));
57 | throw new Error(error.error?.message || `Failed to create completion: ${response.status}`);
58 | }
59 |
60 | return await response.json();
61 | } catch (error) {
62 | console.error('Error creating chat completion:', error);
63 | throw error;
64 | }
65 | }
--------------------------------------------------------------------------------
/web/app/sitemap.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from 'next';
2 | import { getWarehouse } from './services/warehouseService';
3 |
4 | // 生成动态站点地图
5 | export default async function sitemap(): Promise {
6 | try {
7 | // 获取所有仓库,每页50个
8 | const response = await getWarehouse(1, 50);
9 | const repos = response.data?.items || [];
10 |
11 | // 创建仓库主页URL,从地址中提取owner和name
12 | const repoUrls = repos.map((repo) => {
13 | // 例如:从 'https://github.com/owner/name' 提取 owner/name
14 | const addressParts = repo.address.split('/');
15 | const repoOwner = addressParts[addressParts.length - 2] || 'unknown';
16 | const repoName = addressParts[addressParts.length - 1] || repo.name;
17 |
18 | return {
19 | url: `https://koala.wiki/${repoOwner}/${repoName}`,
20 | lastModified: new Date(repo.updatedAt || Date.now()),
21 | changeFrequency: 'daily' as const,
22 | priority: 0.8,
23 | };
24 | });
25 |
26 | // 添加静态页面
27 | const staticUrls = [
28 | {
29 | url: 'https://koala.wiki',
30 | lastModified: new Date(),
31 | changeFrequency: 'daily' as const,
32 | priority: 1.0,
33 | },
34 | {
35 | url: 'https://koala.wiki/search',
36 | lastModified: new Date(),
37 | changeFrequency: 'weekly' as const,
38 | priority: 0.5,
39 | },
40 | ];
41 |
42 | return [...staticUrls, ...repoUrls];
43 | } catch (error) {
44 | console.error('生成站点地图时出错:', error);
45 |
46 | // 发生错误时返回基本站点地图
47 | return [
48 | {
49 | url: 'https://koala.wiki',
50 | lastModified: new Date(),
51 | changeFrequency: 'daily' as const,
52 | priority: 1.0,
53 | },
54 | ];
55 | }
56 | }
--------------------------------------------------------------------------------
/web/app/types.ts:
--------------------------------------------------------------------------------
1 | export interface Repository {
2 | id: string | null;
3 | name: string;
4 | description: string;
5 | address: string;
6 | type: string;
7 | branch: string;
8 | /**
9 | * Repository status:
10 | * 0 = Pending (待处理)
11 | * 1 = Processing (处理中)
12 | * 2 = Completed (已完成)
13 | * 3 = Canceled (已取消)
14 | * 4 = Unauthorized (未授权)
15 | * 99 = Failed (已失败)
16 | */
17 | status: number;
18 | prompt: string;
19 | version: string;
20 | isRecommended: boolean;
21 | createdAt: string;
22 | updatedAt?: string;
23 | error?:string;
24 | organizationName: string;
25 | success: boolean;
26 | stars: number;
27 | forks: number;
28 | avatarUrl: string;
29 | ownerUrl: string;
30 | repoUrl: string;
31 | language?: string;
32 | license?: string;
33 | }
34 |
35 | export interface RepositoryFormValues {
36 | address: string;
37 | type: string;
38 | branch: string;
39 | prompt: string;
40 | }
--------------------------------------------------------------------------------
/web/app/types/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 仓库表单值接口定义
3 | */
4 | export interface RepositoryFormValues {
5 | address?: string; // 仓库地址 (Git仓库)
6 | type?: string; // 仓库类型
7 | branch?: string; // 仓库分支
8 | enableGitAuth?: boolean; // 是否启用Git认证
9 | gitUserName?: string; // Git用户名 (私有仓库)
10 | gitPassword?: string; // Git密码/令牌 (私有仓库)
11 | submitType?: 'git' | 'upload'; // 提交方式:Git仓库或上传压缩包
12 | organization?: string; // 组织名称 (上传压缩包时)
13 | repositoryName?: string; // 仓库名称 (上传压缩包时)
14 | }
15 |
16 | /**
17 | * 仓库信息接口定义
18 | */
19 | export interface Repository {
20 | id: string;
21 | owner?: string; // 仓库所有者/组织(兼容旧版)
22 | organizationName: string; // 仓库所有者/组织
23 | name: string; // 仓库名称
24 | address?: string; // 仓库地址
25 | type?: string; // 仓库类型,如 git
26 | branch?: string; // 分支名
27 | status: string | number; // 仓库状态
28 | description?: string; // 描述
29 | createdAt: string; // 创建时间
30 | updatedAt?: string; // 更新时间
31 | isEmbedded?: boolean; // 是否为嵌入式仓库
32 | isRecommended?: boolean; // 是否推荐
33 | error?: string; // 错误信息
34 | prompt?: string; // 提示信息
35 | version?: string; // 版本
36 | }
--------------------------------------------------------------------------------
/web/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/web/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | output: 'standalone',
4 | reactStrictMode: true,
5 | transpilePackages: ['antd','@ant-design/icons'],
6 | async rewrites() {
7 | // 使用占位符,在运行时会被替换
8 | const apiUrl = 'http://__API_URL_PLACEHOLDER__';
9 | return [
10 | {
11 | source: '/api/:path*',
12 | destination: `${apiUrl}/api/:path*`,
13 | },
14 | ];
15 | },
16 | };
17 |
18 | module.exports = nextConfig;
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "https://github.com/AIDotNet/OpenDeepWiki",
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start",
7 | "lint": "next lint"
8 | },
9 | "dependencies": {
10 | "@ant-design/cssinjs": "^1.23.0",
11 | "@ant-design/nextjs-registry": "^1.0.2",
12 | "@ant-design/v5-patch-for-react-19": "^1.0.3",
13 | "@lobehub/ui": "^2.0.12",
14 | "@types/react-syntax-highlighter": "^15.5.13",
15 | "@uiw/react-md-editor": "^4.0.6",
16 | "accept-language": "^3.0.20",
17 | "antd": "^5.24.8",
18 | "framer-motion": "^11.18.2",
19 | "i18next": "^25.2.0",
20 | "i18next-browser-languagedetector": "^8.1.0",
21 | "i18next-resources-to-backend": "^1.2.1",
22 | "lucide-react": "^0.511.0",
23 | "marked": "^15.0.11",
24 | "md-editor-rt": "^5.6.0",
25 | "mermaid": "^11.6.0",
26 | "next": "^15.3.1",
27 | "next-i18next": "^15.4.2",
28 | "react": "^19.1.0",
29 | "react-cookie": "^8.0.1",
30 | "react-dom": "^19.1.0",
31 | "react-i18next": "^15.5.1",
32 | "react-markdown": "^9.1.0",
33 | "react-syntax-highlighter": "^15.6.1",
34 | "rehype-highlight": "^7.0.2",
35 | "rehype-katex": "^7.0.1",
36 | "rehype-raw": "^7.0.0",
37 | "rehype-slug": "^6.0.0",
38 | "remark-gfm": "^4.0.1",
39 | "remark-html": "^16.0.1",
40 | "remark-math": "^6.0.0",
41 | "remark-toc": "^9.0.0"
42 | },
43 | "devDependencies": {
44 | "@tailwindcss/postcss": "^4.1.7",
45 | "@types/node": "22.15.2",
46 | "@types/react": "19.1.2",
47 | "autoprefixer": "^10.4.21",
48 | "postcss": "^8.5.3",
49 | "tailwindcss": "^4.1.7",
50 | "typescript": "5.8.3"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | '@tailwindcss/postcss': {},
4 | 'autoprefixer': {},
5 | }
6 | }
--------------------------------------------------------------------------------
/web/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIDotNet/OpenDeepWiki/ee597a3390e5b84bbd82b9066ac68aae6104cbbb/web/public/favicon.ico
--------------------------------------------------------------------------------
/web/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIDotNet/OpenDeepWiki/ee597a3390e5b84bbd82b9066ac68aae6104cbbb/web/public/favicon.png
--------------------------------------------------------------------------------
/web/public/locales/ar/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - قاعدة معرفة الشفرة المدعومة بالذكاء الاصطناعي",
3 | "changelog": {
4 | "title": "سجل التغييرات",
5 | "github_message": "عرض تاريخ الالتزامات الكامل على GitHub",
6 | "commit": "التزام",
7 | "types": {
8 | "feature": "ميزة",
9 | "fix": "إصلاح",
10 | "docs": "وثائق",
11 | "refactor": "إعادة هيكلة",
12 | "chore": "صيانة",
13 | "style": "نمط",
14 | "perf": "أداء",
15 | "test": "اختبار",
16 | "build": "بناء",
17 | "ci": "CI/CD",
18 | "revert": "إرجاع"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "الصينية (المبسطة)",
23 | "en-US": "الإنجليزية (الولايات المتحدة)",
24 | "zh-TW": "الصينية (التقليدية)",
25 | "ja": "اليابانية",
26 | "ko": "الكورية",
27 | "de": "الألمانية",
28 | "fr": "الفرنسية",
29 | "es": "الإسبانية",
30 | "it": "الإيطالية",
31 | "pt": "البرتغالية",
32 | "ru": "الروسية",
33 | "ar": "العربية",
34 | "hi": "الهندية",
35 | "nl": "الهولندية",
36 | "tr": "التركية",
37 | "vi": "الفيتنامية",
38 | "id": "الإندونيسية",
39 | "th": "التايلاندية",
40 | "asia": "آسيا",
41 | "europe": "أوروبا والأمريكتين",
42 | "middle_east": "الشرق الأوسط"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/es/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - Base de conocimiento de código impulsada por IA",
3 | "changelog": {
4 | "title": "Registro de cambios",
5 | "github_message": "Ver historial completo de commits en GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Característica",
9 | "fix": "Corrección",
10 | "docs": "Documentación",
11 | "refactor": "Refactorización",
12 | "chore": "Mantenimiento",
13 | "style": "Estilo",
14 | "perf": "Rendimiento",
15 | "test": "Prueba",
16 | "build": "Construcción",
17 | "ci": "CI/CD",
18 | "revert": "Reversión"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Chino (simplificado)",
23 | "en-US": "Inglés (EE.UU.)",
24 | "zh-TW": "Chino (tradicional)",
25 | "ja": "Japonés",
26 | "ko": "Coreano",
27 | "de": "Alemán",
28 | "fr": "Francés",
29 | "es": "Español",
30 | "it": "Italiano",
31 | "pt": "Portugués",
32 | "ru": "Ruso",
33 | "ar": "Árabe",
34 | "hi": "Hindi",
35 | "nl": "Neerlandés",
36 | "tr": "Turco",
37 | "vi": "Vietnamita",
38 | "id": "Indonesio",
39 | "th": "Tailandés",
40 | "asia": "Asia",
41 | "europe": "Europa y América",
42 | "middle_east": "Oriente Medio"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/fr/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - Base de connaissances de code alimentée par l'IA",
3 | "changelog": {
4 | "title": "Journal des modifications",
5 | "github_message": "Voir l'historique complet des commits sur GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Fonctionnalité",
9 | "fix": "Correction",
10 | "docs": "Documentation",
11 | "refactor": "Refactorisation",
12 | "chore": "Maintenance",
13 | "style": "Style",
14 | "perf": "Performance",
15 | "test": "Test",
16 | "build": "Build",
17 | "ci": "CI/CD",
18 | "revert": "Annulation"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Chinois (simplifié)",
23 | "en-US": "Anglais (États-Unis)",
24 | "zh-TW": "Chinois (traditionnel)",
25 | "ja": "Japonais",
26 | "ko": "Coréen",
27 | "de": "Allemand",
28 | "fr": "Français",
29 | "es": "Espagnol",
30 | "it": "Italien",
31 | "pt": "Portugais",
32 | "ru": "Russe",
33 | "ar": "Arabe",
34 | "hi": "Hindi",
35 | "nl": "Néerlandais",
36 | "tr": "Turc",
37 | "vi": "Vietnamien",
38 | "id": "Indonésien",
39 | "th": "Thaï",
40 | "asia": "Asie",
41 | "europe": "Europe & Amériques",
42 | "middle_east": "Moyen-Orient"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/id/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - Basis Pengetahuan Kode Bertenaga AI",
3 | "changelog": {
4 | "title": "Log Perubahan",
5 | "github_message": "Lihat riwayat commit lengkap di GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Fitur",
9 | "fix": "Perbaikan",
10 | "docs": "Dokumentasi",
11 | "refactor": "Refaktor",
12 | "chore": "Pemeliharaan",
13 | "style": "Gaya",
14 | "perf": "Kinerja",
15 | "test": "Tes",
16 | "build": "Build",
17 | "ci": "CI/CD",
18 | "revert": "Kembalikan"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Mandarin (Sederhana)",
23 | "en-US": "Inggris (AS)",
24 | "zh-TW": "Mandarin (Tradisional)",
25 | "ja": "Jepang",
26 | "ko": "Korea",
27 | "de": "Jerman",
28 | "fr": "Prancis",
29 | "es": "Spanyol",
30 | "it": "Italia",
31 | "pt": "Portugis",
32 | "ru": "Rusia",
33 | "ar": "Arab",
34 | "hi": "Hindi",
35 | "nl": "Belanda",
36 | "tr": "Turki",
37 | "vi": "Vietnam",
38 | "id": "Indonesia",
39 | "th": "Thailand",
40 | "asia": "Asia",
41 | "europe": "Eropa & Amerika",
42 | "middle_east": "Timur Tengah"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/it/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - Base di conoscenza del codice basata su IA",
3 | "changelog": {
4 | "title": "Registro delle modifiche",
5 | "github_message": "Visualizza la cronologia completa dei commit su GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Funzionalità",
9 | "fix": "Correzione",
10 | "docs": "Documentazione",
11 | "refactor": "Refactoring",
12 | "chore": "Manutenzione",
13 | "style": "Stile",
14 | "perf": "Prestazioni",
15 | "test": "Test",
16 | "build": "Build",
17 | "ci": "CI/CD",
18 | "revert": "Ripristino"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Cinese (semplificato)",
23 | "en-US": "Inglese (Stati Uniti)",
24 | "zh-TW": "Cinese (tradizionale)",
25 | "ja": "Giapponese",
26 | "ko": "Coreano",
27 | "de": "Tedesco",
28 | "fr": "Francese",
29 | "es": "Spagnolo",
30 | "it": "Italiano",
31 | "pt": "Portoghese",
32 | "ru": "Russo",
33 | "ar": "Arabo",
34 | "hi": "Hindi",
35 | "nl": "Olandese",
36 | "tr": "Turco",
37 | "vi": "Vietnamita",
38 | "id": "Indonesiano",
39 | "th": "Tailandese",
40 | "asia": "Asia",
41 | "europe": "Europa e Americhe",
42 | "middle_east": "Medio Oriente"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/nl/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - AI-gestuurde codeconnaissancebase",
3 | "changelog": {
4 | "title": "Wijzigingslogboek",
5 | "github_message": "Bekijk volledige commit geschiedenis op GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Functie",
9 | "fix": "Reparatie",
10 | "docs": "Documentatie",
11 | "refactor": "Refactoring",
12 | "chore": "Onderhoud",
13 | "style": "Stijl",
14 | "perf": "Prestaties",
15 | "test": "Test",
16 | "build": "Build",
17 | "ci": "CI/CD",
18 | "revert": "Terugdraaien"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Chinees (vereenvoudigd)",
23 | "en-US": "Engels (VS)",
24 | "zh-TW": "Chinees (traditioneel)",
25 | "ja": "Japans",
26 | "ko": "Koreaans",
27 | "de": "Duits",
28 | "fr": "Frans",
29 | "es": "Spaans",
30 | "it": "Italiaans",
31 | "pt": "Portugees",
32 | "ru": "Russisch",
33 | "ar": "Arabisch",
34 | "hi": "Hindi",
35 | "nl": "Nederlands",
36 | "tr": "Turks",
37 | "vi": "Vietnamees",
38 | "id": "Indonesisch",
39 | "th": "Thais",
40 | "asia": "Azië",
41 | "europe": "Europa & Amerika",
42 | "middle_east": "Midden-Oosten"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/ru/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - База знаний кода на основе ИИ",
3 | "changelog": {
4 | "title": "Журнал изменений",
5 | "github_message": "Просмотреть полную историю коммитов на GitHub",
6 | "commit": "Коммит",
7 | "types": {
8 | "feature": "Функция",
9 | "fix": "Исправление",
10 | "docs": "Документация",
11 | "refactor": "Рефакторинг",
12 | "chore": "Обслуживание",
13 | "style": "Стиль",
14 | "perf": "Производительность",
15 | "test": "Тест",
16 | "build": "Сборка",
17 | "ci": "CI/CD",
18 | "revert": "Откат"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Китайский (упрощенный)",
23 | "en-US": "Английский (США)",
24 | "zh-TW": "Китайский (традиционный)",
25 | "ja": "Японский",
26 | "ko": "Корейский",
27 | "de": "Немецкий",
28 | "fr": "Французский",
29 | "es": "Испанский",
30 | "it": "Итальянский",
31 | "pt": "Португальский",
32 | "ru": "Русский",
33 | "ar": "Арабский",
34 | "hi": "Хинди",
35 | "nl": "Нидерландский",
36 | "tr": "Турецкий",
37 | "vi": "Вьетнамский",
38 | "id": "Индонезийский",
39 | "th": "Тайский",
40 | "asia": "Азия",
41 | "europe": "Европа и Америка",
42 | "middle_east": "Ближний Восток"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/th/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - ฐานความรู้โค้ดที่ขับเคลื่อนด้วย AI",
3 | "changelog": {
4 | "title": "บันทึกการเปลี่ยนแปลง",
5 | "github_message": "ดูประวัติ commit ทั้งหมดบน GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "คุณสมบัติ",
9 | "fix": "แก้ไข",
10 | "docs": "เอกสาร",
11 | "refactor": "ปรับโครงสร้าง",
12 | "chore": "การบำรุงรักษา",
13 | "style": "สไตล์",
14 | "perf": "ประสิทธิภาพ",
15 | "test": "การทดสอบ",
16 | "build": "การสร้าง",
17 | "ci": "CI/CD",
18 | "revert": "ย้อนกลับ"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "จีน (ประยุกต์)",
23 | "en-US": "อังกฤษ (สหรัฐอเมริกา)",
24 | "zh-TW": "จีน (ดั้งเดิม)",
25 | "ja": "ญี่ปุ่น",
26 | "ko": "เกาหลี",
27 | "de": "เยอรมัน",
28 | "fr": "ฝรั่งเศส",
29 | "es": "สเปน",
30 | "it": "อิตาลี",
31 | "pt": "โปรตุเกส",
32 | "ru": "รัสเซีย",
33 | "ar": "อาหรับ",
34 | "hi": "ฮินดี",
35 | "nl": "ดัตช์",
36 | "tr": "ตุรกี",
37 | "vi": "เวียดนาม",
38 | "id": "อินโดนีเซีย",
39 | "th": "ไทย",
40 | "asia": "เอเชีย",
41 | "europe": "ยุโรปและอเมริกา",
42 | "middle_east": "ตะวันออกกลาง"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/tr/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - Yapay Zeka Destekli Kod Bilgi Tabanı",
3 | "changelog": {
4 | "title": "Değişiklik Günlüğü",
5 | "github_message": "GitHub'da tam commit geçmişini görüntüle",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Özellik",
9 | "fix": "Düzeltme",
10 | "docs": "Dokümantasyon",
11 | "refactor": "Yeniden Düzenleme",
12 | "chore": "Bakım",
13 | "style": "Stil",
14 | "perf": "Performans",
15 | "test": "Test",
16 | "build": "Yapı",
17 | "ci": "CI/CD",
18 | "revert": "Geri Alma"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Çince (Basitleştirilmiş)",
23 | "en-US": "İngilizce (ABD)",
24 | "zh-TW": "Çince (Geleneksel)",
25 | "ja": "Japonca",
26 | "ko": "Korece",
27 | "de": "Almanca",
28 | "fr": "Fransızca",
29 | "es": "İspanyolca",
30 | "it": "İtalyanca",
31 | "pt": "Portekizce",
32 | "ru": "Rusça",
33 | "ar": "Arapça",
34 | "hi": "Hintçe",
35 | "nl": "Hollandaca",
36 | "tr": "Türkçe",
37 | "vi": "Vietnamca",
38 | "id": "Endonezce",
39 | "th": "Tayca",
40 | "asia": "Asya",
41 | "europe": "Avrupa ve Amerika",
42 | "middle_east": "Orta Doğu"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/locales/vi/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "OpenDeekWiki - Cơ sở kiến thức mã nguồn được hỗ trợ bởi AI",
3 | "changelog": {
4 | "title": "Nhật ký thay đổi",
5 | "github_message": "Xem lịch sử commit đầy đủ trên GitHub",
6 | "commit": "Commit",
7 | "types": {
8 | "feature": "Tính năng",
9 | "fix": "Sửa lỗi",
10 | "docs": "Tài liệu",
11 | "refactor": "Tái cấu trúc",
12 | "chore": "Bảo trì",
13 | "style": "Phong cách",
14 | "perf": "Hiệu suất",
15 | "test": "Kiểm thử",
16 | "build": "Xây dựng",
17 | "ci": "CI/CD",
18 | "revert": "Hoàn nguyên"
19 | }
20 | },
21 | "language": {
22 | "zh-CN": "Tiếng Trung (Giản thể)",
23 | "en-US": "Tiếng Anh (Mỹ)",
24 | "zh-TW": "Tiếng Trung (Phồn thể)",
25 | "ja": "Tiếng Nhật",
26 | "ko": "Tiếng Hàn",
27 | "de": "Tiếng Đức",
28 | "fr": "Tiếng Pháp",
29 | "es": "Tiếng Tây Ban Nha",
30 | "it": "Tiếng Ý",
31 | "pt": "Tiếng Bồ Đào Nha",
32 | "ru": "Tiếng Nga",
33 | "ar": "Tiếng Ả Rập",
34 | "hi": "Tiếng Hindi",
35 | "nl": "Tiếng Hà Lan",
36 | "tr": "Tiếng Thổ Nhĩ Kỳ",
37 | "vi": "Tiếng Việt",
38 | "id": "Tiếng Indonesia",
39 | "th": "Tiếng Thái",
40 | "asia": "Châu Á",
41 | "europe": "Châu Âu & Châu Mỹ",
42 | "middle_east": "Trung Đông"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIDotNet/OpenDeepWiki/ee597a3390e5b84bbd82b9066ac68aae6104cbbb/web/public/logo.png
--------------------------------------------------------------------------------
/web/public/mcp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIDotNet/OpenDeepWiki/ee597a3390e5b84bbd82b9066ac68aae6104cbbb/web/public/mcp.png
--------------------------------------------------------------------------------
/web/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Crawl-delay: 5
4 | sitemap:https://opendeep.wiki/api/sitemap.xml
5 | disallow:['/api/','/_next/']
--------------------------------------------------------------------------------
/web/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -x
3 |
4 | # 设置默认API URL
5 | DEFAULT_API_URL="http://localhost:5085"
6 | API_URL="${API_URL:-$DEFAULT_API_URL}"
7 |
8 | echo "Starting application with API_URL: $API_URL"
9 |
10 | # 如果API_URL不是默认值,则进行替换
11 | if [ "$API_URL" != "http://__API_URL_PLACEHOLDER__" ]; then
12 | echo "Replacing API URL placeholder with: $API_URL"
13 |
14 | # 替换 .next 目录下所有 JavaScript 文件中的占位符
15 | find /app/.next -name "*.js" -type f -exec sed -i "s|http://__API_URL_PLACEHOLDER__|$API_URL|g" {} \;
16 | find /app/.next -name "*.json" -type f -exec sed -i "s|http://__API_URL_PLACEHOLDER__|$API_URL|g" {} \;
17 |
18 | # 替换服务端渲染文件中的占位符
19 | find /app -name "server.js" -type f -exec sed -i "s|http://__API_URL_PLACEHOLDER__|$API_URL|g" {} \;
20 |
21 | # 创建客户端运行时配置
22 | cat > /app/public/runtime-config.js << EOF
23 | window.__API_URL__ = '$API_URL';
24 | EOF
25 |
26 | echo "API URL replacement completed"
27 | else
28 | echo "Using default API URL configuration"
29 | fi
30 |
31 | # 启动Next.js应用
32 | echo "Starting Next.js server..."
33 | exec node server.js
--------------------------------------------------------------------------------
/web/styles.module.css:
--------------------------------------------------------------------------------
1 | body{
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .adminLayout {
7 | display: flex;
8 | height: 100vh;
9 | background-color: #f7f9fc;
10 | }
11 |
12 | .sidebarContainer {
13 | position: fixed;
14 | left: 0;
15 | top: 0;
16 | height: 100%;
17 | background-color: white;
18 | transition: all 0.3s;
19 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
20 | z-index: 20;
21 | }
22 |
23 | .sidebarOpen {
24 | width: 16rem; /* 64px */
25 | }
26 |
27 | .sidebarClosed {
28 | width: 5rem; /* 20px */
29 | }
30 |
31 | .sidebarLogo {
32 | height: 4rem;
33 | display: flex;
34 | align-items: center;
35 | justify-content: center;
36 | padding: 0 1rem;
37 | border-bottom: 1px solid #f1f5f9;
38 | }
39 |
40 | .mainContent {
41 | display: flex;
42 | flex-direction: column;
43 | flex: 1;
44 | overflow: hidden;
45 | }
46 |
47 | .headerContainer {
48 | background-color: white;
49 | height: 4rem;
50 | border-bottom: 1px solid #f1f5f9;
51 | display: flex;
52 | align-items: center;
53 | padding: 0 1.5rem;
54 | position: sticky;
55 | top: 0;
56 | z-index: 10;
57 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
58 | }
59 |
60 | .contentContainer {
61 | flex: 1;
62 | overflow: auto;
63 | padding: 1.5rem;
64 | }
65 |
66 | .contentWrapper {
67 | background-color: white;
68 | border-radius: 0.5rem;
69 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
70 | padding: 1.5rem;
71 | min-height: calc(100vh - 10rem);
72 | }
73 |
74 | .navItem {
75 | display: flex;
76 | align-items: center;
77 | padding: 0.75rem 1rem;
78 | border-radius: 0.5rem;
79 | transition: all 0.2s;
80 | margin-bottom: 0.5rem;
81 | color: #4b5563;
82 | }
83 |
84 | .navItemActive {
85 | background-color: #f0f7ff;
86 | color: #0771c9;
87 | font-weight: 500;
88 | }
89 |
90 | .navItemIcon {
91 | font-size: 1.25rem;
92 | }
93 |
94 | .navItemLabel {
95 | margin-left: 0.75rem;
96 | }
97 |
98 | @media (min-width: 768px) {
99 | .sidebarContainer {
100 | position: relative;
101 | }
102 | }
--------------------------------------------------------------------------------
/web/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./app/**/*.{js,ts,jsx,tsx}",
5 | "./pages/**/*.{js,ts,jsx,tsx}",
6 | "./components/**/*.{js,ts,jsx,tsx}",
7 | ],
8 | theme: {
9 | extend: {
10 | colors: {
11 | primary: {
12 | 50: '#f0f7ff',
13 | 100: '#e0effe',
14 | 200: '#bae0fd',
15 | 300: '#81c7fa',
16 | 400: '#41a7f6',
17 | 500: '#1a8ee6',
18 | 600: '#0771c9',
19 | 700: '#095ca3',
20 | 800: '#0c4d86',
21 | 900: '#0f4171',
22 | 950: '#092847',
23 | },
24 | secondary: {
25 | 50: '#f8f8f9',
26 | 100: '#f0f1f2',
27 | 200: '#e6e8ea',
28 | 300: '#d1d6da',
29 | 400: '#b2bac1',
30 | 500: '#8e99a3',
31 | 600: '#717c87',
32 | 700: '#5c6570',
33 | 800: '#4e5660',
34 | 900: '#42484f',
35 | 950: '#25292e',
36 | },
37 | },
38 | },
39 | },
40 | plugins: [],
41 | corePlugins: {
42 | preflight: true,
43 | },
44 | }
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "noEmit": true,
13 | "incremental": true,
14 | "module": "esnext",
15 | "esModuleInterop": true,
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "plugins": [
21 | {
22 | "name": "next"
23 | }
24 | ]
25 | },
26 | "include": [
27 | "next-env.d.ts",
28 | ".next/types/**/*.ts",
29 | "**/*.ts",
30 | "**/*.tsx"
31 | ],
32 | "exclude": [
33 | "node_modules"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------