Merge branch 'docs-pena-team' of github.com:janhq/jan into docs-pena-team

This commit is contained in:
Arista Indrajaya 2024-03-12 16:55:12 +07:00
commit 8dd925f978
32 changed files with 1268 additions and 1142 deletions

View File

@ -7,6 +7,6 @@ if [[ -z "$APP_PATH" ]] || [[ -z "$DEVELOPER_ID" ]]; then
fi
# If both variables are set, execute the following commands
find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \;
find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \;
find "$APP_PATH" -type f -name "*.o" -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \;
find "$APP_PATH" -type f -name "*.o" -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \;

View File

@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
<tr style="text-align:center">
<td style="text-align:center"><b>Stable (Recommended)</b></td>
<td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-win-x64-0.4.7.exe'>
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-win-x64-0.4.8.exe'>
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
<b>jan.exe</b>
</a>
</td>
<td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-x64-0.4.7.dmg'>
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-x64-0.4.8.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>Intel</b>
</a>
</td>
<td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-arm64-0.4.7.dmg'>
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-arm64-0.4.8.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>M1/M2</b>
</a>
</td>
<td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-amd64-0.4.7.deb'>
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-amd64-0.4.8.deb'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.deb</b>
</a>
</td>
<td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-x86_64-0.4.7.AppImage'>
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-x86_64-0.4.8.AppImage'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.AppImage</b>
</a>
@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
<tr style="text-align:center">
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.7-304.exe'>
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.8-313.exe'>
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
<b>jan.exe</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.7-304.dmg'>
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.8-313.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>Intel</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.7-304.dmg'>
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.8-313.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>M1/M2</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.7-304.deb'>
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.8-313.deb'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.deb</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.7-304.AppImage'>
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.8-313.AppImage'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.AppImage</b>
</a>

View File

@ -1,18 +1,20 @@
# Website
# Website & Docs
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
This website is built using [Docusaurus 3.0](https://docusaurus.io/), a modern static website generator.
## Information Architecture
### Information Architecture
We try to **keep routes consistent** to maintain SEO.
- `/guides`: Guides on how to use the Jan application, with GIFs. For end users who are directly using Jan. Always assume users are not technical.
- **`/guides/`**: Guides on how to use the Jan application. For end users who are directly using Jan.
- `/developer`: Developer docs on how to extend Jan. These pages are about what people can build with our software. We must hide the complexity of HOW the app is built, but explain just enough of the high level architecture so devs know enough to build on top of it.
- **`/developer/`**: Developer docs on how to extend Jan. These pages are about what people can build with our software.
- `/api-reference`: Reference documentation, written in Swagger/OpenAPI format.
- **`/api-reference/`**: Reference documentation for the Jan API server, written in Swagger/OpenAPI format.
- `/docs`: Engineering specs and product specs, i.e. HOW the app is built. Mostly for internal reference and for our core contributors who are building the SDK itself.
- **`/changelog/`**: A list of changes made to the Jan application with each release.
- **`/blog/`**: A blog for the Jan application.
### Sidebar Autogeneration
@ -20,34 +22,36 @@ The order of each page is either explicitly defined in `sidebar.js` or follows t
Important slugs are hardcoded at the document level (and shouldn't be rerouted):
```md
```
---
title: Overview
slug: /docs
---
```
## Contributing
## How to Contribute
### Installation
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/dev/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
```
$ yarn
```
### Pre-requisites and Installation
### Local Development
- [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher)
- [yarn](https://yarnpkg.com/) (version 1.22.0 or higher)
```
$ cp .env.example .env
$ yarn start
#### Installation
```bash
cd jan/docs
yarn install
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
#### Build
```
$ yarn build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
@ -56,25 +60,27 @@ This command generates static content into the `build` directory and can be serv
Using SSH:
```
$ USE_SSH=true yarn deploy
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```bash
GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
### Preview URL, Pre-release and Publishing Documentation
When a PR is created, the preview URL will be automatically commented on the PR.
- When a pull request is created, the preview URL will be automatically commented on the pull request.
The documentation will then be published to [https://jan.ai/](https://jan.ai/) when the PR is merged to `main`.
- The documentation will then be published to [https://dev.jan.ai/](https://dev.jan.ai/) when the pull request is merged to `dev`.
- Our open-source maintainers will sync the updated content from `dev` to `docs` branch, which will then be published to [https://jan.ai/](https://jan.ai/).
### Additional Plugins
- @docusaurus/theme-live-codeblock
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/OpenAPISpec.json`
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/jan.yaml` to update the API reference documentation.

View File

@ -18,7 +18,7 @@ keywords:
]
---
Jan turns computers into a thinking machine to change how you use computers.
Jan turns computers into thinking machines to change how we use them.
Jan is created and maintained by Jan Labs, a robotics company.
With Jan, you can:

View File

@ -1,3 +1,88 @@
---
title: Website & Docs
---
This website is built using [Docusaurus 3.0](https://docusaurus.io/), a modern static website generator.
### Information Architecture
We try to **keep routes consistent** to maintain SEO.
- **`/guides/`**: Guides on how to use the Jan application. For end users who are directly using Jan.
- **`/developer/`**: Developer docs on how to extend Jan. These pages are about what people can build with our software.
- **`/api-reference/`**: Reference documentation for the Jan API server, written in Swagger/OpenAPI format.
- **`/changelog/`**: A list of changes made to the Jan application with each release.
- **`/blog/`**: A blog for the Jan application.
### Sidebar Autogeneration
The order of each page is either explicitly defined in `sidebar.js` or follows the [Docusaurus autogenerated](https://docusaurus.io/docs/next/sidebar/autogenerated) naming format, `##-path-name.md`.
Important slugs are hardcoded at the document level (and shouldn't be rerouted):
```
---
title: Overview
slug: /docs
---
```
## How to Contribute
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/dev/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
### Pre-requisites and Installation
- [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher)
- [yarn](https://yarnpkg.com/) (version 1.22.0 or higher)
#### Installation
```bash
cd jan/docs
yarn install
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
#### Build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```bash
GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
### Preview URL, Pre-release and Publishing Documentation
- When a pull request is created, the preview URL will be automatically commented on the pull request.
- The documentation will then be published to [https://dev.jan.ai/](https://dev.jan.ai/) when the pull request is merged to `dev`.
- Our open-source maintainers will sync the updated content from `dev` to `docs` branch, which will then be published to [https://jan.ai/](https://jan.ai/).
### Additional Plugins
- @docusaurus/theme-live-codeblock
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/jan.yaml` to update the API reference documentation.

View File

@ -6,14 +6,13 @@ async function fetchData(siteConfig) {
const repo = siteConfig.projectName;
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases`;
const outputDirectory = path.join(__dirname, '../../docs/guides/changelogs');
const outputDirectory = path.join(__dirname, '../../docs/releases/changelog');
if (!fs.existsSync(outputDirectory)) {
fs.mkdirSync(outputDirectory);
}
let counter = 1;
const categoryFilePath = path.join(outputDirectory, '_category_.json');
const cacheFilePath = path.join(outputDirectory, 'cache.json');
let cachedData = {};
@ -83,7 +82,7 @@ async function fetchData(siteConfig) {
const changes = release.body;
let markdownContent = `---\nsidebar_position: ${counter}\n---\n# ${version}\n\nFor more details, [GitHub Issues](${releaseUrl})\n\nHighlighted Issue: ${issueLink}\n\n${changes}\n`;
let markdownContent = `---\nsidebar_position: ${counter}\nslug: /changelog/changelog-${version}\n---\n# ${version}\n\nFor more details, [GitHub Issues](${releaseUrl})\n\nHighlighted Issue: ${issueLink}\n\n${changes}\n`;
// Write to a separate markdown file for each version
const outputFilePath = path.join(outputDirectory, `changelog-${version}.mdx`);
@ -93,20 +92,6 @@ async function fetchData(siteConfig) {
counter++;
}
// Create _category_.json file
const categoryContent = {
label: 'Changelogs',
position: 5,
link: {
type: 'generated-index',
description: 'Changelog for Jan',
},
};
fs.writeFileSync(categoryFilePath, JSON.stringify(categoryContent, null, 2), 'utf-8');
console.log(`_category_.json has been created at: ${categoryFilePath}`);
}
module.exports = fetchData;

View File

@ -7,6 +7,7 @@ import {
autoUpdater,
} from 'electron-updater'
import { AppEvent } from '@janhq/core'
import { trayManager } from '../managers/tray'
export let waitingToInstallVersion: string | undefined = undefined
@ -22,6 +23,7 @@ export function handleAppUpdates() {
message: 'Would you like to download and install it now?',
buttons: ['Download', 'Later'],
})
trayManager.destroyCurrentTray()
if (action.response === 0) await autoUpdater.downloadUpdate()
})

View File

@ -1,4 +1,4 @@
import { app, BrowserWindow, Menu, Tray } from 'electron'
import { app, BrowserWindow, Tray } from 'electron'
import { join } from 'path'
/**
@ -27,6 +27,7 @@ import { setupReactDevTool } from './utils/dev'
import { cleanLogs } from './utils/log'
import { registerShortcut } from './utils/selectedText'
import { trayManager } from './managers/tray'
const preloadPath = join(__dirname, 'preload.js')
const rendererPath = join(__dirname, '..', 'renderer')
@ -38,6 +39,8 @@ const quickAskUrl = `${mainUrl}/search`
const quickAskHotKey = 'CommandOrControl+J'
const gotTheLock = app.requestSingleInstanceLock()
app
.whenReady()
.then(setupReactDevTool)
@ -48,37 +51,26 @@ app
.then(setupMenu)
.then(handleIPCs)
.then(handleAppUpdates)
.then(createQuickAskWindow)
.then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
.then(createMainWindow)
.then(() => {
if (!app.isPackaged) {
windowManager.mainWindow?.webContents.openDevTools()
}
})
.then(() => {
const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png')
const tray = new Tray(iconPath)
tray.setToolTip(app.getName())
const contextMenu = Menu.buildFromTemplate([
{
label: 'Open Jan',
type: 'normal',
click: () => windowManager.showMainWindow(),
},
{
label: 'Open Quick Ask',
type: 'normal',
click: () => windowManager.showQuickAskWindow(),
},
{ label: 'Quit', type: 'normal', click: () => app.quit() },
])
tray.setContextMenu(contextMenu)
})
.then(() => process.env.CI !== 'e2e' && trayManager.createSystemTray())
.then(() => {
log(`Version: ${app.getVersion()}`)
})
.then(() => {
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
windowManager.showMainWindow()
})
}
app.on('activate', () => {
if (!BrowserWindow.getAllWindows().length) {
createMainWindow()
@ -91,6 +83,10 @@ app.on('ready', () => {
registerGlobalShortcuts()
})
app.on('before-quit', function (evt) {
trayManager.destroyCurrentTray()
})
app.once('quit', () => {
cleanUpAndQuit()
})

39
electron/managers/tray.ts Normal file
View File

@ -0,0 +1,39 @@
import { join } from 'path'
import { Tray, app, Menu } from 'electron'
import { windowManager } from '../managers/window'
class TrayManager {
currentTray: Tray | undefined
createSystemTray = () => {
if (this.currentTray) {
return
}
const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png')
const tray = new Tray(iconPath)
tray.setToolTip(app.getName())
const contextMenu = Menu.buildFromTemplate([
{
label: 'Open Jan',
type: 'normal',
click: () => windowManager.showMainWindow(),
},
{
label: 'Open Quick Ask',
type: 'normal',
click: () => windowManager.showQuickAskWindow(),
},
{ label: 'Quit', type: 'normal', click: () => app.quit() },
])
tray.setContextMenu(contextMenu)
this.currentTray = tray
}
destroyCurrentTray() {
this.currentTray?.destroy()
this.currentTray = undefined
}
}
export const trayManager = new TrayManager()

View File

@ -101,6 +101,7 @@ class WindowManager {
expandQuickAskWindow(heightOffset: number): void {
const width = quickAskWindowConfig.width!
const height = quickAskWindowConfig.height! + heightOffset
this._quickAskWindow?.setMinimumSize(width, height)
this._quickAskWindow?.setSize(width, height, true)
}

View File

@ -41,7 +41,6 @@
"notarize": {
"teamId": "F8AH6NHVY5"
},
"icon": "icons/icon.png"
},
"linux": {
@ -92,7 +91,7 @@
"request": "^2.88.2",
"request-progress": "^3.0.0",
"ulid": "^2.3.0",
"@hurdlegroup/robotjs": "^0.11.4"
"@nut-tree/nut-js": "^4.0.0"
},
"devDependencies": {
"@electron/notarize": "^2.1.0",

View File

@ -1,19 +1,24 @@
import { clipboard, globalShortcut } from "electron";
import { keyTap, keys } from "@hurdlegroup/robotjs";
import { clipboard, globalShortcut } from 'electron'
import { keyboard, Key } from '@nut-tree/nut-js'
/**
* Gets selected text by synthesizing the keyboard shortcut
* "CommandOrControl+c" then reading text from the clipboard
*/
export const getSelectedText = async () => {
const currentClipboardContent = clipboard.readText(); // preserve clipboard content
clipboard.clear();
keyTap("c" as keys, process.platform === "darwin" ? "command" : "control");
await new Promise((resolve) => setTimeout(resolve, 200)); // add a delay before checking clipboard
const selectedText = clipboard.readText();
clipboard.writeText(currentClipboardContent);
return selectedText;
};
const currentClipboardContent = clipboard.readText() // preserve clipboard content
clipboard.clear()
const hotkeys: Key[] = [
process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl,
Key.C,
]
await keyboard.pressKey(...hotkeys)
await keyboard.releaseKey(...hotkeys)
await new Promise((resolve) => setTimeout(resolve, 200)) // add a delay before checking clipboard
const selectedText = clipboard.readText()
clipboard.writeText(currentClipboardContent)
return selectedText
}
/**
* Registers a global shortcut of `accelerator`. The `callback` is called
@ -26,14 +31,14 @@ export const registerShortcut = (
callback: (selectedText: string) => void
) => {
return globalShortcut.register(accelerator, async () => {
callback(await getSelectedText());
});
};
callback(await getSelectedText())
})
}
/**
* Unregisters a global shortcut of `accelerator` and
* is equivalent to electron.globalShortcut.unregister
*/
export const unregisterShortcut = (accelerator: Electron.Accelerator) => {
globalShortcut.unregister(accelerator);
};
globalShortcut.unregister(accelerator)
}

View File

@ -29,15 +29,15 @@ const SelectedText = ({ onCleared }: { onCleared?: () => void }) => {
return shouldShowSelectedText ? (
<div
ref={containerRef}
className="relative rounded-lg border-[1px] border-[#0000000F] bg-[#0000000A] p-[10px]"
className="relative rounded-lg border border-border bg-secondary p-[10px]"
>
<div
className="absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full border-[1px] border-[#0000000F] bg-white drop-shadow"
className="absolute right-2 top-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border border-border bg-white shadow dark:bg-black/80"
onClick={onClearClicked}
>
<X size={16} />
<X size={14} className="text-muted-foreground" />
</div>
<p className="font-semibold text-[#00000099]">{text}</p>
<p className="pr-8 font-medium text-muted-foreground">{text}</p>
</div>
) : (
<div />

View File

@ -56,7 +56,7 @@ const UserInput: React.FC = () => {
}
return (
<div className="flex flex-col space-y-3 p-3">
<div className="flex flex-col space-y-3 bg-white p-3 dark:bg-background">
<form
ref={formRef}
className="flex h-full w-full items-center justify-center"
@ -66,7 +66,7 @@ const UserInput: React.FC = () => {
<LogoMark width={28} height={28} className="mx-auto" />
<input
ref={inputRef}
className="flex-1 bg-transparent font-bold text-black focus:outline-none"
className="flex-1 bg-transparent font-bold focus:outline-none"
type="text"
value={inputValue}
onChange={handleChange}
@ -77,7 +77,6 @@ const UserInput: React.FC = () => {
</Button>
</div>
</form>
<SelectedText onCleared={() => inputRef?.current?.focus()} />
</div>
)

View File

@ -4,7 +4,7 @@ import UserInput from './UserInput'
const Search: React.FC = () => {
return (
<div className="h-screen w-screen overflow-hidden bg-white">
<div className="h-screen w-screen overflow-hidden bg-white dark:bg-background">
<UserInput />
</div>
)

View File

@ -79,6 +79,8 @@ export default function useSendChatMessage() {
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
const activeThreadRef = useRef<Thread | undefined>()
const selectedModelRef = useRef<Model | undefined>()
useEffect(() => {
modelRef.current = activeModel
}, [activeModel])
@ -91,6 +93,10 @@ export default function useSendChatMessage() {
activeThreadRef.current = activeThread
}, [activeThread])
useEffect(() => {
selectedModelRef.current = selectedModel
}, [selectedModel])
const resendChatMessage = async (currentMessage: ThreadMessage) => {
if (!activeThreadRef.current) {
console.error('No active thread')
@ -128,11 +134,13 @@ export default function useSendChatMessage() {
type: MessageRequestType.Thread,
messages: messages,
threadId: activeThreadRef.current.id,
model: activeThreadRef.current.assistants[0].model ?? selectedModel,
model:
activeThreadRef.current.assistants[0].model ?? selectedModelRef.current,
}
const modelId =
selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id
selectedModelRef.current?.id ??
activeThreadRef.current.assistants[0].model.id
if (modelRef.current?.id !== modelId) {
setQueuedMessage(true)
@ -213,7 +221,7 @@ export default function useSendChatMessage() {
{
role: ChatCompletionRole.User,
content:
selectedModel && base64Blob
selectedModelRef.current && base64Blob
? [
{
type: ChatCompletionMessageContentType.Text,
@ -242,7 +250,7 @@ export default function useSendChatMessage() {
)
let modelRequest =
selectedModel ?? activeThreadRef.current.assistants[0].model
selectedModelRef?.current ?? activeThreadRef.current.assistants[0].model
if (runtimeParams.stream == null) {
runtimeParams.stream = true
}
@ -344,7 +352,8 @@ export default function useSendChatMessage() {
?.addNewMessage(threadMessage)
const modelId =
selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id
selectedModelRef.current?.id ??
activeThreadRef.current.assistants[0].model.id
if (modelRef.current?.id !== modelId) {
setQueuedMessage(true)