diff --git a/README.md b/README.md
index 9b380e5..9ce420e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Shibuya
-[![Lint code](https://github.com/SkywardAI/shibuya/actions/workflows/lint.yml/badge.svg)](https://github.com/SkywardAI/shibuya/actions/workflows/lint.yml)
+[![Lint code](https://github.com/SkywardAI/shibuya/actions/workflows/lint.yml/badge.svg)](https://github.com/SkywardAI/shibuya/actions/workflows/lint.yml) [![Release Distribution](https://github.com/SkywardAI/shibuya/actions/workflows/distribution.yml/badge.svg)](https://github.com/SkywardAI/shibuya/actions/workflows/distribution.yml)
A project built Electron + React.js, to dig out the potential of cross platform AI completion.
## Development Build
@@ -22,6 +22,12 @@ And
pnpm run electron
```
One on each terminal, so they won't conflict with each other.
+
+## Distributions
+There are some distribution files in releast page. Please download and run `SkywardaiChat-vX.Y.Z.(AppImage|dmg|exe)` according to your platform.
+Currently there's no `Code Signing` in our distributions, so your defender might block you from using the application. Please allow install to use the distributions.
+
+> Sensitive informations are stored only at your own machine. No one can see them.
+
## References
* [Wllama](https://github.com/ngxson/wllama)
-* [Voy](https://github.com/tantaraio/voy)
diff --git a/electron.js b/electron.js
index 1386ad1..74f5e2f 100644
--- a/electron.js
+++ b/electron.js
@@ -12,7 +12,7 @@ function createWindow() {
width: 900,
minWidth: 560,
minHeight: 250,
- // autoHideMenuBar: true,
+ autoHideMenuBar: true,
})
if(app.isPackaged) {
diff --git a/package.json b/package.json
index b86ac0b..bc9e849 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"name": "Bohan Cheng",
"email": "cbh778899@outlook.com"
},
- "version": "0.1.9",
+ "version": "0.1.10",
"main": "electron.js",
"scripts": {
"dev": "npm run start & npm run electron",
diff --git a/src/components/Entry.jsx b/src/components/Entry.jsx
index 6c0a97b..cb6fe06 100644
--- a/src/components/Entry.jsx
+++ b/src/components/Entry.jsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import useIDB from '../utils/idb'
import { downloadModel, isModelDownloaded, loadModel } from '../utils/workers/worker'
-import { getPlatformSettings } from '../utils/platform_settings';
+import { getPlatformSettings } from '../utils/general_settings';
export default function Entry({complete}) {
diff --git a/src/components/chat/Conversation.jsx b/src/components/chat/Conversation.jsx
index 96f4b08..bcb30a6 100644
--- a/src/components/chat/Conversation.jsx
+++ b/src/components/chat/Conversation.jsx
@@ -89,7 +89,8 @@ export default function Conversation({ uid }) {
if(upload_file) {
const is_img = upload_file.type.startsWith('image')
const file_obj = {
- content: new Uint8Array(await upload_file.arrayBuffer())
+ content: new Uint8Array(await upload_file.arrayBuffer()),
+ format: upload_file.name.split('.').pop().toLowerCase()
}
if(!is_img) file_obj.name = upload_file.name;
user_message[
diff --git a/src/components/settings/AwsSettings.jsx b/src/components/settings/AwsSettings.jsx
index a35e12f..b0ed73f 100644
--- a/src/components/settings/AwsSettings.jsx
+++ b/src/components/settings/AwsSettings.jsx
@@ -4,13 +4,15 @@ import SettingSection from "./SettingSection";
import TextComponent from "./components/TextComponent";
import PasswordComponent from "./components/PasswordComponent";
import { getJSONCredentials, storeCredentials } from "../../utils/workers/aws-worker";
-import { getPlatformSettings } from "../../utils/platform_settings";
+import { getPlatformSettings } from "../../utils/general_settings";
-export default function AwsSettings({ platform_setting, updatePlatformSetting }) {
+export default function AwsSettings({ trigger, platform_setting, updatePlatformSetting }) {
const [ aws_enabled, setAwsEnabled ] = useState(false);
const [ aws_region, setAwsRegion ] = useState('');
- const [ aws_pool_id, setAwsPoolId ] = useState('');
+ const [ aws_key_id, setAwsKeyID ] = useState('');
+ const [ aws_secret_key, setAwsSecretKey ] = useState('');
+ const [ aws_session_token, setAwsSessionToken ] = useState('');
const [ aws_model_id, setAwsModelID ] = useState('');
function setEnabled(is_enabled) {
@@ -26,12 +28,18 @@ export default function AwsSettings({ platform_setting, updatePlatformSetting })
}
function saveSettings() {
+ const credentials = {
+ key_id: aws_key_id, secret_key: aws_secret_key
+ }
+ if(aws_session_token) {
+ credentials.session_token = aws_session_token
+ }
storeCredentials(
- aws_region, aws_pool_id,
+ credentials, aws_key_id && aws_secret_key,
platform_setting.enabled_platform === 'AWS'
)
updatePlatformSetting({
- aws_model_id
+ aws_model_id, aws_region
})
}
@@ -41,12 +49,14 @@ export default function AwsSettings({ platform_setting, updatePlatformSetting })
const credentials = await getJSONCredentials();
if(credentials) {
- setAwsRegion(credentials.region);
- setAwsPoolId(credentials.pool_id);
+ setAwsKeyID(credentials.key_id);
+ setAwsSecretKey(credentials.secret_key);
+ setAwsSessionToken(credentials.session_token);
}
- const { aws_model_id: model_id } = getPlatformSettings();
+ const { aws_model_id: model_id, aws_region: region } = getPlatformSettings();
setAwsModelID(model_id);
+ setAwsRegion(region);
})()
}, [])
@@ -54,22 +64,39 @@ export default function AwsSettings({ platform_setting, updatePlatformSetting })
setAwsEnabled(platform_setting.enabled_platform === 'AWS');
}, [platform_setting])
+ useEffect(()=>{
+ trigger && saveSettings();
+ // eslint-disable-next-line
+ }, [trigger])
+
return (
-
+
+
- save settings
)
}
\ No newline at end of file
diff --git a/src/components/settings/ModelSettings.jsx b/src/components/settings/ModelSettings.jsx
new file mode 100644
index 0000000..8c35c9a
--- /dev/null
+++ b/src/components/settings/ModelSettings.jsx
@@ -0,0 +1,52 @@
+import { useEffect, useState } from "react";
+import ScrollBarComponent from "./components/ScrollBarComponent";
+import SettingSection from "./SettingSection";
+import { getModelSettings, updateModelSettings } from "../../utils/general_settings";
+
+export default function ModelSettings({ trigger }) {
+
+ const [max_tokens, setMaxTokens] = useState(0);
+ const [top_p, setTopP] = useState(0);
+ const [temperature, setTemperature] = useState(0);
+
+ function saveSettings() {
+ updateModelSettings({
+ max_tokens, top_p, temperature
+ })
+ }
+
+ useEffect(()=>{
+ trigger && saveSettings();
+ // eslint-disable-next-line
+ }, [trigger])
+
+ useEffect(()=>{
+ const model_settings = getModelSettings();
+ setMaxTokens(model_settings.max_tokens);
+ setTopP(model_settings.top_p);
+ setTemperature(model_settings.temperature);
+ }, [])
+
+ return (
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/settings/components/ScrollBarComponent.jsx b/src/components/settings/components/ScrollBarComponent.jsx
new file mode 100644
index 0000000..152e2d7
--- /dev/null
+++ b/src/components/settings/components/ScrollBarComponent.jsx
@@ -0,0 +1,52 @@
+import { useEffect, useState } from "react"
+
+export default function ScrollBarComponent({ cb, value, disabled, title, description, min, max, times_10, step }) {
+
+ const [scrollValue, setScrollValue] = useState((times_10 ? 10 : 1) * value);
+ const [textValue, setTextValue] = useState(value);
+
+ function checkValue(v) {
+ v = v || +textValue;
+ return v <= max && v >= min;
+ }
+
+ function setValue(value, is_scroll = false) {
+ if(is_scroll) {
+ setTextValue(times_10 ? value / 10 : value);
+ setScrollValue(value);
+ } else {
+ if(!isNaN(+value)) {
+ setScrollValue(times_10 ? value * 10 : value);
+ }
+ setTextValue(value);
+ }
+ }
+
+ useEffect(()=>{
+ !isNaN(+textValue) && checkValue() && cb(textValue);
+ // eslint-disable-next-line
+ }, [textValue])
+
+ useEffect(()=>{
+ setScrollValue((times_10 ? 10 : 1) * value)
+ setTextValue(value);
+ // eslint-disable-next-line
+ }, [value])
+
+
+ return (
+
+
{title}
+ { description &&
{description}
}
+
+ setValue(evt.target.value, true)}
+ step={step || 1} value={scrollValue}
+ min={(times_10 ? 10 : 1) * min} max={(times_10 ? 10 : 1) * max}
+ disabled={disabled}
+ />
+ setValue(evt.target.value, false)} />
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/settings/components/TrueFalseComponent.jsx b/src/components/settings/components/TrueFalseComponent.jsx
index 1ff9830..57653d6 100644
--- a/src/components/settings/components/TrueFalseComponent.jsx
+++ b/src/components/settings/components/TrueFalseComponent.jsx
@@ -3,8 +3,12 @@ export default function TrueFalseComponent({ cb, value, title, description }) {
{title}
{ description &&
{description}
}
-
-
cb(evt.target.checked)} />
+
+
OFF
+
+ cb(evt.target.checked)} />
+
+
ON
)
diff --git a/src/components/settings/index.jsx b/src/components/settings/index.jsx
index 73a5b1f..2f3e2fd 100644
--- a/src/components/settings/index.jsx
+++ b/src/components/settings/index.jsx
@@ -1,10 +1,12 @@
import { useState } from "react";
import AwsSettings from "./AwsSettings";
-import { getPlatformSettings, updatePlatformSettings as setStorageSetting } from "../../utils/platform_settings";
+import { getPlatformSettings, updatePlatformSettings as setStorageSetting } from "../../utils/general_settings";
+import ModelSettings from "./ModelSettings";
export default function Settings() {
const [platfom_settings, updatePlatformSettings] = useState(getPlatformSettings())
+ const [ saveSettingTrigger, toggleSaveSetting ] = useState(false);
function updateSettings(settings) {
const new_settings = {
@@ -15,12 +17,24 @@ export default function Settings() {
setStorageSetting(new_settings);
}
+ function save() {
+ toggleSaveSetting(true);
+ setTimeout(()=>toggleSaveSetting(false), 1000);
+ }
+
return (
+
+
+ { saveSettingTrigger ? "Settings Saved!" : "Save Settings" }
+
)
}
\ No newline at end of file
diff --git a/src/styles/settings.css b/src/styles/settings.css
index ec33c1a..4b99b5a 100644
--- a/src/styles/settings.css
+++ b/src/styles/settings.css
@@ -6,6 +6,27 @@
overflow: auto;
}
+.setting-page > .save-settings {
+ position: fixed;
+ right: 20px;
+ top: 20px;
+ width: fit-content;
+ height: fit-content;
+ background-color: dodgerblue;
+ color: white;
+ padding: 10px 20px;
+ user-select: none;
+ z-index: 10;
+ border-radius: 10px;
+ transition-duration: .3s;
+}
+.setting-page > .save-settings:not(.saved):hover {
+ background-color: rgb(24, 115, 205);
+}
+.setting-page > .save-settings.saved {
+ background-color: limegreen;
+}
+
.setting-page > .setting-section > .title {
font-size: 25px;
font-weight: bold;
@@ -62,9 +83,21 @@
margin: auto;
}
-.setting-page > .setting-section > .component > .checkbox-container {
- --checkbox-width: 100px;
+.setting-page > .setting-section > .component > .checkbox {
+ display: flex;
+ align-items: center;
+}
+
+.setting-page > .setting-section > .component > .checkbox > .text {
+ color: gray;
+ font-weight: bold;
+}
+
+.setting-page > .setting-section > .component > .checkbox > .checkbox-container {
+ --checkbox-width: 80px;
+ --checkbox-height: 25px;
width: var(--checkbox-width);
+ height: var(--checkbox-height);
background-color: lightgray;
border-radius: 50px;
background-image: linear-gradient(to right, limegreen 50%, transparent 50%);
@@ -72,13 +105,16 @@
background-position: 100%;
transition-duration: .3s;
border: 1px solid gray;
+ position: relative;
+ margin-right: 20px;
+ margin-left: 20px;
}
-.setting-page > .setting-section > .component > .checkbox-container::after {
+.setting-page > .setting-section > .component > .checkbox > .checkbox-container::after {
content: "";
display: block;
- width: var(--main-height);
- height: var(--main-height);
+ width: var(--checkbox-height);
+ height: var(--checkbox-height);
border-radius: 50px;
position: absolute;
left: -2px;
@@ -89,7 +125,7 @@
transition-duration: .3s;
}
-.setting-page > .setting-section > .component > .checkbox-container > input {
+.setting-page > .setting-section > .component > .checkbox > .checkbox-container > input {
width: 100%;
height: 100%;
position: absolute;
@@ -99,10 +135,31 @@
opacity: 0;
}
-.setting-page > .setting-section > .component > .checkbox-container:has(input:checked) {
+.setting-page > .setting-section > .component > .checkbox > .checkbox-container:has(input:checked) {
background-position: 0%;
}
-.setting-page > .setting-section > .component > .checkbox-container:has(input:checked)::after {
- transform: translateX(calc(var(--checkbox-width) - var(--main-height)));
+.setting-page > .setting-section > .component > .checkbox > .checkbox-container:has(input:checked)::after {
+ transform: translateX(calc(var(--checkbox-width) - var(--checkbox-height)));
+}
+
+.setting-page > .setting-section > .component > .scroll-group {
+ display: flex;
+ align-items: center;
+
+ --elem-margin: 5px;
+ --range-width: calc(70% - var(--elem-margin));
+ --text-width: calc(30% - var(--elem-margin));
+}
+
+.setting-page > .setting-section > .component > .scroll-group > input[type='range'] {
+ width: var(--range-width);
+ margin-right: var(--elem-margin);
+ padding: unset;
+}
+.setting-page > .setting-section > .component > .scroll-group > input[type='text'] {
+ text-align: center;
+ height: 100%;
+ width: var(--text-width);
+ margin-left: var(--elem-margin);
}
\ No newline at end of file
diff --git a/src/utils/general_settings.js b/src/utils/general_settings.js
new file mode 100644
index 0000000..4910a62
--- /dev/null
+++ b/src/utils/general_settings.js
@@ -0,0 +1,41 @@
+const PLATFORM_SETTINGS_KEY = 'platform-settings'
+const DEFAULT_PLATFORM_SETTINGS = {
+ enabled_platform: null,
+ // aws
+ aws_model_id: '', aws_region: ''
+}
+
+const MODEL_SETTINGS_KEY = 'general-model-settings'
+const DEFAULT_MODEL_SETTINGS = {
+ max_tokens: 128,
+ top_p: 0.9,
+ temperature: 0.7
+}
+
+function getSettings(key, default_settings) {
+ const setting = localStorage.getItem(key);
+ if(!setting) {
+ localStorage.setItem(key, JSON.stringify(default_settings))
+ }
+ return setting ? JSON.parse(setting) : default_settings;
+}
+
+function updateSettings(key, settings, default_settings) {
+ localStorage.setItem(key, JSON.stringify({...default_settings, ...settings}));
+}
+
+export function getPlatformSettings() {
+ return getSettings(PLATFORM_SETTINGS_KEY, DEFAULT_PLATFORM_SETTINGS);
+}
+
+export function updatePlatformSettings(settings) {
+ updateSettings(PLATFORM_SETTINGS_KEY, settings, DEFAULT_PLATFORM_SETTINGS);
+}
+
+export function getModelSettings() {
+ return getSettings(MODEL_SETTINGS_KEY, DEFAULT_MODEL_SETTINGS);
+}
+
+export function updateModelSettings(settings) {
+ updateSettings(MODEL_SETTINGS_KEY, settings, DEFAULT_MODEL_SETTINGS);
+}
\ No newline at end of file
diff --git a/src/utils/platform_settings.js b/src/utils/platform_settings.js
deleted file mode 100644
index 9b39423..0000000
--- a/src/utils/platform_settings.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const PLATFORM_SETTINGS_KEY = 'platform-settings'
-const DEFAULT_PLATFORM_SETTINGS = {
- enabled_platform: null,
- // aws
- aws_model_id: ''
-}
-
-export function getPlatformSettings() {
- const setting = localStorage.getItem(PLATFORM_SETTINGS_KEY);
- if(!setting) {
- localStorage.setItem(PLATFORM_SETTINGS_KEY, JSON.stringify(DEFAULT_PLATFORM_SETTINGS))
- }
- return setting ? JSON.parse(setting) : DEFAULT_PLATFORM_SETTINGS;
-}
-
-export function updatePlatformSettings(settings) {
- localStorage.setItem(PLATFORM_SETTINGS_KEY, JSON.stringify(settings))
-}
\ No newline at end of file
diff --git a/src/utils/workers/aws-worker.js b/src/utils/workers/aws-worker.js
index 6d1eff7..76657fd 100644
--- a/src/utils/workers/aws-worker.js
+++ b/src/utils/workers/aws-worker.js
@@ -1,26 +1,24 @@
-import { fromCognitoIdentityPool } from "@aws-sdk/credential-providers";
import { BedrockRuntimeClient, ConverseStreamCommand } from "@aws-sdk/client-bedrock-runtime";
import { instance } from "../idb";
-import { getPlatformSettings } from "../platform_settings";
-import { genRandomID } from '../tools'
+import { getModelSettings, getPlatformSettings } from "../general_settings";
export async function getCredentials(json_credentials = null) {
const credentials = json_credentials || (await getJSONCredentials());
if(!credentials) return null;
- return fromCognitoIdentityPool({
- clientConfig: { region: credentials.region },
- identityPoolId: credentials.pool_id,
- logins: {
- 'skywardai-developer-id-provider': genRandomID()
- }
- })
+ const obj = {
+ accessKeyId: credentials.key_id,
+ secretAccessKey: credentials.secret_key,
+ }
+ if(credentials.session_token) {
+ obj.sessionToken = credentials.session_token
+ }
+ return obj
}
-export async function storeCredentials(region, pool_id, enabled = false) {
- console.log(region, pool_id)
- const update_result = await instance.updateByID('credentials', 'AWS', {json: JSON.stringify({region, pool_id})})
- if(region && pool_id && enabled) await initBedrockClient();
+export async function storeCredentials(credentials, all_filled, enabled = false) {
+ const update_result = await instance.updateByID('credentials', 'AWS', {json: JSON.stringify(credentials)})
+ if(all_filled && enabled) await initBedrockClient();
return !!update_result
}
@@ -38,9 +36,9 @@ export async function initBedrockClient() {
const credentials = await getJSONCredentials();
if(!credentials) return false;
+ const { aws_region: region } = getPlatformSettings();
bedrock_client = new BedrockRuntimeClient({
- region: credentials.region,
- credentials: (await getCredentials(credentials))
+ region, credentials: (await getCredentials(credentials))
});
return true;
}
@@ -94,8 +92,8 @@ let abort_signal = false;
* @returns { Promise
}
*/
export async function chatCompletions(messages, cb = null) {
- const { aws_model_id } = getPlatformSettings();
- if(!aws_model_id || (!bedrock_client && !await initBedrockClient())) {
+ const { aws_model_id, aws_region } = getPlatformSettings();
+ if(!aws_model_id || !aws_region || (!bedrock_client && !await initBedrockClient())) {
console.log('no bedrock')
cb && cb("**Cannot Initialize AWS Bedrock Client**", true)
return null;
@@ -109,41 +107,39 @@ export async function chatCompletions(messages, cb = null) {
system.push({text: content});
return;
}
- normal_messages.push({ role, content: [{text: content}] });
+ const message = {
+ role, content: [{text: content}]
+ }
if(image) {
- normal_messages.push({
- role, content: [
- { image: {
- format: image.format,
- source: { bytes: image.content }
- } }
- ]
- })
+ message.content.push(
+ { image: {
+ format: image.format,
+ source: { bytes: image.content }
+ } }
+ )
}
if(document) {
- normal_messages.push({
- role, content: [
- { document: {
+ message.content.push(
+ { document: {
format: document.format,
name: document.name,
source: { bytes: document.content }
- } }
- ]
- })
+ } }
+ )
}
+ normal_messages.push(message);
})
+ const { max_tokens:maxTokens, top_p:topP, temperature } = getModelSettings();
const input = {
modelId: aws_model_id,
messages: normal_messages,
inferenceConfig: {
- maxTokens: 128,
- temperature: 0.7,
- topP: 0.9
+ maxTokens, temperature, topP
}
}
- if(system) input.system = system;
+ if(system.length) input.system = system;
let response_text = '', usage = {}
abort_signal = false;
diff --git a/src/utils/workers/index.js b/src/utils/workers/index.js
index f5873c4..cb32bcb 100644
--- a/src/utils/workers/index.js
+++ b/src/utils/workers/index.js
@@ -1,4 +1,4 @@
-import { getPlatformSettings } from "../platform_settings";
+import { getPlatformSettings } from "../general_settings";
import { chatCompletions as WllamaCompletions, abortCompletion as WllamaAbort } from "./worker";
import { chatCompletions as AwsCompletions, abortCompletion as AwsAbort } from "./aws-worker"