Skip to content

Commit

Permalink
Feat/ai configure panel (#46)
Browse files Browse the repository at this point in the history
#34

---------

Signed-off-by: seven <[email protected]>
  • Loading branch information
Blankll authored Apr 12, 2024
1 parent 5e55560 commit 7a4416e
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 1 deletion.
3 changes: 3 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ declare module 'vue' {
export interface GlobalComponents {
AppProvider: typeof import('./src/components/AppProvider.vue')['default']
NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCard: typeof import('naive-ui')['NCard']
'NCard:': typeof import('naive-ui')['NCard:']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDropdown: typeof import('naive-ui')['NDropdown']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemRow: typeof import('naive-ui')['NFormItemRow']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
Expand Down
3 changes: 3 additions & 0 deletions src/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ contextBridge.exposeInMainWorld('storeAPI', {
ipcRenderer.invoke('storeAPI', { method: 'GET', key, value: defaultValue }),
set: async (key: string, value: unknown) =>
ipcRenderer.invoke('storeAPI', { method: 'SET', key, value }),
getSecret: async (key: string) => ipcRenderer.invoke('storeAPI', { method: 'GET_SECRET', key }),
setSecret: async (key: string, value: unknown) =>
ipcRenderer.invoke('storeAPI', { method: 'SET_SECRET', key, value }),
});

contextBridge.exposeInMainWorld('sourceFileAPI', {
Expand Down
46 changes: 46 additions & 0 deletions src/electron/storeApi.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,70 @@
import Electron from 'electron';
import Store from 'electron-store';
import * as os from 'node:os';
import crypto from 'crypto';

export enum StoreApiMethods {
GET = 'GET',
SET = 'SET',
GET_SECRET = 'GET_SECRET',
SET_SECRET = 'SET_SECRET',
}
export type StoreApiInput = {
method: StoreApiMethods;
key: string;
value: unknown;
};

const getMacAddress = (): string | undefined => {
const networkInterfaces = os.networkInterfaces();

for (const name of Object.keys(networkInterfaces)) {
for (const net of networkInterfaces[name]!) {

Check warning on line 22 in src/electron/storeApi.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest, 20.x)

Forbidden non-null assertion

Check warning on line 22 in src/electron/storeApi.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 20.x)

Forbidden non-null assertion

Check warning on line 22 in src/electron/storeApi.ts

View workflow job for this annotation

GitHub Actions / build (windows-latest, 20.x)

Forbidden non-null assertion
// Skip over non-ipv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) {
return net.mac;
}
}
}
};
const store = new Store();

const macAddress = getMacAddress();
const secretKey = crypto.createHash('sha256').update(macAddress).digest();
const iv = Buffer.from('dockitse'.repeat(2), 'utf8');
const encryptValue = (value: string) => {
const cipher = crypto.createCipheriv('aes-256-cbc', secretKey, iv);
let encrypted = cipher.update(value, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
};

const decryptValue = (encryptedValue: string) => {
const decipher = crypto.createDecipheriv('aes-256-cbc', secretKey, iv);
let decrypted = decipher.update(encryptedValue, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};

const storeApi: { [key: string]: (key: string, val: unknown) => unknown } = {
get: (key: string, defaultValue: unknown) => store.get(key, defaultValue),

set: (key: string, value: unknown) => {
store.set(key, value);
},

get_secret: (key: string) => {
const encryptedValue = store.get(key, '');

return encryptedValue ? JSON.parse(decryptValue(encryptedValue as string)) : undefined;
},

set_secret: (key: string, value: unknown) => {
const encryptedValue = encryptValue(JSON.stringify(value));
store.set(key, encryptedValue);
},
};

export const registerStoreApiListener = (ipcMain: Electron.IpcMain) => {
ipcMain.handle('storeAPI', (_, { method, key, value }: StoreApiInput) =>
storeApi[method.toLowerCase()](key, value),
Expand Down
7 changes: 7 additions & 0 deletions src/lang/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export const enUS = {
auto: 'Follow OS',
dark: 'Dark Theme',
light: 'Light Theme',
ai: {
title: 'GPTs',
others: 'Other GPTs',
model: 'Model',
apiKey: 'API Key',
prompt: 'Prompt',
},
},
connection: {
new: 'New connection',
Expand Down
7 changes: 7 additions & 0 deletions src/lang/zhCN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export const zhCN = {
auto: '跟随系统',
dark: '暗黑主题',
light: '月白主题',
ai: {
title: 'GPTs配置',
others: '其他GPTs',
model: '模型',
apiKey: 'API密钥',
prompt: '提示词',
},
},
connection: {
new: '新建连接',
Expand Down
15 changes: 15 additions & 0 deletions src/store/appStore.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
import { defineStore } from 'pinia';
import { pureObject } from '../common';

export enum ThemeType {
AUTO = 'auto',
DARK = 'dark',
LIGHT = 'light',
}

export enum LanguageType {
AUTO = 'auto',
ZH_CN = 'zhCN',
EN_US = 'enUS',
}
const { storeAPI } = window;
export const useAppStore = defineStore('app', {
state: (): {
themeType: ThemeType;
languageType: LanguageType;
connectPanel: boolean;
uiThemeType: Exclude<ThemeType, ThemeType.AUTO>;
skipVersion: string;
aigcConfig: {
openAi: { apiKey?: string; model?: string; prompt?: string };
};
} => {
return {
themeType: ThemeType.AUTO,
languageType: LanguageType.AUTO,
connectPanel: true, //
uiThemeType: ThemeType.LIGHT,
skipVersion: '',
aigcConfig: { openAi: {} },
};
},
persist: true,
actions: {
async fetchAigcConfig() {
this.aigcConfig = await storeAPI.getSecret('aigcConfig', { openAi: {} });
},
setConnectPanel() {
this.connectPanel = !this.connectPanel;
},
Expand All @@ -49,5 +60,9 @@ export const useAppStore = defineStore('app', {
getEditorTheme() {
return this.uiThemeType === ThemeType.DARK ? 'vs-dark' : 'vs-light';
},
async saveAigcConfig(config: { [key: string]: unknown }) {
this.aigcConfig = config;
await storeAPI.setSecret('aigcConfig', pureObject(config));
},
},
});
73 changes: 73 additions & 0 deletions src/views/setting/components/about-us.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div class="about-us">
<h1 class="title">About Us</h1>
<div class="section">
<h2 class="subtitle">DocKit</h2>
<p>
DocKit is a modern cross-platform NoSQL/NewSQL GUI client. Explore your data any time from
your Mac, Windows, and Linux.
</p>
</div>
<div class="section">
<h2 class="subtitle">Features</h2>
<ul>
<li>
Full-featured editor, powered by monaco-editor, the backbone of VSCode, providing a
familiar editor environment for developers.
</li>
<li>Keep your connections in desktop apps, moving the dependencies of dashboard tools.</li>
<li>
File persistence, allowing you to save your code in your machine as a file, never lost.
</li>
<li>Supports multiple engines including Elasticsearch, OpenSearch, and more to come.</li>
</ul>
</div>
<div class="section">
<h2 class="subtitle">License</h2>
<p>DocKit is an open-source project under the Apache 2.0 License.</p>
</div>
</div>
</template>

<script setup lang="ts">
// Add any necessary TypeScript code here
</script>

<style lang="scss" scoped>
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.about-us {
animation: fadeIn 2s ease-in;
.title {
font-size: 2em;
text-align: center;
margin-bottom: 1em;
}
.section {
margin-bottom: 2em;
.subtitle {
font-size: 1.5em;
}
p,
ul {
font-size: 1.2em;
line-height: 1.5;
}
ul {
padding-left: 1em;
}
}
}
</style>
58 changes: 58 additions & 0 deletions src/views/setting/components/aigc.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<div>
<n-tabs type="line" animated>
<n-tab-pane name="OpenAI" tab="OpenAI">
<n-form class="form-tab-pane">
<n-form-item-row :label="$t('setting.ai.model')">
<n-input v-model:value="openAi.model" />
</n-form-item-row>
<n-form-item-row :label="$t('setting.ai.apiKey')">
<n-input type="password" show-password-on="click" v-model:value="openAi.apiKey" />
</n-form-item-row>
<n-form-item-row :label="$t('setting.ai.prompt')">
<n-input type="textarea" v-model:value="openAi.prompt" />
</n-form-item-row>
<n-button type="error" @click="reset" class="action-button">Cancel</n-button>
<n-button type="success" @click="save" class="action-button">Save</n-button>
<n-button type="primary" @click="enable" class="action-button">Enable</n-button>
</n-form>
</n-tab-pane>
<n-tab-pane :name="$t('setting.ai.others')" :tab="$t('setting.ai.others')">
Coming soon
</n-tab-pane>
</n-tabs>
</div>
</template>

<script setup lang="ts">
import { useAppStore } from '../../../store';
import { storeToRefs } from 'pinia';
const appStore = useAppStore();
const { fetchAigcConfig, saveAigcConfig } = appStore;
const { aigcConfig } = storeToRefs(appStore);
const openAi = ref({ ...aigcConfig.value.openAi });
const reset = () => {
openAi.value = { apiKey: '', model: '', prompt: '' };
aigcConfig.value.openAi = openAi.value;
};
const save = async () => {
await saveAigcConfig({ ...aigcConfig.value, openAi: openAi.value });
};
const enable = async () => {
await saveAigcConfig({ ...aigcConfig.value, openAi: openAi.value, enabled: true });
};
fetchAigcConfig();
</script>

<style lang="scss" scoped>
.form-tab-pane {
width: 96%;
.action-button {
margin-right: 10px;
}
}
</style>
8 changes: 7 additions & 1 deletion src/views/setting/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
<n-tab-pane n-tab-pane name="Basic" :tab="$t('setting.basic')">
<basic-setting />
</n-tab-pane>
<n-tab-pane n-tab-pane name="AI" :tab="$t('setting.ai.title')">
<aigc />
</n-tab-pane>
<n-tab-pane n-tab-pane name="About" :tab="$t('setting.about')">
<basic-setting />
<about-us />
</n-tab-pane>
</n-tabs>
</div>
</template>

<script setup lang="ts">
import BasicSetting from './components/basic.vue';
import Aigc from './components/aigc.vue';
import AboutUs from './components/about-us.vue';
</script>

<style lang="scss" scoped>
Expand All @@ -25,6 +30,7 @@ import BasicSetting from './components/basic.vue';
padding: 15px 0;
border-right: 1px solid var(--border-color);
}
.n-tabs-nav-scroll-content {
height: 100%;
width: 200px;
Expand Down
2 changes: 2 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface IElectronAPI {
export interface IStoreAPI {
get: <T>(key: string, defaultValue: T) => Promise<T>;
set: <T>(key: string, value: T) => Promise<void>;
getSecret: <T>(key: string, defaultValue: T) => Promise<T>;
setSecret: <T>(key: string, value: T) => Promise<void>;
}

export interface ISourceFileAPI {
Expand Down

0 comments on commit 7a4416e

Please sign in to comment.