Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,6 @@ dist
.trae
.workbuddy
__pycache__
.catpawai
.catpawai
build/ios/DerivedData
/output
31 changes: 31 additions & 0 deletions build/replace/src/app/lib/install-src.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// install-src.js
// Determines the Android APK architecture identifier at runtime.
// Used to match the correct release asset when checking/downloading upgrades.
//
// The Android APK splits produce four flavors:
// arm64-v8a -> Node.js os.arch() === 'arm64'
// armeabi-v7a -> Node.js os.arch() === 'arm'
// x86_64 -> Node.js os.arch() === 'x64'
// universal -> (ignored; the device CPU resolves to one of the above)
//
// We resolve at runtime from os.arch() so the same bundled code works for
// every split without a build-time injection step: the APK the user installed
// only contains the native libraries for its target ABI, so os.arch() always
// reflects the ABI that is actually running on device.

import os from 'os'

const archMap = {
arm64: 'arm64-v8a',
arm: 'armeabi-v7a',
x64: 'x86_64',
// 32-bit x86 is virtually nonexistent on Android; treat it as x86_64 so
// upgrade matching still resolves to a real asset.
ia32: 'x86_64',
x32: 'x86_64'
}

const arch = os.arch()
const installSrc = 'electerm-ios-' + (archMap[arch] || 'arm64-v8a')

export default installSrc
44 changes: 34 additions & 10 deletions build/replace/src/app/lib/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ const mandatoryGuardrails = [
'6. If a request violates these policies, decline politely, state the reason in one sentence, and offer a safe alternative when possible. Do not lecture beyond that.'
].join('\n')

const defaultAIPreset = {
baseURLAI: 'https://ai.electerm.org/api/ai',
apiPathAI: '/chat/completions',
modelAI: 'mistral-small-latest',
authHeaderNameAI: 'Authorization: Bearer',
id: 'ai.electerm.org',
nameAI: 'ai.electerm.org(default free)'
}
// const defaultAIPreset = {
// baseURLAI: 'https://ai.electerm.org/api/ai',
// apiPathAI: '/chat/completions',
// modelAI: 'mistral-small-latest',
// authHeaderNameAI: 'Authorization: Bearer',
// id: 'ai.electerm.org',
// nameAI: 'ai.electerm.org(default free)'
// }

function buildServer () {
return `http://${process.env.HOST}:${process.env.PORT}`
Expand Down Expand Up @@ -66,7 +66,6 @@ export async function index (req, res) {
home,
version: packInfo.version,
siteName: packInfo.name,
defaultAIPreset,
fsFunctions,
isWebApp: true,
versionFile: 'version-android.html',
Expand All @@ -82,7 +81,32 @@ export async function index (req, res) {
hideLocalTerminal: true,
AIDisclamer: 'AI generated content is for reference only',
mandatoryGuardrails,
enableAIFlag: true
enableAIFlag: true,
syncTypes: ['github', 'custom', 'webdav'],
AITermOfUse: `About the AI feature / AI 功能说明

When you use the AI feature, your input (such as text, commands, terminal output, or other data you send to the model) is transmitted to the third-party LLM provider you configure. That provider may collect, store, and use it according to their own privacy policy. electerm does not control, and is not responsible for, how third-party LLM providers handle your data.

Please review the privacy policy of the LLM provider you choose, and avoid sending sensitive or confidential information when using AI features.

使用 AI 功能时,你输入的内容(如文本、命令、终端输出或发送给模型的其他数据)会被传输给你所配置的第三方大模型服务商。该服务商可能根据其自身隐私政策收集、存储并使用这些内容。electerm 无法控制,也不对第三方服务商如何处理你的数据负责。

请仔细阅读所选服务商的隐私政策,并在使用 AI 功能时避免发送敏感或机密信息。

For full details, see the privacy notice:
https://github.com/electerm/electerm/wiki/privacy-notice-ios`,
SyncTermOfUse: `About data sync / 数据同步说明

When you sync your data (such as bookmarks, connection profiles, and notes) using GitHub Gist, Gitee Gist, WebDAV, or a custom sync server, your data is stored in accounts and services you control. This data is private to you and not publicly displayed; electerm does not collect, host, or have access to it. Data handling for these services is governed by the respective provider's privacy policy.

Please review the privacy policy of the sync service you choose.

当你使用 GitHub Gist、Gitee Gist、WebDAV 或自定义同步服务器进行同步时,你的数据(如书签、连接配置、笔记等)存储在你自己掌控的账户与服务中,仅你本人可见,electerm 不收集、不托管、也无法访问这些数据。这些服务的数据处理由其服务商隐私政策决定。

请仔细阅读所选同步服务的隐私政策。

For full details, see the privacy notice:
https://github.com/electerm/electerm/wiki/privacy-notice-ios`
}
const {
ENABLE_AUTH
Expand Down
109 changes: 109 additions & 0 deletions build/replace/src/client/entry-web/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* init app data then write main script to html body
*/
import '../electerm-react/css/basic.styl'
import '../web-components/style-overide.styl'
import '../electerm-react/css/mobile.styl'
import '../web-components/web-api.js'
import '../web-components/web-pre.js'
import { get as _get } from 'lodash-es'

// iOS WKWebView has no requestIdleCallback. bookmark-form's save path wraps
// addItem/editItem in runIdle() -> window.requestIdleCallback; without this
// polyfill every save silently throws "undefined is not a function" and the
// bookmark never reaches the store (connect/test paths don't use runIdle,
// which is why those buttons still work). Semantics follow the spec draft:
// call the callback ASAP with a deadline, support options.timeout, return a
// cancellable id.
if (typeof window.requestIdleCallback !== 'function') {
window.requestIdleCallback = function (cb, options) {
const start = Date.now()
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start))
}
})
}, options && options.timeout ? 1 : 0)
}
window.cancelIdleCallback = function (id) {
clearTimeout(id)
}
}

const { isDev, version, cdn } = window.et

window.et.buildWsUrl = (
host,
port,
tokenElecterm,
id,
type = 'terminals',
extra = ''
) => {
const ss = isDev ? window.et.server : window.location.href
const s = ss
? ss.replace(/https?:\/\//, '').replace(/\/$/, '')
: `${host}:${port}`
const pre = ss.startsWith('https') ? 'wss' : 'ws'
return `${pre}://${s}/${type}/${id}?token=${tokenElecterm}${extra}`
}

async function loadWorker () {
return new Promise((resolve) => {
const url = !isDev ? cdn + `/js/worker-${version}.js` : cdn + '/js/worker.js'
window.worker = new window.Worker(url)
function onInit (e) {
if (!e || !e.data) {
return false
}
const {
action
} = e.data
if (action === 'worker-init') {
window.worker.removeEventListener('message', onInit)
resolve(1)
}
}
window.worker.addEventListener('message', onInit)
})
}

async function load () {
window.capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1)
}
function loadScript () {
const rcs = document.createElement('script')
const url = !isDev ? cdn + `/js/electerm-${version}.js` : cdn + '/js/electerm.js'
rcs.src = url
rcs.type = 'module'
rcs.onload = () => {
const loadingEl = document.getElementById('content-loading')
if (loadingEl) {
document.body.removeChild(loadingEl)
}
}
document.body.appendChild(rcs)
}
window.getLang = (lang = window.store?.config.language || 'en_us') => {
return _get(window.langMap, `[${lang}].lang`)
}
window.translate = txt => {
const lang = window.getLang()
const str = _get(lang, `[${txt}]`) || txt
return window.capitalizeFirstLetter(str)
}
await loadWorker()
if (!window.et.isDev) {
window.worker.postMessage({
action: 'init-url',
url: window.location.href
})
}
loadScript()
}

// window.addEventListener('load', load)
load()
Loading
Loading